Laravel chunk和chunkById的坑

在编写定时任务脚本的时候,经常会用到chunk和chunkById的API。

一、前言

数据库引擎为innodb。

表结构简述,只列出了本文用到的字段。

字段类型注释
idint(11)ID
typeint(11)类型
mark_timeint(10)标注时间(时间戳)

索引,也只列出需要的部分。

索引名字段
PRIMARYid
idx_sid_blogdel_marktimetype blog_del mark_time
Idx_marktimemark_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){// 业务处理
});

从一个月中的数据,筛选出type为99,并且标注时间在某天的00:00:00-23:59:59的数据。可以使用到mark_time和type的索引。

type为99,一天的数据大概在15-25w上下的样子。使用->get()->toArray()内存会直接炸掉。所以使用chunk方法,每次取出1000条数据。

使用chucnk,不会出现内存不够的情况。但是性能较差。粗略估计,从一月数据中取出最后一天的数据,跑完20w数据大概需要一两分钟。

查看源码,底层的chunk方法,是为sql语句添加了限制和偏移量。

select * from users asc limit 500 offset 500;

在数据较多的时候,越往后的话效率会越慢,因为Mysql的limit方法底层是这样的。

limit 10000,10

是扫描满足条件的10010行,然后扔掉前面的10000行,返回最后最后20行。在数据较多的时候,性能会非常差。

查了下API,对于这种情况Laraverl提供了另一个API chunkById。

四、chunkById 原理

使用limit和偏移量在处理大量的数据会有性能的明显下降。于是chunkById使用了id进行分页处理。很好理解,代码如下:

select * from users where id > :last_id order by id asc limit 500;

API会自动保存最后一个ID,然后通过id > :last_id 再加上limit就可以通过主键索引进行分页。只取出来需要的行数。性能会有明显的提升。

五、chunkById的坑

API显示chunk和chunkById的用法完全相同。于是把脚本的代码换成了chunkById。

$this->dao->where('type', 99)->whereBetween('mark_time', [$date, $date+86399])->select(array('mark_time', 'id'))->chunkById(1000, function ($rows){// 业务处理
});

在执行脚本的时候,1月2号和1月1号的数据没有任何问题。执行速度快了很多。但是在执行12月31号的数据的时候,发现脚本一直执行不完。

在定位后发现是脚本没有进入业务处理的部分,也就是sql一直没有执行完。当时很疑惑,因为刚才执行的没问题,为什么执行12月31号的就出问题了呢。

于是查看sql服务器中的执行情况。

show full processlist;

发现了问题。上节说了chunkById的底层是通过id进行order by,然后limie取出一部分一部分的数据,也就是我们预想的sql是这样的。

select * from tabel where type = 99 and mark_time between :begin_date and :end_date limit 500;

explain出来的情况如下:

在这里插入图片描述

实际上的sql是这样的:

select * from tabel where type = 99 and mark_time between :begin_date and :end_date order by id limit 500;

实际explain出来的情况是这样的:

在这里插入图片描述

chunkById会自动添加order by id。innodb一定会使用主键索引。那么就不会再使用mark_time的索引了。导致sql执行效率及其缓慢。

六、解决方法

再次查看chunkById的源码。

/*** Chunk the results of a query by comparing IDs.** @param  int  $count* @param  callable  $callback* @param  string|null  $column* @param  string|null  $alias* @return bool*/
public function chunkById($count, callable $callback, $column = null, $alias = null)
{$column = $column ?? $this->defaultKeyName();$alias = $alias ?? $column;$lastId = null;do {$clone = clone $this;// We'll execute the query for the given page and get the results. If there are// no results we can just break and return from here. When there are results// we will call the callback with the current chunk of these results here.$results = $clone->forPageAfterId($count, $lastId, $column)->get();$countResults = $results->count();if ($countResults == 0) {break;}// On each chunk result set, we will pass them to the callback and then let the// developer take care of everything within the callback, which allows us to// keep the memory low for spinning through large result sets for working.if ($callback($results) === false) {return false;}$lastId = $results->last()->{$alias};unset($results);} while ($countResults == $count);return true;
}

能看到这个方法有四个参数count,callback,column,alias。
默认的column为null,第一行会进行默认赋值。

/*** Get the default key name of the table.** @return string*/
protected function defaultKeyName()
{return 'id';
}

能看到默认的column为id。
进入forPageAfterId方法。

/*** Constrain the query to the next "page" of results after a given ID.** @param  int  $perPage* @param  int|null  $lastId* @param  string  $column* @return \Illuminate\Database\Query\Builder|static*/
public function forPageAfterId($perPage = 15, $lastId = 0, $column = 'id')
{$this->orders = $this->removeExistingOrdersFor($column);if (! is_null($lastId)) {$this->where($column, '>', $lastId);}return $this->orderBy($column, 'asc')->limit($perPage);
}

能看到如果lastId不为0则自动添加where语句,还会自动添加order by column。

看到这里就明白了。上文的chunkById没有添加column参数,所以底层自动添加了order by id。走了主键索引,没有使用上mark_time的索引。导致查询效率非常低。

chunkById的源码显示了我们可以传递一个column字段来让底层使用这个字段来order by。

代码修改如下:

$this->dao->where('type', 99)->whereBetween('mark_time', [$date, $date+86399])->select(array('mark_time', 'id'))->chunkById(1000, function ($rows){// 业务处理
}, 'mark_time');

这样最后执行的sql如下:

select * from tabel where type = 99 and mark_time between :begin_date and :end_date order by mark_time limit 500;

再次执行脚本,大概执行一次也就十秒作用了,性能提升显著。

七、总结

使用 chunkById 或者 chunk 方法的时候不要添加自定义的排序,chunk和chunkById的区别就是chunk是单纯的通过偏移量来获取数据,chunkById进行了优化,不使用偏移量,使用id过滤,性能提升巨大。在数据量大的时候,性能可以差到几十倍的样子。

而且使用chunk在更新的时候,也会遇到数据会被跳过的问题。详见解决Laravel中chunk方法分块处理数据的坑

同时chunkById在你没有传递column参数时,会默认添加order by id。可能会遇到索引失效的问题。解决办法就是传递column参数即可。

本人感觉chunkById不光是根据Id分块,而是可以根据某一字段进行分块,这个字段是可以指定的。叫chunkById有一些误导性,chunkByColumn可能更容易理解。

使用chunkById时不要加排序
使用chunkById比chunk更快

原文

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/63761.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

机器学习笔记之核函数再回首:Nadarya-Watson核回归python手写示例

机器学习笔记之核函数再回首——Nadaraya-Watson核回归手写示例 引言回顾: Nadaraya-Watson \text{Nadaraya-Watson} Nadaraya-Watson核回归通过核函数描述样本之间的关联关系使用 Softmax \text{Softmax} Softmax函数对权重进行划分将权重与相应标签执行加权运算 N…

Linux centos7 bash编程——-求质数和

训练项目:使用函数求质数和。 定义一个函数IsPrime(),据此判断一个数是否为质数 由用户输入一个整数,求出比此数大的两个最小质数之和。 一、解决思路: 1.先在键盘上输入一个整数 2.求出比此数大的最小质数 3.再求出比此质数大的另一个…

ChatGPT 实现动态地图可视化展示

地图可视化分析有许多优点和好处: 1.直观理解:地图可视化使得复杂的数据更易于理解。通过地图可视化,人们可以直观地看到地理位置、地区之间的关系以及空间分布的模式。 2.提高决策效率:地图可视化可以帮助决策者快速理解和解释数据,从而提高决策效率。 3.高效的数据整…

【Pandas 入门-5】Pandas 画图

Pandas 画图 除了结合 matplotlib 与 seaborn 画图外,Pandas 也有自己的画图函数plot,它的语法一般为: DataFrame.plot(xNone,yNone, kindline,subplotsFalse, titleNone)x横坐标数据y纵坐标数据kind默认是线图,还可以是‘bar’…

基于单片机的串行通信发射机设计

一、项目介绍 串行通信是一种常见的数据传输方式,允许将数据以比特流的形式在发送端和接收端之间传输。当前实现基于STC89C52单片机的串行通信发射机,通过红外发射管和接收头实现自定义协议的数据无线传输。 二、系统设计 2.1 单片机选择 在本设计中&…

缓存技术(缓存穿透,缓存雪崩,缓存击穿)

大家好 , 我是苏麟 , 今天聊一聊缓存 . 这里需要一些Redis基础 (可以看相关文章等) 本文章资料来自于 : 黑马程序员 如果想要了解更详细的资料去黑马官网查看 前言:什么是缓存? 缓存,就是数据交换的 缓冲区 (称作Cache [ kʃ ] ),俗称的缓存就是缓冲区内的数据,是存贮数据的…

C语言——多文件编程

多文件编程 把函数声明放在头文件xxx.h中,在主函数中包含相应头文件在头文件对应的xxx.c中实现xxx.h声明的函数 防止头文件重复包含 当一个项目比较大时,往往都是分文件,这时候有可能不小心把同一个头文件 include 多次,或者头…

十六、pikachu之SSRF

文章目录 1、SSRF概述2、SSRF(URL)3、SSRF(file_get_content) 1、SSRF概述 SSRF(Server-Side Request Forgery:服务器端请求伪造):其形成的原因大都是由于服务端提供了从其他服务器应用获取数据的功能&…

Spring容器及实例化

一、前言 Spring 容器是 Spring 框架的核心部分,它负责管理和组织应用程序中的对象(Bean)。Spring 容器负责创建、配置和组装这些对象,并且可以在需要时将它们提供给应用程序的其他部分。 Spring 容器提供了两种主要类型的容器&…

matlab绘制局部放大图

ZoomPlot是一个交互式的matlab局部绘图库,其github仓库地址为 https://github.com/iqiukp/ZoomPlot-MATLAB。在使用库之前需要先将库下载到本地,可以直接添加到matlab的库中,也可以放在项目文件中直接使用。 简单使用 其实使用这个库只需要…

【SpringCloud】SpringCloud整合openFeign

文章目录 前言1. 问题分析2. 了解Feign3. 项目整合Feign3.1 引入依赖3.2 添加注解3.3 编写Feign客户端3.4 测试3.5 总结 4. 自定义配置4.1 配置文件方式4.2 Java代码方式 5. Feign使用优化5.1 引入依赖5.2 配置连接池 6. Feign最佳实践6.1 继承方式6.2 抽取方式 前言 微服务远…

MySQL连接池配置及FullGC分析

本文主要讲述MySQL连接池配置不合适时,由于MySQL以虚引用的方式作为线程清理的后备手段,导致JVM年老代随时间缓慢增长,直至FullGC的问题。为了优化数据库连接池配置,使得JVM进行尽量少的FullGC导致服务故障,本文提供了…

解决springboot项目中的groupId、package或路径的混淆问题

对于像我一样喜欢跳跃着学习的聪明人来说,肯定要学springboot,什么sevlet、maven、java基础,都太老土了,用不到就不学。所以古代的聪明人有句话叫“书到用时方恨少”,测试开源项目时,编译总是报错&#xff…

为什么中国软件需要国产化?

国产化是指技术引进项目投产后所生产的产品中,国内生产件的数量占整件产品生产件数量。换句话说,软件国产化的占比,直接影响到技术是否会在某一个时点上被”卡脖子“。 随着国家经济的发展和技术水平的提高,国家整体实力大大增强…

跨足多领域:人脸美颜SDK在医疗、娱乐和安全中的应用案例

随着科技的不断发展,人脸美颜技术不再局限于满足用户的审美需求,而是在医疗、娱乐和安全领域展现出了广泛的应用前景。本文将深入探讨人脸美颜SDK 在这三个领域中的创新应用案例,展示其在不同场景中的独特价值和潜力。 一、医疗领域 1、皮…

2023腾讯全球数字生态大会预约报名入口

报名入口 2023腾讯全球数字生态大会即将开启,点击打开预约报名入口。 主题与介绍 主题 2023腾讯全球数字生态大会将聚焦产业未来发展新趋势,针对云计算、大数据、人工智能、安全、SaaS等核心数字化工具做关键进展发布,并联合生态伙伴推出最…

用Rust打印hello world!

步骤1 桌面新建1个名为 rustDemo 的文件夹(文件夹名字随便取) 步骤2 打开新建的文件夹,在地址输入栏输入 cmd 按回车键进入命令行窗口 步骤3 打开编译器,按 Ctrl S,保存文件到 rustDemo 文件夹中,保存的…

【git】从一个git仓库迁移到另外一个git仓库

在远端服务器创建一个新的仓库 用界面创建&#xff0c;当然也可以用命令创建 拉去源仓库 git clone --bare git192.168.10.10:java/common.gitgit clone --bare <旧仓库地址>拉去成功以后会出现 进入到文件夹内部 出现下面信息&#xff1a; 推送到新的远端仓库 git …

【IOTE】物联网射频模组和芯片级方案提供商——深圳信驰达科技将精彩亮相IOTE物联网展

►►►强势来袭 Strong Attack 主物联场&#xff0c;相约深圳&#xff1b;2023&#xff0c;共论商机&#xff01;IOTE2023第二十届国际物联网展深圳站将于2023年9月20-22日在深圳国际会展中心(宝安新馆)开展&#xff01;汇聚全球超800家参展企业&#xff0c;呈现更多数字化纷呈…

C# PaddleDetection yolo 印章检测

效果 项目 代码 using OpenCvSharp; using OpenCvSharp.Extensions; using Sdcb.PaddleDetection; using Sdcb.PaddleInference; using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq…