Qt多线程批量插入MySQL表触发死锁问题求助
解决Qt多线程写入MySQL触发死锁的问题
嘿,我来帮你拆解这个死锁问题——结合你描述的场景:Qt多线程程序批量写入A_tbl,触发器联动B_tbl并查询新增ID,这大概率是锁顺序不一致或者锁范围过大导致的,咱们一步步来解决:
先搞懂死锁的核心原因
你的场景里死锁通常来自这几个点:
- 触发器里「先写B_tbl再查B_tbl」的操作,和其他线程的触发器操作形成了锁顺序冲突:比如线程1先锁A_tbl的某行,再锁B_tbl的某行;线程2可能先锁B_tbl的另一行,再锁A_tbl的某行,循环等待就触发死锁。
- 如果触发器里查询B_tbl的语句没用到合适的索引,MySQL会做全表扫描,给B_tbl加范围锁甚至表锁,大幅提升并发冲突概率。
- 每个线程一次写500条记录,属于大事务,持锁时间太长,死锁的概率自然更高。
具体解决方案
1. 用629638替代SELECT查询(最推荐)
触发器插入B_tbl之后,完全不需要用SELECT去查新增ID——629638是会话级别的函数,能直接拿到当前会话最后插入的自增ID,既高效又不会加额外的锁。
修改触发器代码示例:
-- 原触发器里的插入+查询 INSERT INTO B_tbl (col1, col2) VALUES (val1, val2); -- 替换掉下面的SELECT语句 -- SELECT id INTO @new_b_id FROM B_tbl WHERE ...; SET @new_b_id = 629638; -- 后续直接用@new_b_id即可
2. 给B_tbl的查询条件加唯一索引(必须用SELECT的场景)
如果因为业务原因必须用SELECT查B_tbl的ID,一定要给查询的WHERE条件加唯一索引,这样MySQL只会锁对应的单行,而不是范围锁或表锁。
示例:
-- 假设你是根据唯一标识查询B_tbl的ID,先加索引 CREATE UNIQUE INDEX idx_b_unique_key ON B_tbl(unique_key_col); -- 触发器内的查询语句改成这样(确保走索引) SELECT id INTO @new_b_id FROM B_tbl WHERE unique_key_col = 'xxx' LIMIT 1;
3. 拆分批量写入为小事务
一次写500条记录的大事务持锁时间太长,容易和其他线程的锁等待形成死锁。可以把500条拆成多个小批次,比如每次写50条,完成一个小事务再继续下一个。
Qt代码调整示例:
// 原代码:一次性执行500条插入 // 改成循环拆分小事务 for (int batchIdx = 0; batchIdx < 10; ++batchIdx) { QSqlTransaction tx(db); if (!tx.begin()) { qDebug() << "Failed to start transaction"; continue; } bool batchSuccess = true; for (int i = 0; i < 50; ++i) { QSqlQuery query(db); if (!query.exec("INSERT INTO A_tbl (col1, col2) VALUES ('val1', 'val2')")) { batchSuccess = false; qDebug() << "Insert failed:" << query.lastError().text(); break; } } if (batchSuccess) { tx.commit(); qDebug() << "Batch" << batchIdx + 1 << "committed"; } else { tx.rollback(); qDebug() << "Batch" << batchIdx + 1 << "rolled back"; // 可根据需求添加重试逻辑 } }
4. 查看死锁日志精准定位
如果上面的方法还没解决,可以通过MySQL的死锁日志找到具体的冲突点:
-- 临时开启死锁日志(重启MySQL后失效) SET GLOBAL innodb_print_all_deadlocks = ON; -- 查看死锁详情 SHOW ENGINE INNODB STATUS;
在输出的LATEST DETECTED DEADLOCK部分,能看到两个冲突事务的锁顺序和执行语句,根据日志可以更精准地调整触发器或业务代码。
5. 添加死锁重试逻辑
如果业务允许偶尔重试,可以在Qt代码里捕获Deadlock found的错误,自动重试当前批次的写入:
bool executeBatchInsert(QSqlDatabase &db, int batchSize) { const int maxRetries = 3; int retryCount = 0; while (retryCount < maxRetries) { QSqlTransaction tx(db); if (!tx.begin()) { retryCount++; QThread::msleep(100); continue; } bool success = true; for (int i = 0; i < batchSize; ++i) { QSqlQuery query(db); if (!query.exec("INSERT INTO A_tbl (col1, col2) VALUES ('val1', 'val2')")) { QString errMsg = query.lastError().text(); if (errMsg.contains("Deadlock found")) { success = false; tx.rollback(); retryCount++; QThread::msleep(100); break; } else { // 非死锁错误,直接返回失败 tx.rollback(); return false; } } } if (success) { tx.commit(); return true; } } return false; }
内容的提问来源于stack exchange,提问作者morteza ali ahmadi
相关产品推荐
相关产品推荐

