小心 laravel 模型的 Soft Delete
这篇是关于一个 MySQL query optimizer 问题的定位及解决。
公司系统中一个简单的 SQL 查询却花费了近 3 秒的时间,语句大致为:
SELECT slug FROM productsWHERE id IN (id1, id2, ...)AND deleted_at IS NULL;
其中,id
是 PRIMARY KEY,deleted_at
记录删除时间,用于实现 soft delete,也加了索引。deleted_at
允许为空,为空代表是正常商品,不为空代表“已删除”的商品,同时记录下了删除时间。
并且在 Rails 的 ActiveRecord 中设置了 default scope 默认 deleted_at 为空。
这条 SQL 语句当然期待一直使用 PRIMARY index 实现查询效率最高,但是,结果却出人意料的成了 slow query。
使用 MySQL EXPLAIN 来查看查询的执行流程。结果如下:
MySQL EXPLAIN Output
ASCII version as gist
发现当给定的 ID 条件为 20 左右时,是正常地使用 PRIMARY index;但是,当给定的 ID 条件为 50 左右时,竟然使用了 deleted_at
的索引,要知道 deleted_at
为空的有几千万行!
EXPLAIN output 中 Extra 列出给了 Using index condition
,也就是 MySQL 只使用了 deleted_at
index 的数据,而不用读取任何表数据;这是 MySQL 的 ICP 优化。这个过程相当于:
- 读取
deleted_at
索引信息到内存; - 找出
deleted_at
为空的行的 ID(几千万行); - 在上面几千万行中找出 ID 在给定的 ID 列表中的行;
显然,这样效率是极其低下的,出现这种令人意外的行为,我猜测是由于 MySQL 最终获取数据不只一种方式,这就需要评估多种方式执行的复杂度。当指定 ID 比较少时,使用 PRIMARY index 这种方式的复杂度相较使用 deleted_at
index 小;当指定 ID 比较多时,在 MySQL 评估算法中,前一种方式的复杂度竟然诡异地大于了后一种方式。而真实的执行语句时的情况并非如此,让我们先猜测一下该评估算法的一些行为。
- 当指定的 ID 比较多,而且比较离散时,MySQL 认为需要读取更多的 BTree nodes;从 PRIMARY index 中获取到 ID 后,还要离散地读取表数据,从而过滤
deleted_at
为空的行; - 回头看看使用
deleted_at
index 这种方式呢,完全只需要使用 index 数据就可以完成;可以很好地利用 ICP 优化,简直完美。
等了解到更多 MySQL 查询优化后,再补充准确的原因吧。
解决方案
解决的办法当然是让 MySQL 尽量不要使用 deleted_at
index 索引了,可以有如下几种方法:
- 在每次使用 ID 查询时,利用 Index Hints 明确告诉 MySQL 使用哪个索引,这里就是
Product.force_index(:PRIMARY)
;其他条件查询时,还要 case by case 的分析是否使用 index hints; - 直接删除掉
deleted_at
的索引;之前习惯性的给这类列加索引,其实仔细考虑下,几乎没什么用,也就能优化下Product.count
这样的语句;其他查询几乎都不会用到该索引; - 不使用 soft delete;当然出现本文中的问题,怎么也怪不到 soft delete 的头上,不过多少也有 default scope 的问题。使用 soft delete 并不一定合适,我一直认为 soft delete is evil,就像我认为 ActiveRecord callback is evil;这个话题回头专门写吧;
小心 laravel 模型的 Soft Delete相关推荐
- php 数据透视表,php – 使用Laravel模型过滤数据透视表数据
假设我有三个表(这只是一个例子): users user_id username roles role_id name user_roles user_id role_id primary (bool ...
- 语句作用_3分钟短文:Laravel模型作用域,为你“节省”更多代码
引言 原则上代码写一次,处处是引用,不需要大量的冗余代码,这是一种趋势,也是提高代码健壮性的努力方向. laravel模型为我们提供了一层数据库操作层,将数据交互独立出来. 但是久而久之,随着项目的需 ...
- php一对一模型关联,通过实例学习Laravel模型中的一对一关联关系
通过实例学习Laravel模型中的一对一关联关系 一.前言 Laravel遵循[约定优于配置]的原则.PHP开发者只需要遵循Laravel框架的原则,就能减少大量的工作,这便是Laravel的魅力之一 ...
- laravel 模型(2)
//模型文件app/models/Archives.php class Archives extends Eloquent{ protected $table = 'archives';public ...
- 如何更好的组织你的Laravel模型
2019独角兽企业重金招聘Python工程师标准>>> 我经常发现自己希望在Laravel应用程序中获得更多关于模型的结构. 默认情况下,模型位于 App 命名空间内,如果你正在处理 ...
- laravel 模型里自定义属性_关于Laravel 7 的简单隐式路由模型绑定
Laravel 的下一个主要发行版本 ,你可以直接在路由定义中自定义隐式路由模型绑定: Route::get('/posts/{post:slug}', function (Post $post) { ...
- 如何更好的组织你的Laravel模型 1
我经常发现自己希望在Laravel应用程序中获得更多关于模型的结构. 默认情况下,模型位于 App 命名空间内,如果你正在处理大型应用程序,这可能会变得非常难以理解.所以我决定在 App\Models ...
- Laravel 模型中 $hidden 的作用
看源码的注释,$hidden 定义的属性在被 序列化 的时候会被隐藏. 文档解释:https://laravel.com/docs/5.5/eloquent-serialization#hiding- ...
- Laravel 模型
Laravel学院文档 获取模型 get ,all 都可以获取到模型 all 是直接获取所有,get 是在添加了许多约束之后获取模型,get前面如果不加约束条件的话,效果与all等同 App\User ...
最新文章
- 皮一皮:爷的青春一去不回了...
- QSharedMemory共享内存实现进程间通讯(IPC)及禁止程序多开
- 字符串固定长度 易语言_易语言宽字符数据类型怎么设置
- git 无法提交空目录
- NASM汇编语言与计算机系统02-实模式-显存原理
- [220208] Add Digits
- linux中sed和find,Linux运维知识之Linux 之 sed 与 find 命令结合使用
- Keras实现text classification文本二分类
- 翻译:如何理解K-means的缺点
- 二维码加logo demo
- 魔兽怀旧服服务器怎么修改,魔兽世界怀旧服今日开服 魔兽世界怀旧服剥皮制皮玩法攻略 怀旧服服务器连不上怎么办?...
- sql中将字符串转换成日期
- 深入理解Arduino下的ESP8266_Non-OS_SDK API① Non-OS SDK
- 小波调研(三):小波阈值去噪分析
- linux下打开png图片不显示,r – 无法显示png
- java项目-第71期基于ssm的化妆品商城系统【毕业设计】
- Dijkstra算法正确性证明
- C语言while和do-while练习题
- CatiaMagic — 基于MBSE的产品创新和正向开发工具
- 损失惨重:近期安全事件频发