SELECT FOR UPDATE 是 MySQL 中用于行级锁的语句,主要作用是锁定查询到的行,防止其他事务修改这些行,确保数据的一致性。
SELECT * FROM table_name WHERE condition FOR UPDATE;
在事务中先锁定数据,再进行更新操作:
START TRANSACTION;
-- 锁定符合条件的行
SELECT * FROM accounts WHERE user_id = 123 FOR UPDATE;
-- 执行更新操作(其他事务无法修改被锁定的行)
UPDATE accounts SET balance = balance - 100 WHERE user_id = 123;
COMMIT;
START TRANSACTION;
-- 锁定库存记录
SELECT 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 * FROM users WHERE id = 1 FOR UPDATE;
-- 其他操作
COMMIT;
-- 错误用法(非事务中无效)
SELECT * FROM users WHERE id = 1 FOR UPDATE;
默认会一直等待锁释放,可以设置超时时间:
-- 设置锁等待超时为5秒
SET innodb_lock_wait_timeout = 5;
START TRANSACTION;
SELECT * FROM orders FOR UPDATE;
START TRANSACTION;
-- 锁定两个账户
SELECT * FROM accounts WHERE id IN (1, 2) FOR UPDATE;
-- 执行转账
UPDATE accounts SET balance = balance - 500 WHERE id = 1;
UPDATE accounts SET balance = balance + 500 WHERE id = 2;
COMMIT;
START TRANSACTION;
-- 锁定用户和商品
SELECT * FROM users WHERE id = 100 FOR UPDATE;
SELECT * FROM products WHERE id = 5 FOR UPDATE;
-- 创建订单
INSERT INTO orders (user_id, product_id, quantity)
VALUES (100, 5, 1);
COMMIT;
| 锁类型 | 语法 | 作用 |
|---|---|---|
| 排他锁 | FOR UPDATE |
阻止其他事务读写 |
| 共享锁 | LOCK IN SHARE MODE |
允许其他事务读,阻止写 |
| 乐观锁 | 版本号/时间戳 | 通过版本控制实现 |
-- 按相同顺序锁定表,避免死锁
-- 事务1和事务2都按 id 升序锁定
SELECT * FROM accounts WHERE id IN (1, 2, 3) ORDER BY id FOR UPDATE;
当锁定的行过多时,InnoDB 可能将行锁升级为表锁。
不同隔离级别下 FOR UPDATE 的行为:
| 隔离级别 | FOR UPDATE 行为 |
|---|---|
| READ COMMITTED | 锁定实际存在的行 |
| REPEATABLE READ | 锁定实际存在的行和间隙 |
-- 不好:可能锁定多行
SELECT * FROM orders WHERE status = 'pending' FOR UPDATE;
-- 更好:精确锁定 SELECT * FROM orders WHERE id = 1001 FOR UPDATE;
2. **尽快释放锁**
```sql
START TRANSACTION;
-- 业务操作尽量放在锁外
SET @amount = 500;
-- 锁定后立即完成更新
SELECT balance FROM accounts WHERE id = 1 FOR UPDATE;
UPDATE accounts SET balance = balance - @amount WHERE id = 1;
COMMIT; -- 立即提交释放锁
使用超时机制
SET innodb_lock_wait_timeout = 3; -- 3秒超时
考虑替代方案
-- 错误1:非事务中使用
SELECT * FROM users FOR UPDATE; -- 自动提交,锁立即释放
-- 错误2:锁定过多行导致性能问题
SELECT * FROM large_table WHERE create_time > '2023-01-01' FOR UPDATE;
-- 错误3:没有WHERE条件(锁定全表)
SELECT * FROM users FOR UPDATE; -- 非常危险!
SELECT FOR UPDATE 是处理并发数据更新的重要工具,但需要谨慎使用,避免过度锁定影响系统性能。