本章目标:理解 MySQL 锁的分类与作用,掌握常见加锁 SQL、死锁处理策略和并发控制方案,能够在高并发场景下保证数据正确性。
并发场景中,多个事务可能同时读写同一批数据。如果没有锁,就可能出现脏写、超卖、余额错误等问题。
锁的核心作用:
flowchart LR
A[并发请求] --> B{是否冲突同一数据}
B -- 否 --> C[并行执行]
B -- 是 --> D[加锁等待/串行化]
C --> E[结果正确]
D --> E
START TRANSACTION;
SELECT id, stock
FROM products
WHERE id = 1001
FOR UPDATE;
UPDATE products
SET stock = stock - 1
WHERE id = 1001 AND stock > 0;
COMMIT;FOR UPDATE 会对命中行加排他锁,适合“先查后改”。
START TRANSACTION;
SELECT id, stock
FROM products
WHERE id = 1001
LOCK IN SHARE MODE;
COMMIT;MySQL 8.0 也支持
FOR SHARE,语义更直观。
先加锁再操作,假设冲突概率高。
适用场景:
先读后改,提交时校验版本号(或时间戳),冲突则重试。
示例(版本号):
UPDATE products
SET stock = stock - 1,
version = version + 1
WHERE id = 1001
AND version = 12
AND stock > 0;死锁是指两个或多个事务互相持有对方需要的锁,形成循环等待。
sequenceDiagram
participant T1 as 事务T1
participant T2 as 事务T2
T1->>DB: 锁住行A
T2->>DB: 锁住行B
T1->>DB: 请求行B(等待)
T2->>DB: 请求行A(等待)
Note over T1,T2: 形成死锁,InnoDB会回滚一个事务
行锁是“基于索引项加锁”的:
示例:
-- 若 user_id 无索引,这条更新可能扫描并锁大量行
UPDATE orders
SET status = 2
WHERE user_id = 10086;结论:高并发更新条件列,一定要有合适索引。
CREATE TABLE products (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(100) NOT NULL,
stock INT NOT NULL,
version INT NOT NULL DEFAULT 0
);START TRANSACTION;
SELECT stock
FROM products
WHERE id = 1
FOR UPDATE;
UPDATE products
SET stock = stock - 1
WHERE id = 1 AND stock > 0;
COMMIT;UPDATE products
SET stock = stock - 1,
version = version + 1
WHERE id = 1
AND stock > 0
AND version = ?;SHOW PROCESSLIST 看是否有长事务、锁等待SHOW ENGINE INNODB STATUS 查看死锁信息FOR UPDATE 一定会锁行吗 不一定。要看是否命中索引与执行计划;否则锁范围可能扩大。
死锁是并发系统中的正常现象,关键是要有可观测性与自动重试机制。
FOR UPDATE 写一个“转账扣款”事务示例。version 的乐观锁更新 SQL。