首先,存在三种锁上下文:
然后你有四种锁定模式:
IX 和 IS 锁是“意图”锁。这些锁在获取其他类型的锁之前被持有。X 锁是排他(写)锁,S 锁是共享(读)锁。
可以在任何上下文级别获取锁(IX、IS、X 或 S)锁。例如,数据库级别的 X 锁将阻止数据库中的所有其他操作。这是 SQLlite 采用的锁类型。在读取期间为整个数据库获取 S 锁,在写入期间为整个数据库获取 X 锁。写入将等待任何 S 锁完成,并将阻止新的 S 和 X 锁,直到释放写入锁为止。这提供了可序列化的隔离事务级别。
对于 MySQL,锁定取决于存储引擎。MyISAM 将在整个(组)表上使用 X 和 S 锁。X 锁将等待现有的 S 或 X 锁并阻止新锁。新的 X 锁将在队列中被赋予更高的优先级,排在新的 S 锁之前。可以通过设置 LOW_PRIORITY_UPDATES 来更改此行为,这可能会导致写入不足,因为写入将被取消优先级以支持读取。
在 MySQL 中,可以使用'FLUSH TABLES WITH READ LOCK'获得整个数据库的 X 锁。
InnoDB 在通过索引读取遇到行时锁定行。InnoDB 锁定索引记录,并在遍历索引记录时锁定记录。InnoDB 使用称为“间隙”锁的特殊锁来确保 REPEATABLE-READ 事务隔离级别。锁定在索引条目上,因此如果一个表没有很好地为 UPDATE 查询建立索引,那么许多行将被锁定。请注意,InnoDB 不会为正常的 SELECT 查询创建 S 锁。它使用行版本控制,而不是行级锁定来获得一致的快照。
在获取 X 锁时,数据库需要检测死锁。考虑以下:
>connection 1
start transaction;
update T set c = c + 1 order by id asc;
>connection 2
start transaction;
update T set c = c - 1 order by id desc;
在行锁定模型中,这两条语句不可能都成功完成。第一个将永远等待获取第二个持有的锁,反之亦然。数据库将选择其中一个连接进行回滚。InnoDB 将选择更改次数最少的连接。MyISAM 将为首先获得锁的连接锁定整个表,然后第二个将在第一个完成后运行。
您给出的简单示例将通过任何上下文(数据库、表或行)的 X 锁来解决。如果两个连接以完全相同的类型开始,都运行两个尝试更新同一行的更新,则两者都将尝试获取 X 锁。只有一个连接可以获取 X 锁。无法准确确定哪一个将获得锁。另一个连接必须等到锁被释放,才能获得 X 锁。请记住,如果该行被 DELETE 或 UPDATE 锁定,那么等待者在等待后可能最终没有获得锁,因为数据库中没有任何东西可以锁定。
在您的示例中,第一个 UPDATE 获取 X 锁,然后第二个 UPDATE 将等待 X 锁并最终执行但不匹配任何行。