【问题标题】:How can I improve MySQL Insert Performance using MySQL C++ Connector?如何使用 MySQL C++ 连接器提高 MySQL 插入性能?
【发布时间】:2022-08-19 22:55:43
【问题描述】:

我正在向 MySQL 数据库中插入大量记录,并且正在尝试实现不错的 INSERT 性能。我正在使用 MySQL 8.0 和 MySQL 连接器 C++ 8.0。

为了确定插入数据的最快方式,我构建了一个小型测试程序,它简单地将 10000 条记录插入到一​​个表中。如果有帮助,这是表结构:

CREATE TABLE IF NOT EXISTS Parent (
id BIGINT AUTO_INCREMENT NOT NULL PRIMARY KEY,
xxuint1 INTEGER UNSIGNED,
xxuint2 INTEGER UNSIGNED,
xxuint3 INTEGER UNSIGNED,
xxuint4 INTEGER UNSIGNED)

我创建了一个包含值的结构,并创建了一个包含 10,000 个随机数的数组 (tblParent[10000])。这个数组的填充是在插入之前完成的,所以我只能测量插入性能。下面的函数是我的基本插入函数:

void InsertData(sql::Connection* con)
{
    sql::PreparedStatement* pstmt = NULL;

    try {
        std::string sql = \"INSERT INTO Parent(\"
            \"xxuint1, xxuint2, xxuint3, xxuint4\"
            \") VALUES (?,?,?,?);\";

        pstmt = con->prepareStatement(sql);
        for (size_t i = 0; i < NUM_PARENTS; ++i) {
            pstmt->setUInt(1, tblParent[i].uint1);
            pstmt->setUInt(2, tblParent[i].uint2);
            pstmt->setUInt(3, tblParent[i].uint3);
            pstmt->setUInt(4, tblParent[i].uint4);
            pstmt->execute();
        }
    } catch(sql::SQLException &e) {
        std::cout << \"SQLException: \" << e.what() << std::endl;
    }

    delete pstmt;
}

通常,当插入许多记录时,您可以通过使用多个值列表来获得更好的性能:

INSERT INTO MyTable (col1, col2, col3) VALUES (?, ?, ?), (?, ?, ?), ... number_of_records

而不是一次插入一条记录。 对于每个记录数:

INSERT INTO MyTable (col1, col2, col3) VALUES (?, ?, ?)

我假设上面的代码将在幕后使用多值列表方法,但根据我的性能测量,我不相信它是。 这是我得到的:
具有 10,000 条记录的 InsertData 代码:
~300 条记录/秒。
用 \"START TRANSACTION\" 和 \"COMMIT\" 包围 InsertData:
~8000 条记录/秒

如果我重写插入数据,以便将数组中的数据作为字符串直接插入到 sql 中,例如

std::string sql = \"INSERT INTO Parent(\"
            \"xxuint1, xxuint2, xxint3, xxbigint4\"
            \") VALUES (\";
for (size_t i = 0; i < NUM_PARENTS; ++i) {
    sql += to_string(tblParent[i].uint1) + \", \";
    sql += to_string(tblParent[i].uint2) + \", \";
    sql += to_string(tblParent[i].uint3) + \", \";
    sql += to_string(tblParent[i].uint4) + \"); \";
}

我得到与上述类似的性能。

当我明确开始使用多个值列表时,性能得到了提高。我调整了我的 sql 以包含 \"VALUES (?, ?, ?), (?, ?, ?), ...\",这将性能提高到 ~14,000 条记录/秒。但最好的时间来自将我的数据转换为字符串,并使用多个值列表将该数据直接插入到 sql 中。我这样做的速度高达约 40,000 条记录/秒。

但是,虽然速度还不错,但我不认为将我的数据转换为文本并将其插入到 sql 中是一种理想的方法。如何优化插入速度并仍然使用 pstmt->setUint() 方法?

  • 为什么不为此简单地使用 load data infile 语句呢?
  • 因为我正在努力的真实场景是记录动态给我的数据。对于上述问题,我试图找到加载数据文件之外的最快插入方法,我意识到如果我有一个文件可以读取,这将是最快的。

标签: c++ mysql


【解决方案1】:

几年前我做了一个演示,比较了不同插入方法的开销:https://www.slideshare.net/billkarwin/load-data-fast

和你一样,我发现在 VALUES 子句中包含多个元组的一条语句中插入多行更好。您的第一个代码示例本身不会这样做,您必须编写带有多个元组的 INSERT 语句,就像您的第二个代码示例一样。

避免每行的完整事务(即自动提交)有很大帮助。这就是您在循环之前开始事务时所做的。在破坏二进制日志之前,每个事务可以插入的字节数有一个实际限制,因此如果您有大量插入,请尝试分批进行,例如不超过 10k 行。为了安全起见,我可能会选择每批 1000 行。这至少是事务开销的 1/1000。

如果您可以减少表上的索引和插入触发器的数量,那将有所帮助。插入一行的成本大约与它需要更新的索引数量成正比(由于更改缓冲区等原因会有一些变化)。触发器会增加开销,因为它可能会运行其他 DML,例如插入日志表,这意味着更多的索引写入。

一些 MySQL 服务器调优选项可以帮助减少一点开销,但代价是降低数据持久性。

通过切换到 LOAD DATA INFILE(专为批量数据加载而设计),所有这些优化都相形见绌。通过这种方式,您可以获得一个数量级的改进。但是由于索引写入,每行仍然存在开销,并且事务大小仍然存在实际限制。

【讨论】:

  • 与任何性能优化一样,您的结果可能会有所不同,具体取决于您的系统或应用程序。
  • 如果您必须先创建和写入文件,LOAD DATA 的好处就有些丧失了。当然,如果数据已经在 CSV 文件中,那就是大获全胜了。
  • 感谢您的答复。不幸的是,我不认为我可以使用 LOAD DATA INFILE 技术,因为我没有要读取的文件。我主要担心的是它似乎获得了最高的速度,我需要首先将我的数据转换为字符串,以便它们可以连接成一个 INSERT sql 字符串。如果需要,我会这样做,但我希望学习一种不涉及这种开销并且仍然非常快的技术。
  • @JasonK,我会使用参数。然后数据永远不会连接到 SQL 查询字符串中。
  • 这就是我为实现上述 14,000 次插入/秒值所做的工作,但我将它们转换为字符串并插入 sql 字符串所带来的 2.8 倍性能提升是难以忽视的。我试图找到两全其美的方法,我可以使用参数并仍然获得如此高的速度。
【解决方案2】:

为您的 my.cnf 或 my.ini [mysqld] 部分考虑的建议

innodb_change_buffer_max_size=50  # from 25 (percent) set aside in buffer pool
innodb_change_buffering=none  # from all - most likely -
innodb_write_io_threads=64  # for max capacity

提高每秒插入率。

参考以前的答案。 dba.stackexchange.com 问题 5666 请参阅 Rolando 的 9/12/2011 详细信息 dba.stackexchange.com 问题 196715 看下 Rolando 的零钱缓冲 以及与这些变量的许多方面相关的注意事项。

【讨论】:

    猜你喜欢
    • 2018-11-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-10-07
    • 2012-04-11
    • 2010-10-29
    • 1970-01-01
    相关资源
    最近更新 更多