mysql中使用事务与锁实现数据一致性与隔离性

来源:这里教程网 时间:2026-02-28 20:45:02 作者:

事务必须显式开启,否则 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
MySQL 检测到循环等待后会回滚其中一个事务(报错
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
)替代长事务悲观锁。

相关推荐