[英]How can I improve MySQL Insert Performance using MySQL C++ Connector?
我正在向 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])。 这个数组的填充是在插入之前完成的,所以我只能测量插入性能。 下面的 function 是我的基本插入 function:
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() 方法?
几年前我做了一个演示,比较了不同插入方法的开销: https://www.slideshare.net/billkarwin/load-data-fast
和你一样,我发现在 VALUES 子句中包含多个元组的一条语句中插入多行更好。 您的第一个代码示例本身不会这样做,您必须编写带有多个元组的 INSERT 语句,就像您的第二个代码示例一样。
避免每行的完整事务(即自动提交)有很大帮助。 这就是您在循环之前开始事务时所做的。 在破坏二进制日志之前,每个事务可以插入的字节数有一个实际限制,因此如果您有大量插入,请尝试分批进行,例如不超过 10k 行。 为了安全起见,我可能会选择每批 1000 行。 这至少是交易开销的 1/1000。
如果您可以减少表上的索引和插入触发器的数量,那将有所帮助。 插入一行的成本大约与它需要更新的索引数量成正比(由于更改缓冲区等原因会有一些变化)。 触发器会增加开销,因为它可能会运行其他 DML,例如插入日志表,这意味着更多的索引写入。
一些 MySQL 服务器调整选项可以帮助减少一点开销,但以降低数据持久性为代价。
通过切换到 LOAD DATA INFILE(专为批量数据加载而设计),所有这些优化都相形见绌。 通过这种方式,您可以获得一个数量级的改进。 但是由于索引写入,每行仍然存在开销,并且事务大小仍然存在实际限制。
为您的 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 的 2011 年 9 月 12 日详细信息 dba.stackexchange.com 问题 196715 查找 Rolando 的变更缓冲和与这些变量的许多方面相关的注意事项
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.