You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.21 06:53:38