Mysql 为什么在向数据库插入数据时,“state”不比“my”快? 在转换数据库的中间,我尝试使用最好/最快的插入。好的,惯用的mass-insert应该准备语句处理程序,然后迭代数据以插入它。诸如此类: my $sql = q|INSERT INTO test.table ( value ) VALUES ( ? ) |; my $sth = $dbh->prepare( $sql ); for my $val ( 1 .. 1000000 ) { $sth->execute( $val ); }

Mysql 为什么在向数据库插入数据时,“state”不比“my”快? 在转换数据库的中间,我尝试使用最好/最快的插入。好的,惯用的mass-insert应该准备语句处理程序,然后迭代数据以插入它。诸如此类: my $sql = q|INSERT INTO test.table ( value ) VALUES ( ? ) |; my $sth = $dbh->prepare( $sql ); for my $val ( 1 .. 1000000 ) { $sth->execute( $val ); },mysql,perl,benchmarking,dbi,Mysql,Perl,Benchmarking,Dbi,我认为在状态声明器的帮助下,我可以将这个例程重构为函数,类似这样: 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"; } 现在,在所有插入过程中,

我认为在状态声明器的帮助下,我可以将这个例程重构为函数,类似这样:

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准备一次,这是增强的基础

在迁移数据库的过程中,我觉得这一改进并没有给我带来我所希望的胜利。然后我发现了一篇文章 ,这和我问自己的问题非常相似:为什么州政府不给我的生活带来任何改善

在注释中,RJB猜测,当使用state和my初始化语句处理程序时,差异将是显著的。我做了一些基准测试,得出了和本文作者一样的结论:即使在某些情况下,我的状态快了0.5%,但在大多数情况下,我的状态保持在相同的速度,甚至快了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( ) ; } ,  
  }
);  

那么,为什么州政府不能在这里战胜我的对手呢?这应该很容易。或者?

你在一块有一头牛在吃草的田地里追赶蚱蜢,寻找蛋白质

在对数据库进行大容量插入或更新时,如果您希望提高速度,autocommit不是您的朋友。完成后执行一次提交,或者每1000条记录中抛出一条


最后,如果您还在等待更改和索引更新的磁盘I/O完成,那么每次迭代节省一些CPU周期将毫无意义。

您在一块有奶牛放牧的田地里追逐蚱蜢寻找蛋白质

在对数据库进行大容量插入或更新时,如果您希望提高速度,autocommit不是您的朋友。完成后执行一次提交,或者每1000条记录中抛出一条


最后,如果您也在等待更改和索引更新的磁盘I/O完成,那么每次迭代节省一些CPU周期将毫无意义。

多亏了@Borodin,我可能会宣布:状态仍然战胜了我,这是应该和预期的

我的代码时间有输入错误。这些代码应该有一行正确的“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)
在我看来,自动提交在这里没有很好的影响


把我的答案留在这里供将来参考。

多亏了@Borodin,我可能会宣布:国家仍然战胜了我,这是应该的,也是意料之中的

我的代码时间有输入错误。这些代码应该有一行正确的“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)
在我看来,自动提交在这里没有很好的影响


将我的答案留在这里供将来参考。

如果您想测试state vs my,那么您真正想要基准测试的就是准备的成本。我加入了prepare_缓存,因为它做同样的事情

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%             --
这只是告诉您,不运行代码比运行代码快。如果您想知道这会对实际应用程序产生多大的影响,那么您走的是正确的道路,但您的基准测试已经搞砸了。这使它更像是如何在生产自动提交关闭、RaiseError打开、消除表脚手架和使用表的过程中运行代码。重要的是,它删除了大量额外的代码和子例程,这些代码和子例程只会破坏基准测试

结果,毫不奇怪,1000次插入的成本超过了准备插入的成本。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%             --

如果您想测试state和my,那么您真正想要基准测试的就是准备的成本。我加入了prepare_缓存,因为它做同样的事情

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%             --
这只是告诉您,不运行代码比运行代码快。如果您想知道这会对实际应用程序产生多大的影响,那么您走的是正确的道路,但您的基准测试已经搞砸了。这使它更像是如何在生产自动提交关闭、RaiseError打开、消除表脚手架和使用表的过程中运行代码。重要的是,它删除了大量额外的代码和子例程,这些代码和子例程只会破坏基准测试

结果是,毫不奇怪,做100件事的成本 0次插入将大大降低准备插入的成本。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%             --

有点离题:你知道加载数据填充吗?这些是你的选择吗?这是一个不公平的状态测试,因为它提供的速度优势被相关的数据库操作淹没了,特别是当您将DROP/CREATE作为基准计时的一部分时。每次插入都会导致几次磁盘写入操作,执行这些操作需要数千个CPU周期。这可以通过启动一个事务在某种程度上缓解,特别是如果您可以让MySQL将该事务记录在内存中而不是磁盘上。这在SQLite中是可能的,但我也不知道MySQL。a是这样,但b更像是太多的磁盘操作。目前,您每次都在提交插入。更有效的方法是积累一个更改列表,让DB引擎执行,然后一次完成所有更改。底线是,通过对包含磁盘操作的代码段进行计时来对CPU性能进行基准测试肯定会失败。但是通过编写callFor=>\&callFor等来避免额外级别的子例程调用会更干净。@w.k这两个选项都不需要单独的工具。要进行批处理/复合插入,只需在一条语句中组合多个值:插入表值foo、bar、apple、orange、pony、unicorn。如果一路盘车,一次最多可以发送1GB的查询,所有这些都来自Perl脚本。对于大规模更新,加载数据填充会更快;munge您的数据,将其写入一个CSV文件,然后调用LoadDataInfile,同样都是在您的Perl脚本中?这些是你的选择吗?这是一个不公平的状态测试,因为它提供的速度优势被相关的数据库操作淹没了,特别是当您将DROP/CREATE作为基准计时的一部分时。每次插入都会导致几次磁盘写入操作,执行这些操作需要数千个CPU周期。这可以通过启动一个事务在某种程度上缓解,特别是如果您可以让MySQL将该事务记录在内存中而不是磁盘上。这在SQLite中是可能的,但我也不知道MySQL。a是这样,但b更像是太多的磁盘操作。目前,您每次都在提交插入。更有效的方法是积累一个更改列表,让DB引擎执行,然后一次完成所有更改。底线是,通过对包含磁盘操作的代码段进行计时来对CPU性能进行基准测试肯定会失败。但是通过编写callFor=>\&callFor等来避免额外级别的子例程调用会更干净。@w.k这两个选项都不需要单独的工具。要进行批处理/复合插入,只需在一条语句中组合多个值:插入表值foo、bar、apple、orange、pony、unicorn。如果一路盘车,一次最多可以发送1GB的查询,所有这些都来自Perl脚本。对于大规模更新,加载数据填充会更快;munge您的数据,将其写入CSV文件,然后调用LoadDatainfle,同样都是从您的Perl脚本中进行的。我知道你不是素食程序员。我的基准测试没有证实你的假设。你有什么想法吗,为什么?首先,为了简单起见,我放弃了提交,但在将其添加到flow之后,我很惊讶它没有产生好的影响。另一种方法是将插入的内容排队到文件中,然后在达到一定的批处理大小后将文件加载到DB中,我们在工作时默认为5000行。这个方法很干净,,对我们来说,这是一种有效的方法,因为我们可以使用一个简单的Perl模块来隐藏这种丑陋,只需使用一个push_行队列,将一行文件上传到数据库,并在程序中重置批处理对象,如果我的程序从自动提交切换到每1000次插入执行一次提交,并且程序的速度是1000倍。但这是针对SQLite而不是MySQL的。@w.k autocommit在使用MyISAM表时没有任何好的效果,这是因为MyISAM不支持事务,所以即使禁用了autocommit,每个语句都会自动提交。确实如此。我知道你不是素食程序员。我的基准测试没有证实你的假设。你有什么想法吗,为什么?首先,为了简单起见,我没有提交,但在将其添加到flow之后,我很惊讶它没有产生好的影响。另一种方法是将插入的内容排队到一个文件中,然后在达到某个批处理大小后将文件加载到DB中,我们默认在wor时为5000行
K这个方法很干净,,对我们来说,这是一种有效的方法,因为我们可以使用一个简单的Perl模块来隐藏这种丑陋,只需使用一个push_行队列,将一行文件上传到数据库,并在程序中重置批处理对象,如果我的程序从自动提交切换到每1000次插入执行一次提交,并且程序的速度是1000倍。但这是针对SQLite而不是MySQL的。@w.k autocommit在使用MyISAM表时没有任何好的效果,这是因为MyISAM不支持事务,所以即使禁用了autocommit,每个语句也会自动提交。