From 964bb094a0fd7f194fb4f76e4785257b3157e551 Mon Sep 17 00:00:00 2001 From: Daniel Nichter Date: Thu, 13 Oct 2011 09:23:53 -0600 Subject: [PATCH] Get row estimate from NibbleIterator. Use user's index over MySQL's index when --where is given. --- bin/pt-table-checksum | 163 ++++++++++++++++-------------- lib/NibbleIterator.pm | 19 +++- t/lib/NibbleIterator.t | 11 +- t/pt-table-checksum/chunk_index.t | 122 ++++++++++++++-------- 4 files changed, 193 insertions(+), 122 deletions(-) diff --git a/bin/pt-table-checksum b/bin/pt-table-checksum index 97bb5945..1510ddc0 100755 --- a/bin/pt-table-checksum +++ b/bin/pt-table-checksum @@ -3461,15 +3461,18 @@ sub new { } my ($cxn, $tbl, $chunk_size, $o, $q) = @args{@required_args}; + my ($row_est, $mysql_index) = _get_row_estimate(%args); my $one_nibble = !defined $args{one_nibble} || $args{one_nibble} - ? _can_nibble_once(dbh => $cxn->dbh(), %args) + ? $row_est < $chunk_size * $o->get('chunk-size-limit') : 0; + MKDEBUG && _d('One nibble:', $one_nibble ? 'yes' : 'no'); - my $index = _find_best_index(dbh => $cxn->dbh(), %args); + my $index = _find_best_index(%args, mysql_index => $mysql_index); if ( !$index && !$one_nibble ) { die "There is no good index and the table is oversized."; } + my $where = $o->get('where'); my $self; if ( $one_nibble ) { my $tbl_struct = $tbl->{tbl_struct}; @@ -3482,7 +3485,7 @@ sub new { . ($args{select} ? $args{select} : join(', ', map { $q->quote($_) } @cols)) . " FROM " . $q->quote(@{$tbl}{qw(db tbl)}) - . ($args{where} ? " AND ($args{where})" : '') + . ($where ? " AND ($where)" : '') . " /*checksum table*/"; MKDEBUG && _d('One nibble statement:', $nibble_sql); @@ -3491,7 +3494,7 @@ sub new { . ($args{select} ? $args{select} : join(', ', map { $q->quote($_) } @cols)) . " FROM " . $q->quote(@{$tbl}{qw(db tbl)}) - . ($args{where} ? " AND ($args{where})" : '') + . ($where ? " AND ($where)" : '') . " /*explain checksum table*/"; MKDEBUG && _d('Explain one nibble statement:', $explain_nibble_sql); @@ -3521,7 +3524,7 @@ sub new { = "SELECT /*!40001 SQL_NO_CACHE */ " . join(', ', map { $q->quote($_) } @{$asc->{scols}}) . " FROM $from" - . ($args{where} ? " WHERE $args{where}" : '') + . ($where ? " WHERE $where" : '') . " ORDER BY $order_by" . " LIMIT 1" . " /*first lower boundary*/"; @@ -3531,7 +3534,7 @@ sub new { = "SELECT /*!40001 SQL_NO_CACHE */ " . join(', ', map { $q->quote($_) } @{$asc->{scols}}) . " FROM $from" - . ($args{where} ? " WHERE $args{where}" : '') + . ($where ? " WHERE $where" : '') . " ORDER BY " . join(' DESC, ', map {$q->quote($_)} @{$index_cols}) . ' DESC' . " LIMIT 1" @@ -3543,7 +3546,7 @@ sub new { . join(', ', map { $q->quote($_) } @{$asc->{scols}}) . " FROM $from" . " WHERE " . $asc->{boundaries}->{'>='} - . ($args{where} ? " AND ($args{where})" : '') + . ($where ? " AND ($where)" : '') . " ORDER BY $order_by" . " LIMIT ?, 2" . " /*next chunk boundary*/"; @@ -3556,7 +3559,7 @@ sub new { . " FROM $from" . " WHERE " . $asc->{boundaries}->{'>='} # lower boundary . " AND " . $asc->{boundaries}->{'<='} # upper boundary - . ($args{where} ? " AND ($args{where})" : '') + . ($where ? " AND ($where)" : '') . " ORDER BY $order_by" . " /*checksum chunk*/"; MKDEBUG && _d('Nibble statement:', $nibble_sql); @@ -3568,7 +3571,7 @@ sub new { . " FROM $from" . " WHERE " . $asc->{boundaries}->{'>='} # lower boundary . " AND " . $asc->{boundaries}->{'<='} # upper boundary - . ($args{where} ? " AND ($args{where})" : '') + . ($where ? " AND ($where)" : '') . " ORDER BY $order_by" . " /*explain checksum chunk*/"; MKDEBUG && _d('Explain nibble statement:', $explain_nibble_sql); @@ -3589,13 +3592,14 @@ sub new { sql => { columns => $asc->{scols}, from => $from, - where => $args{where}, + where => $where, boundaries => $asc->{boundaries}, order_by => $order_by, }, }; } + $self->{row_est} = $row_est; $self->{nibbleno} = 0; $self->{have_rows} = 0; $self->{rowno} = 0; @@ -3746,26 +3750,46 @@ sub more_boundaries { return !$self->{no_more_boundaries}; } +sub row_estimate { + my ($self) = @_; + return $self->{row_est}; +} + sub _find_best_index { my (%args) = @_; - my @required_args = qw(tbl TableParser dbh Quoter); - my ($tbl, $tp) = @args{@required_args}; - + my @required_args = qw(Cxn tbl TableParser); + my ($cxn, $tbl, $tp) = @args{@required_args}; my $tbl_struct = $tbl->{tbl_struct}; my $indexes = $tbl_struct->{keys}; + my $want_index = $args{chunk_index}; + if ( $want_index ) { + MKDEBUG && _d('User wants to use index', $want_index); + if ( !exists $indexes->{$want_index} ) { + MKDEBUG && _d('Cannot use user index because it does not exist'); + $want_index = undef; + } + } + + if ( !$want_index && $args{mysql_index} ) { + MKDEBUG && _d('MySQL wants to use index', $args{mysql_index}); + $want_index = $args{mysql_index}; + } + my $best_index; my @possible_indexes; - if ( my $want_index = $args{chunk_index} ) { - MKDEBUG && _d('Want to use nibble index', $want_index); - if ( $want_index eq 'PRIMARY' || $indexes->{$want_index}->{is_unique} ) { + if ( $want_index ) { + if ( $indexes->{$want_index}->{is_unique} ) { + MKDEBUG && _d('Will use wanted index'); $best_index = $want_index; } else { + MKDEBUG && _d('Wanted index is a possible index'); push @possible_indexes, $want_index; } } else { + MKDEBUG && _d('Auto-selecting best index'); foreach my $index ( $tp->sort_indexes($tbl_struct) ) { if ( $index eq 'PRIMARY' || $indexes->{$index}->{is_unique} ) { $best_index = $index; @@ -3804,14 +3828,14 @@ sub _find_best_index { sub _get_index_cardinality { my (%args) = @_; - my @required_args = qw(dbh tbl index Quoter); - my ($dbh, $tbl, $index, $q) = @args{@required_args}; + my @required_args = qw(Cxn tbl index Quoter); + my ($cxn, $tbl, $index, $q) = @args{@required_args}; my $sql = "SHOW INDEXES FROM " . $q->quote(@{$tbl}{qw(db tbl)}) . " WHERE Key_name = '$index'"; MKDEBUG && _d($sql); my $cardinality = 1; - my $rows = $dbh->selectall_hashref($sql, 'key_name'); + my $rows = $cxn->dbh()->selectall_hashref($sql, 'key_name'); foreach my $row ( values %$rows ) { $cardinality *= $row->{cardinality} if $row->{cardinality}; } @@ -3819,17 +3843,24 @@ sub _get_index_cardinality { return $cardinality; } -sub _can_nibble_once { +sub _get_row_estimate { my (%args) = @_; - my @required_args = qw(dbh tbl chunk_size OptionParser TableParser); - my ($dbh, $tbl, $chunk_size, $o, $tp) = @args{@required_args}; - my ($table_status) = $tp->get_table_status($dbh, $tbl->{db}, $tbl->{tbl}); - MKDEBUG && _d('TABLE STATUS', Dumper($table_status)); - my $n_rows = $table_status->{rows} || 0; - my $limit = $o->get('chunk-size-limit'); - my $one_nibble = $n_rows < $chunk_size * $limit ? 1 : 0; - MKDEBUG && _d('One nibble:', $one_nibble ? 'yes' : 'no'); - return $one_nibble; + my @required_args = qw(Cxn tbl OptionParser TableParser Quoter); + my ($cxn, $tbl, $o, $tp, $q) = @args{@required_args}; + + if ( my $where = $o->get('where') ) { + MKDEBUG && _d('WHERE clause, using explain plan for row estimate'); + my $table = $q->quote(@{$tbl}{qw(db tbl)}); + my $sql = "EXPLAIN SELECT COUNT(*) FROM $table WHERE $where"; + MKDEBUG && _d($sql); + my $expl = $cxn->dbh()->selectrow_hashref($sql); + MKDEBUG && _d(Dumper($expl)); + return ($expl->{rows} || 0), $expl->{key}; + } + else { + MKDEBUG && _d('No WHERE clause, using table status for row estimate'); + return $tbl->{tbl_status}->{rows} || 0; + } } sub _prepare_sths { @@ -4292,6 +4323,7 @@ sub next { } delete $schema_obj->{ddl} unless $self->{keep_ddl}; + delete $schema_obj->{tbl_status} unless $self->{keep_tbl_status}; if ( my $schema = $self->{Schema} ) { $schema->add_schema_object($schema_obj); @@ -4408,27 +4440,31 @@ sub _iterate_dbh { while ( my $tbl = shift @{$self->{tbls}} ) { next unless $self->_resume_from_table($tbl); - my $engine; + + my $tbl_status; if ( $self->{filters}->{'engines'} - || $self->{filters}->{'ignore-engines'} ) { + || $self->{filters}->{'ignore-engines'} + || $self->{keep_tbl_status} ) + { my $sql = "SHOW TABLE STATUS FROM " . $q->quote($self->{db}) . " LIKE \'$tbl\'"; MKDEBUG && _d($sql); - $engine = $dbh->selectrow_hashref($sql)->{engine}; - MKDEBUG && _d($tbl, 'uses', $engine, 'engine'); + $tbl_status = $dbh->selectrow_hashref($sql); + MKDEBUG && _d(Dumper($tbl_status)); } - - if ( !$engine || $self->engine_is_allowed($engine) ) { + if ( !$tbl_status + || $self->engine_is_allowed($tbl_status->{engine}) ) { my $ddl; if ( my $tp = $self->{TableParser} ) { $ddl = $tp->get_create_table($dbh, $self->{db}, $tbl); } return { - db => $self->{db}, - tbl => $tbl, - ddl => $ddl, + db => $self->{db}, + tbl => $tbl, + ddl => $ddl, + tbl_status => $tbl_status, }; } } @@ -5994,11 +6030,13 @@ sub main { # Checksum each table. # ######################################################################## my $schema_iter = new SchemaIterator( - dbh => $master_dbh, - resume => $last_chunk ? $q->quote(@{$last_chunk}{qw(db tbl)}) : "", - OptionParser => $o, - TableParser => $tp, - Quoter => $q, + dbh => $master_dbh, + resume => $last_chunk ? $q->quote(@{$last_chunk}{qw(db tbl)}) + : "", + keep_tbl_status => 1, + OptionParser => $o, + TableParser => $tp, + Quoter => $q, ); TABLE: @@ -6068,12 +6106,14 @@ sub main { # Make a Progress obj for this table. It may not be used; # depends on how many rows, chunk size, how fast the server # is, etc. But just in case, all tables have a Progress obj. - if ( $o->get('progress') ) { - $tbl->{progress} = table_progress( - dbh => $master_cxn->dbh(), - tbl => $tbl, - OptionParser => $o, - Quoter => $q, + if ( $o->get('progress') + && !$nibble_iter->one_nibble() + && $nibble_iter->row_estimate() ) + { + $tbl->{progress} = new Progress( + jobsize => $nibble_iter->row_estimate(), + spec => $o->get('progress'), + name => "Checksumming $tbl->{db}.$tbl->{tbl}", ); } @@ -6552,31 +6592,6 @@ sub explain_statement { return $expl; } -sub table_progress { - my (%args) = @_; - my @required_args = qw(dbh tbl OptionParser Quoter); - foreach my $arg ( @required_args ) { - die "I need a $arg argument" unless $args{$arg}; - } - my ($dbh, $tbl, $o, $q) = @args{@required_args}; - - my $table = $q->quote(@{$tbl}{qw(db tbl)}); - my $sql = "EXPLAIN SELECT COUNT(*) FROM $table" - . ($args{where} ? " WHERE $args{where}" : ''); - MKDEBUG && _d($sql); - my $expl = $dbh->selectrow_hashref($sql); - my $rows = $expl->{rows} || 0; - my $pr; - if ( $rows ) { - $pr = new Progress( - jobsize => $rows, - spec => $o->get('progress'), - name => "Checksumming $tbl->{db}.$tbl->{tbl}", - ); - } - return $pr; -} - sub last_chunk { my (%args) = @_; my @required_args = qw(dbh repl_table); diff --git a/lib/NibbleIterator.pm b/lib/NibbleIterator.pm index 5d71fda3..5ccddaa6 100644 --- a/lib/NibbleIterator.pm +++ b/lib/NibbleIterator.pm @@ -214,6 +214,7 @@ sub new { }; } + $self->{row_est} = $row_est; $self->{nibbleno} = 0; $self->{have_rows} = 0; $self->{rowno} = 0; @@ -373,6 +374,11 @@ sub more_boundaries { return !$self->{no_more_boundaries}; } +sub row_estimate { + my ($self) = @_; + return $self->{row_est}; +} + sub _find_best_index { my (%args) = @_; my @required_args = qw(Cxn tbl TableParser); @@ -381,12 +387,15 @@ sub _find_best_index { my $indexes = $tbl_struct->{keys}; my $want_index = $args{chunk_index}; - MKDEBUG && _d('Wanted index:', $want_index); - if ( $want_index && !exists $indexes->{$want_index} ) { - MKDEBUG && _d('Wanted index does not exist; will auto-select best index'); - $want_index = undef; + if ( $want_index ) { + MKDEBUG && _d('User wants to use index', $want_index); + if ( !exists $indexes->{$want_index} ) { + MKDEBUG && _d('Cannot use user index because it does not exist'); + $want_index = undef; + } } - elsif ( $args{mysql_index} ) { + + if ( !$want_index && $args{mysql_index} ) { MKDEBUG && _d('MySQL wants to use index', $args{mysql_index}); $want_index = $args{mysql_index}; } diff --git a/t/lib/NibbleIterator.t b/t/lib/NibbleIterator.t index 3ab88f5c..62eeb201 100644 --- a/t/lib/NibbleIterator.t +++ b/t/lib/NibbleIterator.t @@ -39,7 +39,7 @@ if ( !$dbh ) { plan skip_all => 'Cannot connect to sandbox master'; } else { - plan tests => 28; + plan tests => 29; } my $q = new Quoter(); @@ -581,6 +581,7 @@ $ni = make_nibble_iter( one_nibble => 0, argv => [qw(--databases test --where c>'m')], ); +$dbh->do('analyze table test.t'); @rows = (); while (my $row = $ni->next()) { @@ -593,6 +594,14 @@ is_deeply( "Nibbles only values in --where clause range" ); +# The real number of rows is 13, but MySQL may estimate a little. +cmp_ok( + $ni->row_estimate(), + '<=', + 15, + "row_estimate()" +); + # ############################################################################# # Done. # ############################################################################# diff --git a/t/pt-table-checksum/chunk_index.t b/t/pt-table-checksum/chunk_index.t index 96e217ee..4510ec98 100644 --- a/t/pt-table-checksum/chunk_index.t +++ b/t/pt-table-checksum/chunk_index.t @@ -15,7 +15,6 @@ use PerconaTest; use Sandbox; require "$trunk/bin/pt-table-checksum"; -my $vp = new VersionParser(); my $dp = new DSNParser(opts=>$dsn_opts); my $sb = new Sandbox(basedir => '/tmp', DSNParser => $dp); my $dbh = $sb->get_dbh_for('master'); @@ -27,18 +26,29 @@ else { plan tests => 6; } -my $output; + my $cnf='/tmp/12345/my.sandbox.cnf'; -my @args = ('-F', $cnf, 'h=127.1', qw(-d issue_519 --explain --chunk-size 3)); +# The sandbox servers run with lock_wait_timeout=3 and it's not dynamic +# so we need to specify --lock-wait-timeout=3 else the tool will die. +my $master_dsn = 'h=127.1,P=12345,u=msandbox,p=msandbox'; +my @args = ($master_dsn, qw(--lock-wait-timeout 3 -d issue_519 --explain --explain --chunk-size 3)); +my $output; $sb->load_file('master', "t/pt-table-checksum/samples/issue_519.sql"); -my $default_output = "issue_519 t SELECT /*issue_519.t:1/5*/ 0 AS chunk_num, COUNT(*) AS cnt, COALESCE(LOWER(CONV(BIT_XOR(CAST(CRC32(CONCAT_WS('#', `i`, `y`, `t`, CONCAT(ISNULL(`t`)))) AS UNSIGNED)), 10, 16)), 0) AS crc FROM `issue_519`.`t` FORCE INDEX (`PRIMARY`) WHERE (`i` = 0) -issue_519 t `i` = 0 -issue_519 t `i` > 0 AND `i` < '4' -issue_519 t `i` >= '4' AND `i` < '7' -issue_519 t `i` >= '7' AND `i` < '10' -issue_519 t `i` >= '10' +my $default_output = "-- +-- issue_519.t +-- + +REPLACE INTO `percona`.`checksums` (db, tbl, chunk, chunk_index, lower_boundary, upper_boundary, this_cnt, this_crc) SELECT ?, ?, ?, ?, ?, ?, COUNT(*) AS cnt, COALESCE(LOWER(CONV(BIT_XOR(CAST(CRC32(CONCAT_WS('#', `i`, `y`, `t`, CONCAT(ISNULL(`t`)))) AS UNSIGNED)), 10, 16)), 0) AS crc FROM `issue_519`.`t` FORCE INDEX(`PRIMARY`) WHERE ((`i` >= ?)) AND ((`i` <= ?)) ORDER BY `i` /*checksum chunk*/ + +SELECT /*!40001 SQL_NO_CACHE */ `i` FROM `issue_519`.`t` FORCE INDEX(`PRIMARY`) WHERE ((`i` >= ?)) ORDER BY `i` LIMIT ?, 2 /*next chunk boundary*/ + +1 1 3 +2 4 6 +3 7 9 +4 10 11 + "; $output = output( @@ -67,12 +77,19 @@ $output = output( is( $output, -"issue_519 t SELECT /*issue_519.t:1/5*/ 0 AS chunk_num, COUNT(*) AS cnt, COALESCE(LOWER(CONV(BIT_XOR(CAST(CRC32(CONCAT_WS('#', `i`, `y`, `t`, CONCAT(ISNULL(`t`)))) AS UNSIGNED)), 10, 16)), 0) AS crc FROM `issue_519`.`t` FORCE INDEX (`myidx`) WHERE (`i` = 0) -issue_519 t `i` = 0 -issue_519 t `i` > 0 AND `i` < '4' -issue_519 t `i` >= '4' AND `i` < '7' -issue_519 t `i` >= '7' AND `i` < '10' -issue_519 t `i` >= '10' +"-- +-- issue_519.t +-- + +REPLACE INTO `percona`.`checksums` (db, tbl, chunk, chunk_index, lower_boundary, upper_boundary, this_cnt, this_crc) SELECT ?, ?, ?, ?, ?, ?, COUNT(*) AS cnt, COALESCE(LOWER(CONV(BIT_XOR(CAST(CRC32(CONCAT_WS('#', `i`, `y`, `t`, CONCAT(ISNULL(`t`)))) AS UNSIGNED)), 10, 16)), 0) AS crc FROM `issue_519`.`t` FORCE INDEX(`myidx`) WHERE ((`i` > ?) OR (`i` = ? AND `y` >= ?)) AND ((`i` < ?) OR (`i` = ? AND `y` <= ?)) ORDER BY `i`, `y` /*checksum chunk*/ + +SELECT /*!40001 SQL_NO_CACHE */ `i`, `i`, `y` FROM `issue_519`.`t` FORCE INDEX(`myidx`) WHERE ((`i` > ?) OR (`i` = ? AND `y` >= ?)) ORDER BY `i`, `y` LIMIT ?, 2 /*next chunk boundary*/ + +1 1,1,2000 3,3,2002 +2 4,4,2003 6,6,2005 +3 7,7,2006 9,9,2008 +4 10,10,2009 11,11,2010 + ", "Use --chunk-index" ); @@ -81,37 +98,27 @@ $output = output( sub { pt_table_checksum::main(@args, qw(--chunk-index y)) }, ); +# XXX I'm not sure what this tests thinks it's testing because index y +# is a single column index, so there's really not "left-most". is( $output, -"issue_519 t SELECT /*issue_519.t:1/5*/ 0 AS chunk_num, COUNT(*) AS cnt, COALESCE(LOWER(CONV(BIT_XOR(CAST(CRC32(CONCAT_WS('#', `i`, `y`, `t`, CONCAT(ISNULL(`t`)))) AS UNSIGNED)), 10, 16)), 0) AS crc FROM `issue_519`.`t` FORCE INDEX (`y`) WHERE (`y` = 0) -issue_519 t `y` = 0 -issue_519 t `y` > 0 AND `y` < '2003' -issue_519 t `y` >= '2003' AND `y` < '2006' -issue_519 t `y` >= '2006' AND `y` < '2009' -issue_519 t `y` >= '2009' +"-- +-- issue_519.t +-- + +REPLACE INTO `percona`.`checksums` (db, tbl, chunk, chunk_index, lower_boundary, upper_boundary, this_cnt, this_crc) SELECT ?, ?, ?, ?, ?, ?, COUNT(*) AS cnt, COALESCE(LOWER(CONV(BIT_XOR(CAST(CRC32(CONCAT_WS('#', `i`, `y`, `t`, CONCAT(ISNULL(`t`)))) AS UNSIGNED)), 10, 16)), 0) AS crc FROM `issue_519`.`t` FORCE INDEX(`y`) WHERE ((`y` >= ?)) AND ((`y` <= ?)) ORDER BY `y` /*checksum chunk*/ + +SELECT /*!40001 SQL_NO_CACHE */ `y` FROM `issue_519`.`t` FORCE INDEX(`y`) WHERE ((`y` >= ?)) ORDER BY `y` LIMIT ?, 2 /*next chunk boundary*/ + +1 2000 2002 +2 2003 2005 +3 2006 2008 +4 2009 2010 + ", "Chunks on left-most --chunk-index column" ); -# Disabling the index hint with --no-use-index should not affect the -# chunks. It should only remove the FORCE INDEX clause from the SQL. -$output = output( - sub { pt_table_checksum::main(@args, qw(--chunk-index y --no-use-index)) }, -); - -is( - $output, -"issue_519 t SELECT /*issue_519.t:1/5*/ 0 AS chunk_num, COUNT(*) AS cnt, COALESCE(LOWER(CONV(BIT_XOR(CAST(CRC32(CONCAT_WS('#', `i`, `y`, `t`, CONCAT(ISNULL(`t`)))) AS UNSIGNED)), 10, 16)), 0) AS crc FROM `issue_519`.`t` WHERE (`y` = 0) -issue_519 t `y` = 0 -issue_519 t `y` > 0 AND `y` < '2003' -issue_519 t `y` >= '2003' AND `y` < '2006' -issue_519 t `y` >= '2006' AND `y` < '2009' -issue_519 t `y` >= '2009' -", - "No index hint with --no-use-index" -); - - # ############################################################################# # Issue 378: Make mk-table-checksum try to use the index preferred by the # optimizer @@ -127,12 +134,43 @@ $output = output( is( $output, -"issue_519 t SELECT /*issue_519.t:1/1*/ 0 AS chunk_num, COUNT(*) AS cnt, COALESCE(LOWER(CONV(BIT_XOR(CAST(CRC32(CONCAT_WS('#', `i`, `y`, `t`, CONCAT(ISNULL(`t`)))) AS UNSIGNED)), 10, 16)), 0) AS crc FROM `issue_519`.`t` FORCE INDEX (`y`) WHERE (1=1) AND ((y > 2009)) -issue_519 t 1=1 +"-- +-- issue_519.t +-- + +REPLACE INTO `percona`.`checksums` (db, tbl, chunk, chunk_index, lower_boundary, upper_boundary, this_cnt, this_crc) SELECT ?, ?, ?, ?, ?, ?, COUNT(*) AS cnt, COALESCE(LOWER(CONV(BIT_XOR(CAST(CRC32(CONCAT_WS('#', `i`, `y`, `t`, CONCAT(ISNULL(`t`)))) AS UNSIGNED)), 10, 16)), 0) AS crc FROM `issue_519`.`t` FORCE INDEX(`y`) WHERE ((`y` >= ?)) AND ((`y` <= ?)) AND (y > 2009) ORDER BY `y` /*checksum chunk*/ + +SELECT /*!40001 SQL_NO_CACHE */ `y` FROM `issue_519`.`t` FORCE INDEX(`y`) WHERE ((`y` >= ?)) AND (y > 2009) ORDER BY `y` LIMIT ?, 2 /*next chunk boundary*/ + +1 2010 2010 + ", "Auto-chosen --chunk-index for --where (issue 378)" ); +# If user specifies --chunk-index, then ignore the index MySQL wants to +# use (y in this case) and use the user's index. +$output = output( + sub { pt_table_checksum::main(@args, qw(--chunk-index PRIMARY), + "--where", "y > 2009") }, +); + +is( + $output, +"-- +-- issue_519.t +-- + +REPLACE INTO `percona`.`checksums` (db, tbl, chunk, chunk_index, lower_boundary, upper_boundary, this_cnt, this_crc) SELECT ?, ?, ?, ?, ?, ?, COUNT(*) AS cnt, COALESCE(LOWER(CONV(BIT_XOR(CAST(CRC32(CONCAT_WS('#', `i`, `y`, `t`, CONCAT(ISNULL(`t`)))) AS UNSIGNED)), 10, 16)), 0) AS crc FROM `issue_519`.`t` FORCE INDEX(`PRIMARY`) WHERE ((`i` >= ?)) AND ((`i` <= ?)) AND (y > 2009) ORDER BY `i` /*checksum chunk*/ + +SELECT /*!40001 SQL_NO_CACHE */ `i` FROM `issue_519`.`t` FORCE INDEX(`PRIMARY`) WHERE ((`i` >= ?)) AND (y > 2009) ORDER BY `i` LIMIT ?, 2 /*next chunk boundary*/ + +1 11 11 + +", + "Explicit --chunk-index overrides MySQL's index for --where" +); + # ############################################################################# # Done. # #############################################################################