两个人抢同一件货,听谁的?
上期讲了隔离级别,结尾说「撞车了只能整单重来」。这期讲撞车之前,数据库怎么让大家排队:锁。
1. 为什么需要锁:丢失更新
两个事务读了同一行,再按各自读到的旧值去更新它。谁也不知道对方的存在,后写的覆盖了先写的,前一个人的改动就丢了。1
2. 读锁和写锁
InnoDB 的行级锁有两种:共享锁(S,读锁)允许持有者读这一行;排他锁(X,写锁)允许持有者修改或删除这一行。2
注意:在默认的可重复读或读已提交级别下,InnoDB 的普通 SELECT 读快照、不加锁(见 EP.16),所以写锁并不会挡住普通查询;在 SERIALIZABLE 级别并关掉自动提交时,普通 SELECT 会自动加共享锁。要加锁读,得显式写:
3. 行锁和表锁
- InnoDB 用行级锁,多个会话可以同时写同一张表的不同行;不同会话访问不同的行时,锁冲突更少。4
- MyISAM、MEMORY、MERGE 表用表级锁,同一时间只允许一个会话更新整张表。4
- InnoDB 还有表级的意向锁,用来标明一个事务稍后要在表里的某行上加哪种锁。2
4. 等锁和超时
拿不到锁的事务会一直等,直到对方释放。2 但等 InnoDB 行锁不会无限等下去:
innodb_lock_wait_timeout默认 50 秒(这个参数不管表锁),超时会报ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction。5- 超时默认只回滚当前这一条语句,不是整个事务。 想让整个事务回滚,需要用
--innodb-rollback-on-timeout启动服务器。5
5. 死锁
多个事务各自持有对方需要的锁,谁都在等,谁也不释放,这就是死锁。6
- 死锁检测默认是开着的(
innodb_deadlock_detect默认 ON)。InnoDB 发现死锁后,会回滚其中一个事务。65 - 它会尽量挑「小」的事务回滚,大小按插入、更新、删除的行数算。7
- 关掉死锁检测后,就只能靠锁等待超时来回滚了。6
- 即使应用逻辑没错,也要处理事务被回滚、需要重试的情况。6
- PostgreSQL 也会自动发现死锁并中止其中一个事务,但具体中止哪一个很难预测。8 它等多久才开始检查死锁由
deadlock_timeout决定,默认 1 秒。9 - 最好的办法是避免死锁:所有程序都按同样的顺序给多个对象加锁。8
延伸:悲观锁和乐观锁
- 悲观锁:牺牲一些性能或并发,换取安全。10 InnoDB 用的就是悲观锁策略。10 做法是先在数据源上锁住行,不让别人改。11
- 乐观锁:不先上锁,而是在更新时检查值有没有在你读之后被别人改过。11 一种做法是用时间戳列,每次更新都刷新时间戳。11
下面是一种常见的通用写法(用版本号列,思路和时间戳列相同)。这是业界常用做法的示例,不是官方定义;乐观锁这一节的出处目前只有一份 2017 年的资料,仅作参考:
UPDATE tshirt SET price = ?, version = version + 1
WHERE id = ? AND version = ?;
-- 影响 0 行,说明别人先改了,重新读一遍再试 Footnotes
-
Microsoft Learn:Transaction locking and row versioning guide(2026-04-22)https://learn.microsoft.com/en-us/sql/relational-databases/sql-server-transaction-locking-and-row-versioning-guide ↩
-
MySQL 8.4 手册 17.7.1 InnoDB Locking(2026-10-15 抓取)https://dev.mysql.com/doc/refman/8.4/en/innodb-locking.html ↩ ↩2 ↩3 ↩4 ↩5
-
MySQL 8.4 手册 Locking Reads(2026-10-15 抓取)https://dev.mysql.com/doc/refman/8.4/en/innodb-locking-reads.html ↩ ↩2
-
MySQL 8.4 手册 Internal Locking Methods(2026-10-15 抓取)https://dev.mysql.com/doc/refman/8.4/en/internal-locking.html ↩ ↩2
-
MySQL 8.4 手册 InnoDB Startup Options and System Variables(2026-10-15 抓取)https://dev.mysql.com/doc/refman/8.4/en/innodb-parameters.html ↩ ↩2 ↩3
-
MySQL 8.4 手册 17.7.5 Deadlocks in InnoDB(2026-10-15 抓取)https://dev.mysql.com/doc/refman/8.4/en/innodb-deadlocks.html ↩ ↩2 ↩3 ↩4
-
MySQL 8.4 手册 17.7.5.2 Deadlock Detection(2026-10-15 抓取)https://dev.mysql.com/doc/refman/8.4/en/innodb-deadlock-detection.html ↩
-
PostgreSQL 18 文档 13.3 Explicit Locking https://www.postgresql.org/docs/current/explicit-locking.html ↩ ↩2
-
PostgreSQL 18 文档 Lock Management https://www.postgresql.org/docs/current/runtime-config-locks.html ↩
-
MySQL 8.4 手册 Glossary(2026-10-15 抓取)https://dev.mysql.com/doc/refman/8.4/en/glossary.html ↩ ↩2
-
Microsoft ADO.NET:Optimistic Concurrency(2017-03-30,仅参考)https://learn.microsoft.com/en-us/dotnet/framework/data/adonet/optimistic-concurrency ↩ ↩2 ↩3
幕后
这部片子完全由代码生成。
系列 · 数据库第 4/6 期