Laravel“软删除”是不是需要 MySQL 上的索引?
Posted
技术标签:
【中文标题】Laravel“软删除”是不是需要 MySQL 上的索引?【英文标题】:Does Laravel's "soft_delete" need index on MySQL?Laravel“软删除”是否需要 MySQL 上的索引? 【发布时间】:2014-12-18 22:37:35 【问题描述】:如果我在 laravel 4.2 中使用软删除(数据库是 mysql),每个 eloquent 查询都有WHERE deleted_at IS NULL
。 deleted_at
上没有索引。
IS NULL
不需要索引就可以优化)
我应该在deleted_at
列上添加索引吗?
那么,Laravel 的“soft_delete”deleted_at
列在 MySQL 中是否需要索引?
澄清:Laravel 在deleted_at
列中存储一个时间戳来表示记录何时被软删除,而不是布尔值。
【问题讨论】:
它不需要索引。如果为空,则不会被删除。如果它有值,则将其删除。这意味着它有两个可能的值才能工作。具有两个可用值的列不是很好的索引候选 - 它们的基数收敛到 0。因此,deleted_at 不需要索引。 好的,谢谢。我认为几乎每个where
子句都需要编入索引。如果需要,您可以发表评论作为答案。
【参考方案1】:
deleted_at
列不是一个好的索引候选。与评论相比,我将尝试更好地解释:索引仅在其基数相对较高时才有用。基数是描述数据集中索引唯一性的数字。这意味着它是记录总数除以唯一记录总数。
例如,主键的基数是1。每条记录都包含唯一的主键值。 1 也是最高的数字。您可以将其视为“100%”。
但是,deleted_at 之类的列没有这样的值。 Laravel 对 deleted_at 所做的是检查它是否为空。这意味着它有两个可能的值。包含两个值的列的基数极低,随着记录数的增加而降低。
您可以为这样的列建立索引,但它没有任何帮助。将会发生的事情是它可能会减慢速度并占用空间。
TL;DR:不,您不必为该列建立索引,索引不会对性能产生有益影响。
【讨论】:
这是否意味着没有理由为布尔列建立索引,还是我误解了? @Cabloo - 你没有误解,任何具有小范围唯一值的列都是错误的索引候选,即使你有一个tinyint
其值为 0
和 @987654325 @.
@N.B.从what I have been reading 开始,它不是关于唯一值的数量,而是关于值的分布。所以如果 50% 的记录为真,则不会影响查询时间。但是如果 5% 的记录是真的,那么它会减少查询时间。换句话说,它是关于“选择性”的。为了使索引有用,对该索引的搜索在数据集中必须相对较少。
@Cabloo - 有问题的人提到了只有 400 万行的表。我观察到 5000 万行表在没有索引的情况下表现良好。他们通过索引布尔列获得性能的一种情况并不足以证明这一点。想象一个有 1 亿条记录的表。只有一条记录包含true
值。所以是的,执行WHERE x = true
之类的查询会很快,并且您会推断为布尔列建立索引很棒。 WHERE x = false
呢? 2 除以 x,其中x > 0 and < infinity
告诉您,随着数据的增长,您将浪费空间。
@N.B. - 如果deleted_at
是一个包含许多不同值且很少为 NULL 的日期时间,那么索引它会很好。【参考方案2】:
我不知道为什么以上@N.B.有这么多的赞成票,在我的上下文中,我认为这完全不正确。
我在一些键表的deleted_at 时间戳中添加了索引,并享受了一些从32 秒缩短到5.4 毫秒以下的查询。这实际上取决于您应用的性质。
在我的场景中,我有 3 个带有软删除的表,一些简单的连接(都带有索引),但是由于 Laravel 处理软删除的默认性质,我的查询受到了影响。
我强烈建议为这些列编制索引,这样当记录数量增加时,您的应用程序就会阻塞。
【讨论】:
这两个例子都是可疑的——你没有显示有什么索引,尤其是那些将用于JOINing
的索引——它不会出现在deleted_at
上。 GROUP BY
也很重要。请同时显示SHOW CREATE TABLE
和EXPLAIN SELECT ...
丹,您是否阅读了答案并试图理解它?似乎您或 Rick James 都不知道 b-tree 是如何工作的。另外,您在运行查询之前是否清除了所有缓存?您确定innodb_buffer_pool
包含数据吗?您在这里做了一个完全错误的假设 - 第一个查询,即 32 秒的查询,在缓冲池中没有任何数据。一旦执行,它就会填满它。您的第二个查询现在使用内存中的数据。你错误地认为这是因为索引。
这些是可疑的查询——它们测试 any 变体是否为 NULL。使用EXISTS
并摆脱GROUP BY
可以更有效地完成此操作。
@Mjh - 核心点是优化器是否会使用 BTree 组织的索引。其次,如果它必须加载整个索引(或表),那么查看数百万条缓存记录可能需要 5 秒。
我删除了在 2.7 毫秒运行查询的索引并多次重新运行查询。我得到了 167、142 和 151。 innodb_buffer_pool
不是空的,我认为有一些优化,因为有轻微的增加?在此查询中有一个deleted_at
IS NULL 检查,如果我删除 IS NULL 检查的 where 子句,则查询会在 27.5 毫秒内触发,而没有 deleted_at 索引。很明显,这些指数在标准中带来了巨大的好处。我不知道添加太多deleted_at 索引的后果,但我认为我不会在意这次经历。【参考方案3】:
简短回答:也许。
长答案:
如果deleted_at
中的不同 值很少,MySQL 将不会使用INDEX(deleted_at)
。
如果deleted_at
中有不同的非空日期,MySQL 将使用INDEX(deleted_at)
。
(到目前为止)大多数讨论都没有考虑到这个单列索引的基数。
注意:这与 is_deleted
这样的 2 值标志不同。 在此类上设置单列索引是没有用的。
更多讨论(从 MySQL 的角度)
https://laravel.com/docs/5.2/eloquent#soft-deleting 说
现在,当您在模型上调用 delete 方法时,deleted_at 列将被设置为当前日期和时间。并且,当查询使用软删除的模型时,软删除的模型将自动从所有查询结果中排除。
据此,我假设这发生在表定义中:
deleted_at DATETIME NULL -- (or TIMESTAMP NULL)
并且值被初始化(显式或隐式)为NULL
。
案例 1:许多新行,尚未“删除”:所有 deleted_at
值都是 NULL
。在这种情况下,优化器会避开INDEX(deleted_at)
,因为它没有帮助。事实上,使用索引会受到伤害,因为遍历整个索引和数据会花费更多。忽略索引并简单地假设所有行都是SELECTed
的候选者会更便宜。
案例 2:删除了几行(多行):现在 deleted_at
有多个值。虽然 Laravel 只关心 IS NULL
与 IS NOT NULL
,但 MySQL 将其视为多值列。但是,由于测试是针对IS NULL
并且大多数行仍然是NULL
,因此优化器的反应与案例1 相同。
案例 3:软删除的行多于仍然处于活动状态的行:现在索引突然变得有用,因为只有一小部分表 IS NULL
。
案例 2 和案例 3 之间没有确切的分界线。20% 是一个方便的经验法则。
现在,从执行的角度来看。
INDEX(deleted_at)
用于deleted_at IS NULL
:
-
使用
NULL
向下钻取第一行的索引 BTree。
扫描直到IS NULL
失败。
对于每个匹配的行,进入 data BTree 以获取该行。
INDEX(deleted_at)
未使用:
-
扫描数据 BTree(或使用其他索引)
对于每个 data 行,检查
deleted_at IS NULL
,否则过滤掉该行。
复合索引:
可能有一个以deleted_at
开头的“复合”(多列)索引。示例:
INDEX(deleted_at, foo)
WHERE deleted_at IS NULL
AND foo BETWEEN 111 AND 222
这很有可能有效地使用索引无论表中有多少百分比有deleted_at IS NULL
。
-
使用
NULL
和foo >= 111
向下钻取第一行的索引BTree。
扫描直到IS NULL
或foo <= 222
失败。
对于每个匹配的行,进入 data BTree 以获取该行。
请注意,在INDEX
中,NULL
的行为与任何其他单个值非常相似。 (并且NULLs
存储在其他值之前。)
【讨论】:
@Mjh - MySQL 优化的效果取决于 MySQL 明显的基数,而不是 Laravel 提供的 intent。如果deleted_at
实际上有NULL
或许多不同的TIMESTAMPs
,那么MySQL 会将其视为布尔值。相反,它(错误地)假设不同值的数量是均匀分布的。以上是关于Laravel“软删除”是不是需要 MySQL 上的索引?的主要内容,如果未能解决你的问题,请参考以下文章