在将数据插入数据库时​​,为什么`state`不比`my`快?

时间:2015-02-03 12:14:16

标签: mysql perl benchmarking dbi

在转换数据库的过程中,我尝试使用最佳/最快插入。 AFAIK,惯用的大量插入应该准备语句处理程序,然后迭代数据插入它。这样的事情:

my $sql = q|INSERT INTO test.table ( value ) VALUES ( ? ) |;  
my $sth = $dbh->prepare( $sql );  
for my $val ( 1 .. 1000000 ) {
  $sth->execute( $val );
}

我认为在state - 声明符的帮助下,我可以将这个例程重构为函数,如下所示:

sub sql_state {  
  my ( $val ) = @_;
  state $sql = q|INSERT INTO test.table ( value ) VALUES ( ? ) |;  
  state $sth = $dbh->prepare( $sql );  
  $sth->execute( $val ) 
    or die "State";  
}  

所以现在$sql在所有插入期间初始化一次,$sth也准备一次,这是增强的基础。

在迁移我的数据库期间,我觉得这种改进并没有像我希望的那样给我这样的胜利。然后我找到了一篇文章 Enemy of the State,这与我自己提出的问题完全相同:为什么state没有对my做出任何改进?

在rjbs猜测中,在初始化语句处理程序时使用statemy时差异会很大。我做了一些基准测试并得出了与文章作者完全相同的结论:即使在某些情况下我得到state一点点(0.5%)更快,在大多数情况下my速度相同或者甚至更快(高达9%)。

首先,我尝试使用innodb表,因为我需要完成自己的任务:

Benchmark: timing 100 iterations of callFor, callMy, callState...
   callFor: 922 wallclock secs ( 7.31 usr +  3.78 sys = 11.09 CPU) @  9.02/s (n=100)
    callMy: 927 wallclock secs ( 6.09 usr +  4.46 sys = 10.55 CPU) @  9.48/s (n=100)
 callState: 922 wallclock secs ( 6.72 usr +  4.62 sys = 11.34 CPU) @  8.82/s (n=100)

那些对于更广泛的迭代来说太慢了,所以我也用myisam表做了一些(1000x1000 =百万次插入):

Benchmark: timing 1000 iterations of callfor, callmy, callstate...
   callfor: 96 wallclock secs (15.19 usr + 15.50 sys = 30.69 CPU) @ 32.58/s (n=1000)
    callmy: 95 wallclock secs (15.18 usr + 14.90 sys = 30.08 CPU) @ 33.24/s (n=1000)
 callstate: 104 wallclock secs (18.86 usr + 16.15 sys = 35.01 CPU) @ 28.56/s (n=1000)

另一场比赛:

Benchmark: timing 1000 iterations of callfor, callmy, callstate...
   callfor: 94 wallclock secs (14.90 usr + 14.47 sys = 29.37 CPU) @ 34.05/s (n=1000)
    callmy: 92 wallclock secs (14.77 usr + 14.09 sys = 28.86 CPU) @ 34.65/s (n=1000)
 callstate: 99 wallclock secs (17.66 usr + 15.30 sys = 32.96 CPU) @ 30.34/s (n=1000)

以下是我测试的实际代码:

use strict; use warnings; use 5.010;
use ...; # something to get $dbh ...
use Benchmark qw{:all} ;  


sub prepareTable {
  my $dropTable   = q|DROP TABLE IF EXISTS test.table|;
  $dbh->do( $dropTable )
    || die "droptable";

  my $createTable = q|
    CREATE TABLE test.table (
      `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
      `value` varchar(60),
      PRIMARY KEY (`id`)
    ) ENGINE=MYISAM
  |;
  $dbh->do( $createTable )
    || die "createtable";  
}


sub callFor {
  prepareTable();

  my $sql = q|INSERT INTO test.table ( value ) VALUES ( ? ) |;  
  my $sth = $dbh->prepare( $sql );  

  for my $val ( 1 .. 1000 ) {
    sql_for( $sth, $val );
  }
}

sub callMy {
  prepareTable();

  for my $val ( 1 .. 1000 ) {
    sql_my( $val );
  }
}

sub callState {
  prepareTable();

  for my $val ( 1 .. 1000 ) {
    sql_state( $val );
  }
}

sub sql_for {  
  my ( $sth, $val ) = @_;
  $sth->execute( $val ) 
    or die "For";
}  

sub sql_my {  
  my ( $val ) = @_;
  my $sql = q|INSERT INTO test.table ( value ) VALUES ( ? ) |;  
  my $sth = $dbh->prepare( $sql );  
  $sth->execute( $val ) 
    or die "My";
}  

sub sql_state {  
  my ( $val ) = @_;
  state $sql = q|INSERT INTO test.table ( value ) VALUES ( ? ) |;  
  state $sth = $dbh->prepare( $sql );  
  $sth->execute( $val ) 
    or die "State";  
}  


timethese(  
  1000 , {  
    'callFor'   => sub { callFor( ) ; } ,  
    'callMy'    => sub { callFor( ) ; } ,  
    'callState' => sub { callState( ) ; } ,  
  }
);  

那么,为什么statemy上没有获胜?应该很容易。或?

3 个答案:

答案 0 :(得分:10)

你正在牧场上追逐蚱蜢寻找蛋白质。

在对数据库进行批量插入或更新时,如果您想要速度,则autocommit 您的朋友。当你完成时只做一个commit,或者每1000个记录左右抛出一个。{/ p>

最后,如果您还在等待更改的磁盘I / O和索引更新完成,那么每次迭代保存几个CPU周期都没有任何意义。

答案 1 :(得分:5)

如果你想测试状态vs我的,你真正想要的基准是准备的成本。我扔了prepare_cached,因为它做了同样的事情。

use strict; use warnings; use 5.010;
use DBI;
use Benchmark qw{:all} ;  

my $dbh = DBI->connect('DBI:mysql:database=test', '', '',
                       {RaiseError => 1, AutoCommit => 0}
                      );

sub prepareTable {
    my $dropTable   = q|DROP TABLE IF EXISTS test.table|;
    $dbh->do( $dropTable );

    my $createTable = q{
    CREATE TABLE test.table (
      `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
      `value` varchar(60),
      PRIMARY KEY (`id`)
    )
    };
    $dbh->do( $createTable );
}

prepareTable;
my $sql = q|INSERT INTO test.table ( value ) VALUES ( ? ) |;
cmpthese( -3, {
    'my'                => sub { my $sth = $dbh->prepare($sql); return; },  
    'state'             => sub { state $sth = $dbh->prepare($sql); return; },
    'prepare_cached'    => sub { my $sth = $dbh->prepare_cached($sql); return; },
});
__END__
                     Rate             my prepare_cached          state
my                67966/s             --           -73%           -99%
prepare_cached   253414/s           273%             --           -98%
state          11267589/s         16478%          4346%             --

这只是告诉您不运行代码比运行代码更快。如果你想知道这会对实际应用程序产生多大影响,那么你就是在正确的道路上,但是你已经搞砸了你的基准。 Here is my improved benchmark。这使得它更像是如何在生产中运行代码(AutoCommit off,RaiseError on),消除表脚手架,并使用generally superior InnoDB。重要的是,它删除了许多额外的代码和子程序,这些代码和子程序只会破坏基准。

结果是,毫无疑问,执行1000次INSERT的成本淹没了准备INSERT的成本。 INSERT占据主导地位,其运行时间非常不可靠,很难从中获得一致的基准测试结果。

如果每次准备执行次数减少怎么办?准备应该开始产生更大的影响,这就是我们所看到的。

$EXECUTES_PER_PREPARE = 1;
                  Rate             my prepare_cached          state
my             24722/s             --           -36%           -56%
prepare_cached 38610/s            56%             --           -31%
state          56180/s           127%            46%             --

$EXECUTES_PER_PREPARE = 2;
                  Rate             my prepare_cached          state
my             15949/s             --           -22%           -41%
prepare_cached 20325/s            27%             --           -25%
state          27027/s            69%            33%             --

$EXECUTES_PER_PREPARE = 10;
                 Rate             my prepare_cached          state
my             4405/s             --           -17%           -22%
prepare_cached 5305/s            20%             --            -6%
state          5618/s            28%             6%             --

$EXECUTES_PER_PREPARE = 100;
                Rate             my prepare_cached          state
my             546/s             --            -0%            -1%
prepare_cached 546/s             0%             --            -1%
state          552/s             1%             1%             --

答案 2 :(得分:0)

感谢@Borodin我可能会宣布:state仍然胜过my,应该和预期一样。

我的代码timethese()中有拼写错误应该有正确的行'callMy' => sub { callMy( ) ; }并且没有问题要问。

在修复并删除数据库操作后,我得到了很多结果:

Benchmark: timing 1000 iterations of callFor, callMy, callState...
   callFor: 85 wallclock secs (15.60 usr + 13.38 sys = 28.98 CPU) @ 34.51/s (n=1000)
    callMy: 162 wallclock secs (64.36 usr + 21.17 sys = 85.53 CPU) @ 11.69/s (n=1000)
 callState: 86 wallclock secs (15.83 usr + 13.55 sys = 29.38 CPU) @ 34.04/s (n=1000)

然后我设置$dbh->{AutoCommit} = 0;并在迭代后使用$dbh->commit();

Benchmark: timing 1000 iterations of callFor, callMy, callState...
   callFor: 87 wallclock secs (16.57 usr + 14.50 sys = 31.07 CPU) @ 32.19/s (n=1000)
    callMy: 166 wallclock secs (66.39 usr + 21.92 sys = 88.31 CPU) @ 11.32/s (n=1000)
 callState: 87 wallclock secs (16.50 usr + 14.21 sys = 30.71 CPU) @ 32.56/s (n=1000)

对我来说,autocommit在这里影响不大。

留下我的答案以供将来参考。