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

6次阅读

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

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

事务必须显式开启,否则 autocommit=1 会自动提交

mysql 默认是 autocommit=1,每条 INSERT/UPDATE/delete 语句都会立即生效并释放锁。想用事务控制一致性,第一步就是关掉它:

SET autocommit = 0;

或者更推荐的方式是用 BEGINSTART 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;

这样能从源头消除竞争路径。

长事务会拖垮并发性能,锁持有时间远超业务需要

一个事务从 BEGINCOMMIT 之间,所有加过的锁都不会释放。如果中间夹杂了 http 调用、文件读写、用户输入等待等外部耗时操作,锁就会一直占着,阻塞其他事务。典型反例:

BEGIN; UPDATE inventory SET stock = stock - 1 WHERE sku = 'A123'; -- 这里调用第三方支付接口,耗时 2s INSERT INTO orders (...) VALUES (...); COMMIT;

正确做法是把数据库操作尽量聚合成最小原子单元,把非 DB 操作移出事务块;必要时用乐观锁(如版本号字段 version)替代长事务悲观锁。

text=ZqhQzanResources