Laravel的"soft_delete"是否需要MySQL的索引?

rap*_*2-h 29 mysql indexing optimization laravel eloquent

如果我在laravel 4.2(数据库是mysql)中使用软删除,那么每个雄辩的构建查询都有WHERE deleted_at IS NULL.没有索引deleted_at.

  • 大桌子会慢吗?(或者可能没有索引优化IS NULL)
  • 我应该在soft_delete上添加索引吗?

那么,Laravel的"soft_delete"是否需要MySQL的索引?

N.B*_*.B. 51

该列deleted_at不是一个好的索引候选者.与评论相比,我会尝试更好地解释:索引仅在其基数相对较高时才有用.基数是描述数据集中索引唯一性的数字.这意味着它的总记录数除以总的唯一记录.

例如,主键的基数为1.每个记录包含主键的唯一值.1也是最高的数字.您可以将其视为"100%".

但是,像deleted_at这样的列没有这样的值.Laravel对deleted_at的作用是检查它是否为空.这意味着它有两个可能的值.包含两个值的列具有极低的基数,随着记录数量的增加而减少.

您可以索引此类列,但它不会有任何帮助.会发生什么事情会减慢速度并占用空间.

TL; DR:不,您不必索引该列,索引对性能没有任何有益影响.

  • @Cabloo - 有问题的人提到只有400万行的表.我观察到有5000万行表在没有索引的情况下表现良好.他们从索引布尔列中获得性能的一种情况并不足以证明.想象一下拥有1亿条记录的表格.只有一条记录包含"true"值.所以是的,执行诸如`WHERE x = true`之类的查询会很快,你会推断出布尔列的索引是很好的.那么`WHERE x = false`怎么样?2除以x,其中`x> 0和<infinity`告诉您随着数据的增长会浪费空间. (3认同)
  • 这是否意味着没有理由索引布尔列,还是我误解了? (2认同)
  • @Cabloo - 你没有误解,任何具有小范围唯一值的列都是一个糟糕的索引候选者,即使你有一个名为"0"和"1"的`tinyint`. (2认同)
  • @NB从[我一直在阅读的内容](http://stackoverflow.com/a/20504263/1190975),这与唯一值的数量无关,而与值的分布有关。因此,如果有50%的记录为真,则不会影响查询时间。但是,如果有5%的记录为真,那么它将减少查询时间。换句话说,这与“选择性”有关。为了使索引有用,对该索引的搜索在数据集中必须相对较少。 (2认同)
  • @NB - 如果`deleted_at` 是一个具有许多不同值且很少为NULL 的日期时间,则对其进行索引会很好。 (2认同)

Ric*_*mes 7

简短的回答:也许吧。

长答案:

如果有很少的不同deleted_at,MySQL将不会使用INDEX(deleted_at)

如果 中存在不同的非空日期deleted_at,MySQL 将使用INDEX(deleted_at).

大多数讨论(到目前为止)都未能考虑到这个单列索引的基数。

注意:这与 2 值标志(例如 )不同is_deleted 对于这样的单列索引是没有用的。

更多讨论(从MySQL的角度)

https://laravel.com/docs/5.2/eloquent#soft-deleting

现在,当您在模型上调用删除方法时,deleted_at 列将设置为当前日期和时间。并且,当查询使用软删除的模型时,软删除的模型将自动从所有查询结果中排除。

由此,我假设这发生在表定义中:

deleted_at  DATETIME  NULL  -- (or TIMESTAMP NULL)
Run Code Online (Sandbox Code Playgroud)

并且该值被初始化(显式或隐式)为NULL

情况 1:大量新行,尚未“删除”:所有值deleted_at都是NULL. 在这种情况下,优化器将INDEX(deleted_at)因为没有帮助而回避。事实上,使用索引会带来伤害,因为遍历整个索引数据会花费更多。忽略索引并简单地假设所有行都是 的候选行会更便宜SELECTed

情况 2:(多行中的)几行已被删除:现在deleted_at有多个值。虽然 Laravel 只关心IS NULLvs IS NOT NULL,但 MySQL 将其视为多值列。但是,由于测试是IS NULL且大多数行仍然是NULL,优化器的反应与情况 1 相同。

情况 3:软删除的行比仍然活动的行多得多:现在索引突然变得有用,因为只有表的一小部分IS NULL

案例 2 和案例 3 之间没有确切的界限。20% 是一个方便的经验法则。

现在,从执行的角度来看。

INDEX(deleted_at)用于deleted_at IS NULL

  1. 使用 向下钻取索引 BTree 以获得第一行NULL
  2. 扫描直至IS NULL失败。
  3. 对于每个匹配的行,进入数据BTree 以获取该行。

INDEX(deleted_at)未使用:

  1. 扫描数据BTree(或使用其他索引)
  2. 对于每个数据行,检查deleted_at IS NULL,否则过滤掉该行。

综合指数:

拥有以 开头的“复合”(多列)索引可能非常有益deleted_at。例子:

INDEX(deleted_at, foo)

WHERE deleted_at IS NULL
  AND foo BETWEEN 111 AND 222
Run Code Online (Sandbox Code Playgroud)

无论表的百分比是多少,deleted_at IS NULL这都很有可能有效地使用索引。

  1. NULL使用和向下钻取索引 BTree 以获得第一行foo >= 111
  2. 扫描直至IS NULLfoo <= 222失败。
  3. 对于每个匹配的行,进入数据BTree 以获取该行。

请注意,在 , 中INDEXNULL其行为与任何其他单个值非常相似。(并且NULLs存储在其他值之前。)

  • @Mjh - MySQL 优化的效果取决于 MySQL 明显的基数,而不是 Laravel 给出的_意图_。如果“deleted_at”实际上具有“NULL”或许多不同的“TIMESTAMP”,则 MySQL 会将其视为布尔值。相反,它(错误地)假设不同值的数量是均匀分布的。 (2认同)

dan*_*rds 6

我不知道为什么@NB 上面有这么多赞成票,在我的上下文中,我发现这完全不正确。

我在某些键表上为 delete_at 时间戳添加了索引,并享受了一些查询从 32 秒下降到小于 5.4 毫秒的乐趣。这实际上取决于您的应用程序的性质。

在我的场景中,我有 3 个带有软删除的表,一些简单的连接(全部带有索引),但是由于 Laravel 处理软删除的默认性质,我的查询受到了影响。

我强烈建议将这些列编入索引,这样您的应用程序就会在记录数量增加时阻塞。

迁移前。 32s查询时间。 索引1 索引2 索引3 迁移后。 5.4ms

  • 丹,你读过答案并试图理解它吗?看来你和 Rick James 都不知道 B 树是如何工作的。另外,在运行查询之前您是否清除了所有缓存?您确定“innodb_buffer_pool”包含数据吗?您在这里做了一个完全错误的假设 - 第一个查询,即 32 秒的查询,缓冲池中没有任何数据。一旦执行,它就会填满它。您的第二个查询现在使用内存中的数据。您错误地认为这是因为索引。 (2认同)
  • 我删除了以 2.7 毫秒运行查询的索引,并多次重新运行查询。我得到了 167s、142s 和 151s。`innodb_buffer_pool` 不是空的,我假设有一些优化,因为有轻微的增加?在此查询中的 3 个表上有一个 `deleted_at` IS NULL 检查,如果我删除 IS NULL 检查的 where 子句,查询会在 27.5 毫秒内触发,而没有删除的_at 索引。很明显,这些索引在标准中带来了巨大的好处。我不知道添加太多deleted_at 索引的后果,但我认为我不在乎这种经历。 (2认同)