如何确保Sequelize多个查询运行在同一MariaDB连接上?
问题核心结论
你当前的写法无法保证加锁、业务查询、解锁三个阶段的操作运行在同一个MariaDB连接上。
Sequelize默认基于连接池调度数据库请求,每次直接调用sequelize.query()时,都会独立从连接池申请空闲连接,SQL执行完成后立刻将连接归还到池。连续三次独立调用sequelize.query()大概率会分配到三个不同的连接,而MariaDB的GET_LOCK是连接级别的锁,这种写法会直接导致锁逻辑完全失效:加锁在连接A、业务查询跑在无锁的连接B、解锁操作跑在连接C根本拿不到A持有的锁,甚至加锁后的连接A被归还到池后,会被其他无关请求复用,导致锁被意外提前释放。
正确实现方式
必须手动绑定独占连接,保证所有持锁阶段的操作都复用同一个数据库连接,有两种成熟实现方案:
方案1:通过事务绑定连接(推荐)
Sequelize的事务天然和单个数据库连接绑定,所有传入transaction参数的查询都会强制复用该连接,不需要手动管理连接释放,异常场景下连接会自动归还池:
async function createIfNotExists(sequelize) { // 开启事务,从连接池独占一个连接 const transaction = await sequelize.transaction(); try { // 加锁,传入transaction参数绑定连接 const [lockResult] = await sequelize.query( "SELECT GET_LOCK('my_lock', 2) as lockResult;", { transaction } ); if (lockResult[0].lockResult === 0) { throw new Error('lock-was-blocked'); } // 所有业务查询必须传入transaction参数,才会复用同一个连接 // 示例:await User.findOrCreate({ where: { name: 'test' }, transaction }); // 解锁,同样绑定同一个事务连接 await sequelize.query("SELECT RELEASE_LOCK('my_lock');", { transaction }); // 提交事务,连接自动归还到连接池 await transaction.commit(); } catch (err) { // 异常回滚,连接自动归还,持有的锁会随连接回收自动释放 await transaction.rollback(); throw err; } }
方案2:手动申请独占连接
如果不想开启事务,可以直接从连接池手动拿连接,全程复用,注意必须在finally块手动释放连接,否则会造成连接泄漏:
async function createIfNotExists(sequelize) { // 从连接池申请一个写连接,独占使用 const connection = await sequelize.connectionManager.getConnection({ type: 'write' }); try { // 加锁,指定使用拿到的独占连接 const [lockResult] = await sequelize.query( "SELECT GET_LOCK('my_lock', 2) as lockResult;", { connection } ); if (lockResult[0].lockResult === 0) { throw new Error('lock-was-blocked'); } // 所有业务查询必须传入connection参数,保证同连接执行 // 示例:await Order.create({ userId: 1 }, { connection }); // 解锁 await sequelize.query("SELECT RELEASE_LOCK('my_lock');", { connection }); } finally { // 无论成功失败,都要把连接归还到池 await sequelize.connectionManager.releaseConnection(connection); } }
连接一致性验证方法
如果需要确认所有操作确实跑在同一个连接上,可以在加锁后、业务查询中、解锁前分别执行SELECT CONNECTION_ID() as connId,打印三次查询返回的connId值,如果三个值完全相同,就证明操作复用了同一个连接。
注意事项
- 持锁阶段的所有数据库操作,包括Model自带的
find、create、update等方法,都必须传入对应的transaction或connection参数,否则还是会走连接池随机分配连接,不受锁保护 - 不要将拿到的connection或transaction对象存为全局变量跨请求复用,连接是单次请求独占资源,跨请求复用会导致锁串扰、数据异常
- 即使忘记显式调用
RELEASE_LOCK,只要连接被正常归还到池或者断开,MariaDB都会自动释放该连接持有的命名锁,不会出现永久锁死的问题
内容的提问来源于stack exchange,提问作者Adam Baranyai
相关产品推荐
相关产品推荐

