
本文共 2520 字,大约阅读时间需要 8 分钟。
Laravel chunk和chunkById的坑
前言
数据库引擎为InnoDB。以下为表结构简述及索引情况:
字段 | 类型 | 注释 |
---|---|---|
id | int(11) | ID |
type | int(11) | 类型 |
mark_time | int(10) | 标注时间(时间戳) |
索引
索引名 | 字段 |
---|---|
PRIMARY | id |
idx_sid_blogdel_marktime | type, blog_del, mark_time |
Idx_marktime | mark_time |
需求
每天凌晨一点,取出昨天标注type为99的所有数据,进行逻辑判断和其他操作。重点在于取数据阶段。数据按月分表,每个月表中数据量为1000w左右。
chunk处理数据
初始代码如下:
$this->dao->where('type', 99)->whereBetween('mark_time', [$date, $date+86399])->select(array('mark_time', 'id'))->chunk(1000, function ($rows) { // 业务处理});
chunk方法的获取方式可以避免一次性获取所有数据至内存,但在处理10000w数据时,chunk的方式会导致查询效率很低。
chunk的内部实现:
select * from `表` asc limit 500 offset 500;
在处理大数据量时,Mysql的这种处理方式由于会扫描大量数据,导致性能问题。
chunkById的原理
chunkById通过id字段进行分页,优化性能。使用方法如下:
$this->dao->where('type', 99)->whereBetween('mark_time', [$date, $date+86399])->select(array('mark_time', 'id'))->chunkById(1000, function ($rows) { // 业务处理});
chunkById的内部查询:
select * from `表` where `id` > :last_id order by `id` asc limit 500;
这种方式通过主键id进行分页,每次查询控制在500条内,性能较好。
chunkById的坑
改用chunkById后,在处理13月31日的数据时,发现脚本未能进入业务处理部分,无法完成查询。经过检查发现,chunkById默认使用id字段作为分页依据,可能导致索引失效,影响查询性能。
chunkById的查询结果仅使用mark_time索引:
select * from `表` where `type` = 99 and mark_time between :begin_date and :end_date order by id limit 500;
此时,chunkById未使用mark_time索引,导致查询效率很低。
解决方法
检查chunkById的源码:
public function chunkById($count, callable $callback, $column = null, $alias = null){ $column = is_null($column) ? $this->getModel()->getKeyName() : $column; $alias = is_null($alias) ? $column : $alias; $lastId = null; do { $clone = clone $this; $results = $clone->forPageAfterId($count, $lastId, $column)->get(); $countResults = $results->count(); if ($countResults == 0) { break; } if ($callback($results) === false) { return false; } $lastId = $results->last()->{$alias}; unset($results); } while ($countResults == $count); return true;}
默认情况下,chunkById使用id字段作为分页依据。若想指定mark_time字段作为分页依据,可以通过方法参数传递:
$this->dao->where('type', 99)->whereBetween('mark_time', [$date, $date+86399])->select(array('mark_time', 'id'))->chunkById(1000, function ($rows) { // 业务处理}, 'mark_time');
这样查询方式得到优化:
select * from `表` where `type` = 99 and mark_time between :begin_date and :end_date order by mark_time limit 500;
此时,数据库会使用mark_time索引,查询效率明显提升。
总结
chunk与chunkById的主要区别在于后者通过主键字段分页,而默认时使用id字段,可能导致索引失效。若想优化查询方式,建议指定需要排序的字段作为分页依据。
特别提醒:未传递column参数时,默认使用id字段,可能导致索引失效,影响性能。建议根据应用需求传递合适的字段。
以上是对实际案例中的技术问题及解决方案的总结,如有疑问或需要进一步的帮助,欢迎随时留言。
发表评论
最新留言
关于作者
