【问题标题】:Why such a huge difference in time creating a table via script vs INSERT SELECT?为什么通过脚本与 INSERT SELECT 创建表的时间差异如此之大?
【发布时间】:2013-08-16 23:57:40
【问题描述】:

我创建了一个小的 Perl 脚本来向表中插入大量条目。
示例 sn-p:

$query = "insert into big_table (first_name, last_name) values (?, ?) ";

# prepare your statement for connecting to the database
$statement = $connection->prepare($query);

for(my $i = 1; $ i <= 70000; i++) {  
    my $first = "test".$i;  
    my $last = "test".$i;   
    $statement->execute($first, $last);   
}  

它插入了行,大约需要 15 分钟

但是当我这样做时:

CREATE TABLE big_table2 like big_table;  
INSERT INTO big_table2 SELECT * FROM big_table;  

只用了 55 秒!对于 70000 行
为什么会有这么大的差异?

【问题讨论】:

  • 最有可能的罪魁祸首是事务开销(每个事务都可以强制磁盘写入)。尝试将 for 循环包装为单个事务,看看性能是否有所提高。

标签: mysql performance perl create-table


【解决方案1】:

为了加快循环插入速度,您可以尝试使用事务:

在连接中添加AutoCommit=&gt;0属性:

my $connection = DBI->connect($dsn,$username,$password, AutoCommit=>0); # transaction enabled

然后在 for 循环之后,您可以在单个原子操作中提交所有更改:

$connection->commit();

您注意到的性能差异可归因于您在 for 循环中执行的几个操作,您正在执行一个查询并写入磁盘 70000 次,而另一个查询仅对所有记录执行一次...... .

【讨论】:

    【解决方案2】:

    可能是因为脚本必须写入磁盘 70,000 次,而 INSERT SELECT 将磁盘 I/O 降至最低。

    此外,您可以这样简化 SQL:[create table select syntax]

    create table big_table2 select * from big_table;
    

    我认为无需编写 Perl 脚本即可在 SQL 脚本中进行操作。

    【讨论】:

    • I think there's no need to write a Perl script for what you can do in a SQL script.我不知道如何从SQL创建原始的70000行
    • @aleroot 但是,仍然不需要编写 Perl 脚本。
    【解决方案3】:

    为什么会有这么大的差异?

    因为这些单独的 INSERT 语句(来自脚本)中的每一个都必须发送到数据库,在那里必须对其进行解析(检查语法和语义......关键字和标识符位于适当的位置,标识符是有效的, 用户对对象有权限,... 然后 MySQL 必须制定一个执行计划(实际执行操作的可执行代码,对于插入来说非常简单,但仍然必须完成),然后 MySQL 必须执行操作(找到要修改的数据和索引块,获取必要的锁,进行所需的块更改,检查索引违规,触发触发器等,然后将块更改写入二进制/复制日志(二进制日志或语句日志) ),然后提交更改、释放锁、清理资源并向调用者返回一个状态。如果这是从远程机器上运行的,那么那些通过网络往返于数据库的往返会增加时间。

    因此,每个语句都有开销。对单个语句来说并不是很大,但对于很多语句来说,它开始快速加起来(或慢慢加起来)。

    MySQL 对单个 INSERT 语句的工作要少得多。

    这就是为什么我们尽可能避免在循环 RBAR(Row By Agonizing Row.)中处理单个行。


    MySQL有一个优化可以加快INSERT,在同一个语句中插入多行...

    INSERT INTO mytable (mycol1, mycol2) VALUES ('a','a'),('b','b'),('c','c')
    

    但是,在你对行数大发雷霆之前,SQL 语句的最大大小是有限制的;我相信字节数受max_allowed_packet 的限制。 (请注意,限制是字节,而不是字符,以防万一您使用的是 UTF-8 并且某些字符需要两个或更多字节。)


    要在 MySQL 中生成 70,000 行,而不需要脚本,您可以执行以下操作:

    INSERT INTO mytable (first_name, last_name)
    SELECT CONCAT('test',i.i) AS first_name
         , CONCAT('test',i.i) AS last_name
      FROM ( SELECT 1 + ten_thousands.digit*10000
                      + thousands.digit*1000
                      + hundreds.digit*100 
                      + tens.digit*10 
                      + ones.digit
                    AS i
               FROM ( SELECT 0 AS digit UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3
                                        UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6
                    ) ten_thousands
              CROSS
               JOIN ( SELECT 0 AS digit 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
                    ) thousands
              CROSS
               JOIN ( SELECT 0 AS digit 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
                    ) hundreds
              CROSS
               JOIN ( SELECT 0 AS digit 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
                    ) tens 
              CROSS
               JOIN ( SELECT 0 AS digit 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
                    ) ones
              ORDER
                 BY ten_thousands.digit
                  , thousands.digit
                  , hundreds.digit
                  , tens.digit
                  , ones.digit
           ) i
    

    【讨论】:

    • 我认为你在这个答案上太过分了。
    • +1 但我不确定第一部分的解释。我使用预编译和重用的准备好的语句,并将相同的数据库连接绑定值循环到相同的预编译 SQL 语句。那么为什么会出现您提到的开销(解析/特权/执行计划等)?
    • @Jim:如果启用了服务器端准备好的语句,那么你是对的,我所说的一些正在执行的语句处理在后续执行中被消除了。使用我正在使用的 DBD::mysql 库,服务器端准备好的语句未启用除非我包括mysql_server_prepare=1 在连接中。如果没有,它的 client 端执行占位符的替换,并且客户端将包含文字的 SQL 文本发送到服务器。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-12-12
    • 1970-01-01
    • 1970-01-01
    • 2019-09-13
    • 2012-10-18
    • 1970-01-01
    相关资源
    最近更新 更多