为什么 MySQL 在搜索索引列时不锁定行

Why does MySQL not lock rows while searching indexed column

比如我锁定了一些行:

select * from t1 where c2 = 1 for update;

c2 未编入索引。这意味着 MySQL 必须搜索整个 table,如果它读取未提交或读取已提交的隔离级别,它会在每个扫描的行上放置锁,如果它不满足 WHERE 条件,它会立即释放锁。

如果是repeatable读取那些不满足WHERE条件的锁一直保留到事务结束

当 MySQL 出于某种原因搜索索引列时,它不会锁定不满足 WHERE 条件的行。是的,它使用另一种算法,允许它在 3-4 次提取中找到行,但在找到正确的行之前它仍然会接触到一些行。

实际上,真正的问题不是为什么 MySQL 在使用索引时不锁定行,而是为什么它在 使用索引时锁定这些行.

稍微简化一下(并取决于您的隔离级别),为您的查询发出的锁可以防止两件事:

  • 具有 c=1 的行不应由不同的事务更改,例如c=2,因为它不再满足您的原始条件

  • 不应将带有 c=2 的行更改为 c=1(并且不应插入带有 c=1 的新行),因为它现在可以满足您的需求原始条件(因此如果其他交易先出现,您的查询就会选择它)

锁定第一个要点基本上只需要使用 c=1 锁定行。索引和未索引情况之间没有根本区别:当前具有 c=1 的行最终将被锁定。

然而,对于第二个要点,那就更棘手了:

对于未索引的情况,不幸的是,MySQL 无法区分另一个事务是将 c=2 的行更改为 c=5(这没问题)还是c=2c=1(必须避免)。由于锁定太多行总比锁定不够好,因此 MySQL 会这样做:锁定所有行。这确保它可以防止将 c=2 修改为 c=1。其他行上的锁是抵押品,您必须为更大的利益(或不添加索引)付出代价。

MySQL 将保持这种“过度保护行为”:为了防止使用 c=1 插入新行,它基本上会阻止 any 插入(进入主键)通过,嗯,锁定所有行(和间隙,我不会在这里详细介绍)。

对于索引情况,MySQL 有另一个选择:如果修改想要将 c=2 的行更改为 c=1,则需要该行获得一个新条目在索引的 c=1 区域。因此 MySQL 可以“只是”在此处放置一个锁以防止注入(插入或更新)到 c=1 处的索引中。基本上,它不必为了安全而先发制人地锁定所有行,但可以在事后检测到不允许的修改。