事务需显式开启(BEGIN/START TRANSACTION),否则autocommit=1致语句立即提交;READ COMMITTED下SELECT...FOR UPDATE仅锁命中行,不防幻读;死锁源于操作顺序不一致,应统一排序更新;长事务会延长锁持有时间,应拆分非DB操作。

事务必须显式开启,否则 autocommit=1 会自动提交
MySQL 默认是 autocommit=1,每条 INSERT/UPDATE/DELETE 语句都会立即生效并释放锁。想用事务控制一致性,第一步就是关掉它:
SET autocommit = 0;或者更推荐的方式是用
BEGIN 或 START TRANSACTION 显式开启——这两者等价,且能确保后续语句在同一个事务上下文中执行。忘记这一步,后面加再多 SELECT ... FOR UPDATE 都没用,因为语句一执行就提交了。
READ COMMITTED 下的 SELECT ... FOR UPDATE 只锁命中行,但不阻止幻读
在默认隔离级别 READ COMMITTED 中,SELECT ... FOR UPDATE 会对查询结果集中的**已存在行**加行级写锁(Record Lock),其他事务无法修改或删除这些行。但它不会锁住“不存在的间隙”,所以另一个事务仍可插入满足相同条件的新行,造成幻读。例如:
SELECT * FROM orders WHERE status = 'pending' FOR UPDATE;此时若另一事务执行
INSERT INTO orders (status) VALUES ('pending'),能成功插入——这不是 bug,是 READ COMMITTED 的行为边界。要避免幻读,需升级到 REPEATABLE READ 并依赖间隙锁(Gap Lock)。
死锁不是配置问题,而是事务内操作顺序不一致导致的
两个事务按不同顺序访问相同资源时极易触发死锁,比如:
- 事务 A 先更新
user_id = 100,再更新user_id = 200 - 事务 B 先更新
user_id = 200,再更新user_id = 100
Deadlock found when trying to get lock)。解决方法不是调大 innodb_lock_wait_timeout,而是统一所有业务逻辑中对多行记录的操作顺序,例如始终按 user_id 升序更新:UPDATE accounts SET balance = balance - 100 WHERE user_id IN (100, 200) ORDER BY user_id;这样能从源头消除竞争路径。
长事务会拖垮并发性能,锁持有时间远超业务需要
一个事务从 BEGIN 到 COMMIT 之间,所有加过的锁都不会释放。如果中间夹杂了 HTTP 调用、文件读写、用户输入等待等外部耗时操作,锁就会一直占着,阻塞其他事务。典型反例:
BEGIN; UPDATE inventory SET stock = stock - 1 WHERE sku = 'A123'; -- 这里调用第三方支付接口,耗时 2s INSERT INTO orders (...) VALUES (...); COMMIT;正确做法是把数据库操作尽量聚合成最小原子单元,把非 DB 操作移出事务块;必要时用乐观锁(如版本号字段
version)替代长事务悲观锁。










