SQLAlchemy with_for_update跨会话未锁定同一行问题排查
SQLAlchemy
with_for_update 行锁未生效排查方案 以下按问题出现概率从高到低排列排查项:
1. of参数与数据库类型不兼容,锁子句未实际生成
with_for_update(of=Model) 是PostgreSQL、Oracle专属语法,用于多表关联查询时指定仅锁定某张表的对应行。如果你通过brew安装的是MySQL:
- 全系列MySQL均不支持
FOR UPDATE OF table语法 - SQLAlchemy对接MySQL方言时,会直接忽略不兼容的
of参数,最终生成的SQL是普通SELECT语句,不带FOR UPDATE子句,自然不会加任何行锁 - 验证方式:开启SQLAlchemy的
echo=True参数打印执行SQL,确认语句末尾是否存在FOR UPDATE关键字 - 修复方式:单表查询场景直接移除
of参数,写法改为.with_for_update()即可
2. 会话连接/事务状态异常
如果确认SQL生成正确,优先排查两个会话的事务状态:
- 确认两个session拿到的是独立数据库连接:MySQL可以执行
print(session1.connection().connection.thread_id())打印连接线程ID,两个session的ID不能相同 - 确认session1的事务未被意外提交/回滚:在session1执行完加锁查询后、启动session2查询前,执行一条简单探活SQL比如
session1.execute(text("SELECT 1")),同时在数据库侧执行锁查询命令确认锁被持有:-- MySQL 查看当前持有的行锁 SELECT * FROM performance_schema.data_locks; - 注意SQLAlchemy默认连接池有回收机制,如果会话长时间无操作可能导致连接被回收、事务隐式回滚,锁会提前释放。
3. 表结构不满足行锁生效条件
- 确认查询条件命中索引:如果
ProductSet.id不是主键或唯一索引,InnoDB不会精准锁定单行,会升级为Next-Key Lock锁定范围,但这种情况依然会阻塞其他事务的加锁请求,不会出现完全无锁的情况。可以执行SHOW INDEX FROM product_set;确认id字段的索引属性。 - 确认表引擎为InnoDB:MySQL 5.5之后默认引擎为InnoDB,如果你手动修改过引擎为MyISAM,MyISAM不支持事务和行级锁,
FOR UPDATE完全不生效。可以执行SHOW TABLE STATUS WHERE Name='product_set';查看Engine字段值确认。
测试注意事项
锁生效后,单线程执行测试代码会卡在session2的加锁查询步骤,直到锁等待超时抛错(MySQL默认锁等待超时为50秒),这是正常现象。要模拟真实并发请求的串行执行效果,需要用多线程/多进程同时启动两个事务,不能在单线程里顺序执行两个会话的逻辑。
内容的提问来源于stack exchange,提问作者yose93
相关产品推荐
相关产品推荐

