From b2975b520f6efdb693277bdc10c730cb9db48e0a Mon Sep 17 00:00:00 2001 From: CyC2018 Date: Wed, 28 Oct 2026 13:35:24 +0800 Subject: [PATCH] =?UTF-8?q?Update=20=E6=95=B0=E6=8D=AE=E5=BA=93=E7=B3=BB?= =?UTF-8?q?=E7=BB=9F=E5=8E=9F=E7=90=86.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 补充提交读下的加锁范围和没有索引时当前读的加锁,增加实测附录。 --- notes/数据库系统原理.md | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/notes/数据库系统原理.md b/notes/数据库系统原理.md index cd897c78..153a79c1 100644 --- a/notes/数据库系统原理.md +++ b/notes/数据库系统原理.md @@ -45,6 +45,7 @@ * [A3 事务 ID 的分配](#a3-事务-id-的分配) * [A4 快照读与当前读混用](#a4-快照读与当前读混用) * [A5 Next-Key Locks 的加锁范围](#a5-next-key-locks-的加锁范围) + * [A6 没有索引时的加锁](#a6-没有索引时的加锁) * [参考资料](#参考资料) @@ -259,6 +260,8 @@ SELECT ... FOR UPDATE; 一个事务只能读取已经提交的事务所做的修改。换句话说,一个事务所做的修改在提交之前对其它事务是不可见的。 +InnoDB 在提交读下不加间隙锁(只在检查外键和唯一键时使用),UPDATE、DELETE 扫描过但不满足条件的行,判断完条件就释放锁。插入不再被间隙锁阻塞,死锁也少得多,所以不少公司把隔离级别改为提交读,代价是会出现不可重复读和幻读,并且 binlog 必须使用 ROW 格式(见 [binlog](MySQL.md#binlog))。 + ### 可重复读(REPEATABLE READ) 保证在同一个事务中多次读取同一数据的结果是一样的。 @@ -415,6 +418,8 @@ SELECT c FROM t WHERE c BETWEEN 10 and 20 FOR UPDATE; 按 c = 11 查询时,c 不是唯一索引,后面还可能有 c = 11 的记录,所以除了 (10, 11] 的 Next-Key Lock,还要锁住到下一条记录 13 之间的间隙;如果 c 是唯一索引,就只锁 c = 11 这一条记录。 +当前读的条件列没有索引时,InnoDB 只能扫描整个聚簇索引,扫描过的每一行都要加锁。例如表 t_nl 有 5 行,d 列没有索引,执行 UPDATE t_nl SET d = d + 1 WHERE d = 30 只修改 1 行:在可重复读下锁住了全部 5 行和最后的间隙,其它事务插入新行、修改 id 为 5 的行都要等待,效果相当于锁住了整张表;在提交读下只锁住 1 行,插入和修改都不受影响(MySQL 8.0 实测,见[附录 A6](#a6-没有索引时的加锁))。所以 UPDATE、DELETE 的条件列要建索引。 + ## 七、关系数据库设计理论 ### 函数依赖 @@ -670,6 +675,29 @@ SELECT * FROM t WHERE c BETWEEN 10 AND 20 FOR UPDATE 把 idx_c 改为唯一索引后,c = 11 FOR UPDATE 只有 uk_c X,REC_NOT_GAP 11, 2 和主键上的记录锁,插入 c = 12 成功。 ``` +### A6 没有索引时的加锁 + +```sql +CREATE TABLE t_nl (id INT PRIMARY KEY, d INT); +INSERT INTO t_nl VALUES (1, 10), (2, 20), (3, 30), (4, 40), (5, 50); +``` + +A、B 两个会话的锁等待超时设为 1 秒。A 执行 UPDATE t_nl SET d = d + 1 WHERE d = 30 后不提交,查看 data_locks,B 再插入一行、修改另一行: + +```text +可重复读:A 的锁 7 个 + TABLE IX + RECORD X 1 / 2 / 3 / 4 / 5 / supremum pseudo-record + B: INSERT INTO t_nl VALUES (100, 1000) -> 1205 Lock wait timeout exceeded + B: UPDATE t_nl SET d = 51 WHERE id = 5 -> 1205 Lock wait timeout exceeded + +提交读:A 的锁 2 个 + TABLE IX + RECORD X,REC_NOT_GAP 3 + B: INSERT INTO t_nl VALUES (100, 1000) -> OK + B: UPDATE t_nl SET d = 51 WHERE id = 5 -> OK +``` + ## 参考资料 - AbrahamSilberschatz, HenryF.Korth, S.Sudarshan, 等. 数据库系统概念 [M]. 机械工业出版社, 2006. @@ -678,6 +706,7 @@ SELECT * FROM t WHERE c BETWEEN 10 AND 20 FOR UPDATE - [The InnoDB Storage Engine](https://dev.mysql.com/doc/refman/5.7/en/innodb-storage-engine.html) - [MySQL 8.0: Transaction Isolation Levels](https://dev.mysql.com/doc/refman/8.0/en/innodb-transaction-isolation-levels.html) - [MySQL 8.0: InnoDB Locking](https://dev.mysql.com/doc/refman/8.0/en/innodb-locking.html) +- [MySQL 8.0: Locks Set by Different SQL Statements in InnoDB](https://dev.mysql.com/doc/refman/8.0/en/innodb-locks-set.html) - [MySQL 8.0: The data_locks Table](https://dev.mysql.com/doc/refman/8.0/en/performance-schema-data-locks-table.html) - [Transaction isolation levels](https://www.slideshare.net/ErnestoHernandezRodriguez/transaction-isolation-levels) - [Concurrency Control](https://web.archive.org/web/20260614192237/https://scanftree.com/dbms/2-phase-locking-protocol)