这篇是关于一个 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相关推荐

  1. php 数据透视表,php – 使用Laravel模型过滤数据透视表数据

    假设我有三个表(这只是一个例子): users user_id username roles role_id name user_roles user_id role_id primary (bool ...

  2. 语句作用_3分钟短文:Laravel模型作用域,为你“节省”更多代码

    引言 原则上代码写一次,处处是引用,不需要大量的冗余代码,这是一种趋势,也是提高代码健壮性的努力方向. laravel模型为我们提供了一层数据库操作层,将数据交互独立出来. 但是久而久之,随着项目的需 ...

  3. php一对一模型关联,通过实例学习Laravel模型中的一对一关联关系

    通过实例学习Laravel模型中的一对一关联关系 一.前言 Laravel遵循[约定优于配置]的原则.PHP开发者只需要遵循Laravel框架的原则,就能减少大量的工作,这便是Laravel的魅力之一 ...

  4. laravel 模型(2)

    //模型文件app/models/Archives.php class Archives extends Eloquent{ protected $table = 'archives';public ...

  5. 如何更好的组织你的Laravel模型

    2019独角兽企业重金招聘Python工程师标准>>> 我经常发现自己希望在Laravel应用程序中获得更多关于模型的结构. 默认情况下,模型位于 App 命名空间内,如果你正在处理 ...

  6. laravel 模型里自定义属性_关于Laravel 7 的简单隐式路由模型绑定

    Laravel 的下一个主要发行版本 ,你可以直接在路由定义中自定义隐式路由模型绑定: Route::get('/posts/{post:slug}', function (Post $post) { ...

  7. 如何更好的组织你的Laravel模型 1

    我经常发现自己希望在Laravel应用程序中获得更多关于模型的结构. 默认情况下,模型位于 App 命名空间内,如果你正在处理大型应用程序,这可能会变得非常难以理解.所以我决定在 App\Models ...

  8. Laravel 模型中 $hidden 的作用

    看源码的注释,$hidden 定义的属性在被 序列化 的时候会被隐藏. 文档解释:https://laravel.com/docs/5.5/eloquent-serialization#hiding- ...

  9. Laravel 模型

    Laravel学院文档 获取模型 get ,all 都可以获取到模型 all 是直接获取所有,get 是在添加了许多约束之后获取模型,get前面如果不加约束条件的话,效果与all等同 App\User ...

最新文章

  1. 皮一皮:爷的青春一去不回了...
  2. QSharedMemory共享内存实现进程间通讯(IPC)及禁止程序多开
  3. 字符串固定长度 易语言_易语言宽字符数据类型怎么设置
  4. git 无法提交空目录
  5. NASM汇编语言与计算机系统02-实模式-显存原理
  6. [220208] Add Digits
  7. linux中sed和find,Linux运维知识之Linux 之 sed 与 find 命令结合使用
  8. Keras实现text classification文本二分类
  9. 翻译:如何理解K-means的缺点
  10. 二维码加logo demo
  11. 魔兽怀旧服服务器怎么修改,魔兽世界怀旧服今日开服 魔兽世界怀旧服剥皮制皮玩法攻略 怀旧服服务器连不上怎么办?...
  12. sql中将字符串转换成日期
  13. 深入理解Arduino下的ESP8266_Non-OS_SDK API① Non-OS SDK
  14. 小波调研(三):小波阈值去噪分析
  15. linux下打开png图片不显示,r – 无法显示png
  16. java项目-第71期基于ssm的化妆品商城系统【毕业设计】
  17. Dijkstra算法正确性证明
  18. C语言while和do-while练习题
  19. CatiaMagic — 基于MBSE的产品创新和正向开发工具
  20. 损失惨重:近期安全事件频发

热门文章

  1. 聚名商学院:域名起名遵循的原则
  2. 《Python编程从入门到实践 第二版》第九章练习
  3. 梦幻诛仙服务器数据修改,《梦幻诛仙》2010年4月7日改良服务器架构
  4. Android中各种Span的用法
  5. 暗黑血统2 android,暗黑血统2BT版
  6. 【C盘炸了】利用分区助手扩充C盘
  7. iphone开发每日一练【2011-10-24】
  8. 人工智能入行攻略:数据缺陷挖掘与可解释分析(附百度高工直播讲解与动手实践)...
  9. c 语言程序设计1填空,C语言程序设计试题1
  10. kafka 运维中遇到的问题