在 MySQL 中,两个请求同时操作相同的表或记录时,如果使用了锁机制(如 FOR UPDATE
、事务、或是表锁),可能发生一个处理失败或超时的情况。
下面是一些常见情况分析及解决方案:
一、常见原因分析
1. 事务锁冲突(行锁或表锁)
-
如果两个请求都开启了事务,并操作相同的行或表,会发生等待锁的情况。
-
如果其中一个事务失败或等待超时(默认
innodb_lock_wait_timeout=50s
),就会报错。
示例:
请求1:
START TRANSACTION;
UPDATE orders SET status='done' WHERE id=123; -- 持有行锁
请求2(几乎同时):
START TRANSACTION;
UPDATE orders SET status='cancel' WHERE id=123; -- 阻塞直到超时或前者提交/回滚
如果请求1迟迟不提交或回滚,请求2最终会失败报错:
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
2. 死锁
-
两个事务互相等待对方释放锁,形成死锁,MySQL 会主动回滚其中一个。
示例:
请求1:
START TRANSACTION;
UPDATE table_a SET ... WHERE id=1;
-- 等待 table_b 的锁
请求2:
START TRANSACTION;
UPDATE table_b SET ... WHERE id=2;
-- 等待 table_a 的锁
会导致死锁,MySQL 报错:
ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction
3. 显式表锁
使用 LOCK TABLES
会锁整个表,其他请求不能访问:
LOCK TABLES orders WRITE;
另一个请求就会被阻塞,直到释放锁。
二、解决方案建议
1. 减少锁定时间
-
操作尽量短小、快速。
-
尽早
COMMIT
或ROLLBACK
。
2. 设置合理的等待超时时间
-
innodb_lock_wait_timeout
:默认 50 秒。可以根据业务特性调整。
SET innodb_lock_wait_timeout = 10;
3. 捕获并重试事务失败
对于 1213、1205 之类的错误,应用层建议进行捕获并自动重试。
4. 保持一致的访问顺序,避免死锁
始终按相同顺序访问多个表或行。
5. 使用更小粒度的锁
-
避免
LOCK TABLES
。 -
使用行级锁(InnoDB 默认支持)。
6. 开启死锁日志分析
SHOW ENGINE INNODB STATUS;