MySQL:高效地在存储过程中填充表

4
我正在测试一个MySQL服务器的性能,并向一个表中填充超过2亿条记录。存储过程生成大SQL字符串非常缓慢。欢迎任何帮助或评论。
系统信息:
  • 数据库:MySQL 5.6.10 InnoDB数据库(测试)。
  • 处理器:AMD Phenom II 1090T X6核心,每个核心3910Mhz。
  • 内存:16GB DDR3 1600Mhz CL8。
  • 硬盘:Windows 7 64位SP1在SSD上安装了MySQL,在机械硬盘上写入日志。
存储过程创建一个INSERT SQL查询,其中包含要插入到表中的所有值。
DELIMITER $$
USE `test`$$

DROP PROCEDURE IF EXISTS `inputRowsNoRandom`$$

CREATE DEFINER=`root`@`localhost` PROCEDURE `inputRowsNoRandom`(IN NumRows BIGINT)
BEGIN
    /* BUILD INSERT SENTENCE WITH A LOS OF ROWS TO INSERT */
    DECLARE i BIGINT;
    DECLARE nMax BIGINT;
    DECLARE squery LONGTEXT;
    DECLARE svalues LONGTEXT;

    SET i = 1;
    SET nMax = NumRows + 1;
    SET squery = 'INSERT INTO `entity_versionable` (fk_entity, str1, str2, bool1, double1, DATE) VALUES ';
    SET svalues = '("1", "a1", 100, 1, 500000, "2013-06-14 12:40:45"),';

    WHILE i < nMax DO
        SET squery = CONCAT(squery, svalues);
        SET i = i + 1;
    END WHILE;

    /*SELECT squery;*/
    SET squery = LEFT(squery, CHAR_LENGTH(squery) - 1);
    SET squery = CONCAT(squery, ";");
    SELECT squery;

    /* EXECUTE INSERT SENTENCE */
    /*START TRANSACTION;*/
    /*PREPARE stmt FROM squery;
    EXECUTE stmt;
    DEALLOCATE PREPARE stmt;
    */

    /*COMMIT;*/
END$$
DELIMITER ;


结果:

  1. 连接20000个字符串需要约45秒处理时间:

调用test.inputRowsNoRandom(20000);

  1. 连接100000个字符串需要约5/12分钟的处理时间 O_O:

调用test.inputRowsNoRandom(100000);

结果(按持续时间排序)- 持续时间(总和)以秒为单位|| 百分比
释放项目 0.00005 50.00000
启动 0.00002 20.00000
执行 0.00001 10.00000
初始化 0.00001 10.00000
清理 0.00001 10.00000
总计 0.00010 100.00000

由于查询的执行而导致状态变量的更改
变量 值 描述
Bytes_received 21 从客户端发送到服务器的字节数
Bytes_sent 97 从服务器发送到客户端的字节数
Com_select 1 已执行的SELECT语句数量
Questions 1 服务器执行的语句数量

测试:
我已经尝试了不同的MySQL配置,从12到64个线程,开启和关闭缓存,将日志移动到另一个硬盘上进行测试...
还测试了使用TEXT,INT等。

附加信息:


问题:

  • 代码有什么问题吗?如果我发送100000个字符串来构建最终的SQL字符串,那么SELECT squery;的结果将是一个空字符串。发生了什么?(错误一定存在,但我看不到它)。
  • 我能以任何方式改进代码以加快速度吗?
  • 我已经阅读了一些存储过程中的操作可能非常缓慢,我应该在C/Java/PHP中生成文件,然后将其发送到mysql吗?

    mysql -u mysqluser -p databasename < numbers.sql

  • MySQL似乎仅使用一个核心处理单个SQL查询Nginx或其他数据库系统:多线程DBsCassandraRedisMongoDB..)是否可以通过存储过程实现更好的性能,并使用多个CPU处理一个查询?(因为我的单个查询只使用了大约150个线程的总CPU的20%)。

更新:


一个评论!为什么要使用DBMS循环数百万次来连接字符串,这是在用Cthulhu的名字吗?我很惊讶它能如此快速,你在这里检查礼物马的牙齿。 - Tony Hopkinson
这可能是我在这里遇到的最有信息量的问题,太棒了。 - hd1
嗨,Tony Hopkinson,我正在尝试在这里提供帮助https://dev59.com/j2Qn5IYBdhLWcg3wLkgd?noredirect=1#comment24768877_17042760,并且正在尝试快速填充表格以检查性能并使用数百万数据进行一些测试,但是我发现了这个问题。 - xtrm
2个回答

11

不要在关系型数据库中使用循环,尤其是在大规模数据上。

尝试使用查询快速填充您的表格,使其达到100万行。

INSERT INTO `entity_versionable` (fk_entity, str1, str2, bool1, double1, date)
SELECT 1, 'a1', 100, 1, 500000, '2013-06-14 12:40:45'
  FROM
(
select a.N + b.N * 10 + c.N * 100 + d.N * 1000 + e.N * 10000 + f.N * 100000 + 1 N
from (select 0 as N union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) a
      , (select 0 as N union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) b
      , (select 0 as N union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) c
      , (select 0 as N union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) d
      , (select 0 as N union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) e
      , (select 0 as N union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) f
) t

这个操作花费了我的电脑( MacBook Pro 16GB RAM, 2.6Ghz Intel Core i7) 大约8秒钟才能完成。

查询成功,影响了1000000行数据(用时7.63秒)
记录数:1000000,重复数:0,警告数:0

更新1:现在有一种存储过程的版本可以使用预处理语句。

DELIMITER $$
CREATE PROCEDURE `inputRowsNoRandom`(IN NumRows INT)
BEGIN
    DECLARE i INT DEFAULT 0;

    PREPARE stmt 
       FROM 'INSERT INTO `entity_versionable` (fk_entity, str1, str2, bool1, double1, date)
             VALUES(?, ?, ?, ?, ?, ?)';
    SET @v1 = 1, @v2 = 'a1', @v3 = 100, @v4 = 1, @v5 = 500000, @v6 = '2013-06-14 12:40:45';

    WHILE i < NumRows DO
        EXECUTE stmt USING @v1, @v2, @v3, @v4, @v5, @v6;
        SET i = i + 1;
    END WHILE;

    DEALLOCATE PREPARE stmt;
END$$
DELIMITER ;

大约3分钟完成:

mysql> CALL inputRowsNoRandom(1000000);
Query OK, 0 rows affected (2 min 51.57 sec)

感受8秒和3分钟之间的巨大差异

更新2为了加快速度,我们可以显式地使用事务并批量提交插入。因此,这里是SP的改进版本。

DELIMITER $$
CREATE PROCEDURE inputRowsNoRandom1(IN NumRows BIGINT, IN BatchSize INT)
BEGIN
    DECLARE i INT DEFAULT 0;

    PREPARE stmt 
       FROM 'INSERT INTO `entity_versionable` (fk_entity, str1, str2, bool1, double1, date)
             VALUES(?, ?, ?, ?, ?, ?)';
    SET @v1 = 1, @v2 = 'a1', @v3 = 100, @v4 = 1, @v5 = 500000, @v6 = '2013-06-14 12:40:45';

    START TRANSACTION;
    WHILE i < NumRows DO
        EXECUTE stmt USING @v1, @v2, @v3, @v4, @v5, @v6;
        SET i = i + 1;
        IF i % BatchSize = 0 THEN 
            COMMIT;
            START TRANSACTION;
        END IF;
    END WHILE;
    COMMIT;
    DEALLOCATE PREPARE stmt;
END$$
DELIMITER ;

不同批次大小的结果:

mysql> CALL inputRowsNoRandom1(1000000,1000);
查询成功,共影响 0 行 (27.25 秒)
mysql> CALL inputRowsNoRandom1(1000000,10000); 查询成功,共影响 0 行 (26.76 秒)
mysql> CALL inputRowsNoRandom1(1000000,100000); 查询成功,共影响 0 行 (26.43 秒)

您可以自己看到差异。仍然比交叉连接慢三倍以上。


1
非常好的Peterm,非常感谢你,我会仔细查看代码并将标题更改为MySQL:高效地在存储过程中填充表。 此主题还添加了相关信息:https://dev59.com/V0nSa4cB1Zd3GeqPM1On - xtrm
测试了第一种方法(使用选择最快),插入了1亿行数据。完成所需时间为16分37秒。我将使用相同的方法测试插入随机数据,可能需要更长时间。 - xtrm

0
我有一个和你一样的任务。上面的答案解释得很顺畅。我想谈谈我的解决方案。
在我的任务中,我有一个查询。首先我生成随机数据,然后将其加入查询队列,再从队列中出队并写入数据库。一开始需要70个小时才能写入242M数据行。然后我更改了引擎。MyISAM比InnoDB快得多。之后只需要30个小时就可以写入。但仍然太长时间了...所以我必须做出改变...
与其执行"insert ..... VALUES (1,2,3)",
我执行了"insert .... VALUES (1,2,3), (4,5,6), (7,8,9)"。
它运行得非常快。我的观点是将它们分成较小的块,并像批量数据一样写入数据库。
编辑:我写这些是因为我遇到了很多麻烦,然后我找到了解决方法,现在我想分享一下。哈哈

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接