事务需显式开启,否则begin/commit无效;mysql默认autocommit=1,须set autocommit=0或用start transaction包裹;select ... for update锁行取决于索引,无索引可能锁表,范围查询可能锁间隙。

事务必须显式开启,否则 BEGIN 和 COMMIT 不起作用
MySQL 默认是自动提交模式(autocommit=1),每条 SQL 执行完立刻生效,BEGIN 后跟的语句其实已经单独提交了。这时候你 ROLLBACK 也没用——因为根本没在事务里。
实操建议:
- 先查当前模式:
SELECT @@autocommit;,返回1就得关 - 临时关闭:
SET autocommit = 0;,之后所有语句都在同一事务中,直到COMMIT或ROLLBACK - 更稳妥的做法是始终用
START TRANSACTION(等价于BEGIN)显式包裹,而不是依赖autocommit设置 - 注意:某些 ORM(如 Django 的
atomic)会自动管理,但裸写 SQL 时这点极易漏掉
SELECT ... FOR UPDATE 锁的是哪些行?不是“查到的行”那么简单
这个语句加的是当前读 + 行锁,但具体锁住什么,取决于执行计划和索引是否命中。没走索引?可能升级成表锁;范围查询?可能锁住间隙(gap lock),导致其他事务插入被阻塞。
常见错误现象:
- 事务 A 执行
SELECT * FROM orders WHERE status = 'pending' FOR UPDATE,status 字段无索引 → 锁全表 → 事务 B 插入新订单被卡住 - 事务 A 查询
WHERE id > 100 AND id ,id 是主键 → 锁住 (100,200) 这个间隙,B 想插 id=150 就失败
实操建议:
- 加
FOR UPDATE前,务必EXPLAIN看是否走了索引 - 只锁定真正需要修改的行,避免
SELECT * ... FOR UPDATE,改用带明确主键或唯一索引条件的语句 - 高并发场景下,间隙锁可能引发死锁,要结合业务接受“重试”逻辑
隔离级别不是越高越好:REPEATABLE READ 下幻读仍可能真实发生
MySQL 默认是 REPEATABLE READ,它用 MVCC 解决了快照读下的幻读,但只要你用了 SELECT ... FOR UPDATE 或 UPDATE ... WHERE 这类当前读,幻读就回来了——因为当前读看到的是最新版本,且加锁逻辑会暴露新插入的行。
千博购物系统.Net能够适合不同类型商品,为您提供了一个完整的在线开店解决方案。千博购物系统.Net除了拥有一般网上商店系统所具有的所有功能,还拥有着其它网店系统没有的许多超强功能。千博购物系统.Net适合中小企业和个人快速构建个性化的网上商店。强劲、安全、稳定、易用、免费是它的主要特性。系统由C#及Access/MS SQL开发,是B/S(浏览器/服务器)结构Asp.Net程序。多种独创的技术使
使用场景差异:
- 纯读操作(无
FOR UPDATE)+REPEATABLE READ→ 幻读被 MVCC 屏蔽 - 读-改-写流程(比如先查余额再扣款)+
REPEATABLE READ→ 若中间有别人插入符合查询条件的新记录,当前读会看到它,导致逻辑错乱 -
SERIALIZABLE能彻底禁止幻读,但代价是所有SELECT都隐式加共享锁,吞吐暴跌,一般不用
实操建议:
- 别迷信默认隔离级别,关键业务逻辑要自己验证当前读行为
- 想规避幻读又不想降级到
SERIALIZABLE?把查询条件尽量收紧到唯一索引上,减少间隙锁影响范围 - 必要时用
SELECT ... FOR UPDATE加锁前,先用SELECT ... LOCK IN SHARE MODE探路,降低冲突概率
事务超时不是数据库管的,wait_timeout 和 innodb_lock_wait_timeout 完全不同
这两个配置常被混为一谈,但作用对象完全不同:wait_timeout 控制空闲连接断开时间,而 innodb_lock_wait_timeout 才决定一个事务等待锁多久后报错 Lock wait timeout exceeded。
性能与兼容性影响:
- 默认
innodb_lock_wait_timeout=50秒,线上服务等 50 秒才失败太长,容易拖垮调用方 - 设太小(比如 1 秒)又会导致正常争抢被误判,尤其在批量更新或慢查询期间
- 这个值不能全局乱调,建议按业务接口粒度控制:长事务走专用连接池并调大,短平快接口保持默认或略降
实操建议:
- 查当前值:
SELECT @@innodb_lock_wait_timeout; - 会话级调整:
SET innodb_lock_wait_timeout = 10;(仅对当前连接有效) - 应用层必须捕获
Deadlock found when trying to get lock和Lock wait timeout exceeded两种错误,并实现退避重试
事情说清了就结束。ACID 不是开关,是每个环节都要对齐的契约:隔离级别、锁策略、超时设置、连接生命周期——少对一环,事务就只是看起来可靠。









