mirror of
https://github.com/percona/percona-toolkit.git
synced 2026-03-01 02:00:46 +08:00
Merge detect-key-len-with-range-scan.
This commit is contained in:
@@ -5127,6 +5127,158 @@ sub _d {
|
||||
# End CleanupTask package
|
||||
# ###########################################################################
|
||||
|
||||
# ###########################################################################
|
||||
# IndexLength package
|
||||
# This package is a copy without comments from the original. The original
|
||||
# with comments and its test file can be found in the Bazaar repository at,
|
||||
# lib/IndexLength.pm
|
||||
# t/lib/IndexLength.t
|
||||
# See https://launchpad.net/percona-toolkit for more information.
|
||||
# ###########################################################################
|
||||
{
|
||||
|
||||
package IndexLength;
|
||||
|
||||
use strict;
|
||||
use warnings FATAL => 'all';
|
||||
use English qw(-no_match_vars);
|
||||
use constant PTDEBUG => $ENV{PTDEBUG} || 0;
|
||||
|
||||
use Data::Dumper;
|
||||
$Data::Dumper::Indent = 1;
|
||||
$Data::Dumper::Sortkeys = 1;
|
||||
$Data::Dumper::Quotekeys = 0;
|
||||
|
||||
sub new {
|
||||
my ( $class, %args ) = @_;
|
||||
my @required_args = qw(Quoter);
|
||||
foreach my $arg ( @required_args ) {
|
||||
die "I need a $arg argument" unless $args{$arg};
|
||||
}
|
||||
|
||||
my $self = {
|
||||
Quoter => $args{Quoter},
|
||||
};
|
||||
|
||||
return bless $self, $class;
|
||||
}
|
||||
|
||||
sub index_length {
|
||||
my ($self, %args) = @_;
|
||||
my @required_args = qw(Cxn tbl index);
|
||||
foreach my $arg ( @required_args ) {
|
||||
die "I need a $arg argument" unless $args{$arg};
|
||||
}
|
||||
my ($cxn) = @args{@required_args};
|
||||
|
||||
die "The tbl argument does not have a tbl_struct"
|
||||
unless exists $args{tbl}->{tbl_struct};
|
||||
die "Index $args{index} does not exist in table $args{tbl}->{name}"
|
||||
unless $args{tbl}->{tbl_struct}->{keys}->{$args{index}};
|
||||
|
||||
my $index_struct = $args{tbl}->{tbl_struct}->{keys}->{$args{index}};
|
||||
my $index_cols = $index_struct->{cols};
|
||||
my $n_index_cols = $args{n_index_cols};
|
||||
if ( !$n_index_cols || $n_index_cols > @$index_cols ) {
|
||||
$n_index_cols = scalar @$index_cols;
|
||||
}
|
||||
|
||||
my $vals = $self->_get_first_values(
|
||||
%args,
|
||||
n_index_cols => $n_index_cols,
|
||||
);
|
||||
|
||||
my $sql = $self->_make_range_query(
|
||||
%args,
|
||||
n_index_cols => $n_index_cols,
|
||||
vals => $vals,
|
||||
);
|
||||
my $sth = $cxn->dbh()->prepare($sql);
|
||||
PTDEBUG && _d($sth->{Statement}, 'params:', @$vals);
|
||||
$sth->execute(@$vals);
|
||||
my $row = $sth->fetchrow_hashref();
|
||||
$sth->finish();
|
||||
PTDEBUG && _d('Range scan:', Dumper($row));
|
||||
return $row->{key_len}, $row->{key};
|
||||
}
|
||||
|
||||
sub _get_first_values {
|
||||
my ($self, %args) = @_;
|
||||
my @required_args = qw(Cxn tbl index n_index_cols);
|
||||
foreach my $arg ( @required_args ) {
|
||||
die "I need a $arg argument" unless $args{$arg};
|
||||
}
|
||||
my ($cxn, $tbl, $index, $n_index_cols) = @args{@required_args};
|
||||
|
||||
my $q = $self->{Quoter};
|
||||
|
||||
my $index_struct = $tbl->{tbl_struct}->{keys}->{$index};
|
||||
my $index_cols = $index_struct->{cols};
|
||||
my $index_columns = join (', ',
|
||||
map { $q->quote($_) } @{$index_cols}[0..($n_index_cols - 1)]);
|
||||
|
||||
my @where;
|
||||
foreach my $col ( @{$index_cols}[0..($n_index_cols - 1)] ) {
|
||||
push @where, $q->quote($col) . " IS NOT NULL"
|
||||
}
|
||||
|
||||
my $sql = "SELECT /*!40001 SQL_NO_CACHE */ $index_columns "
|
||||
. "FROM $tbl->{name} FORCE INDEX (" . $q->quote($index) . ") "
|
||||
. "WHERE " . join(' AND ', @where)
|
||||
. " ORDER BY $index_columns "
|
||||
. "LIMIT 1 /*key_len*/"; # only need 1 row
|
||||
PTDEBUG && _d($sql);
|
||||
my $vals = $cxn->dbh()->selectrow_arrayref($sql);
|
||||
return $vals;
|
||||
}
|
||||
|
||||
sub _make_range_query {
|
||||
my ($self, %args) = @_;
|
||||
my @required_args = qw(tbl index n_index_cols vals);
|
||||
foreach my $arg ( @required_args ) {
|
||||
die "I need a $arg argument" unless $args{$arg};
|
||||
}
|
||||
my ($tbl, $index, $n_index_cols, $vals) = @args{@required_args};
|
||||
|
||||
my $q = $self->{Quoter};
|
||||
|
||||
my $index_struct = $tbl->{tbl_struct}->{keys}->{$index};
|
||||
my $index_cols = $index_struct->{cols};
|
||||
|
||||
my @where;
|
||||
if ( $n_index_cols > 1 ) {
|
||||
foreach my $n ( 0..($n_index_cols - 2) ) {
|
||||
my $col = $index_cols->[$n];
|
||||
my $val = $vals->[$n];
|
||||
push @where, $q->quote($col) . " = ?";
|
||||
}
|
||||
}
|
||||
|
||||
my $col = $index_cols->[$n_index_cols - 1];
|
||||
my $val = $vals->[-1]; # should only be as many vals as cols
|
||||
push @where, $q->quote($col) . " >= ?";
|
||||
|
||||
my $sql = "EXPLAIN SELECT /*!40001 SQL_NO_CACHE */ * "
|
||||
. "FROM $tbl->{name} FORCE INDEX (" . $q->quote($index) . ") "
|
||||
. "WHERE " . join(' AND ', @where)
|
||||
. " /*key_len*/";
|
||||
return $sql;
|
||||
}
|
||||
|
||||
sub _d {
|
||||
my ($package, undef, $line) = caller 0;
|
||||
@_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; }
|
||||
map { defined $_ ? $_ : 'undef' }
|
||||
@_;
|
||||
print STDERR "# $package:$line $PID ", join(' ', @_), "\n";
|
||||
}
|
||||
|
||||
1;
|
||||
}
|
||||
# ###########################################################################
|
||||
# End IndexLength package
|
||||
# ###########################################################################
|
||||
|
||||
# ###########################################################################
|
||||
# This is a combination of modules and programs in one -- a runnable module.
|
||||
# http://www.perl.com/pub/a/2006/07/13/lightning-articles.html?page=last
|
||||
@@ -5908,30 +6060,30 @@ sub main {
|
||||
}
|
||||
else { # chunking the table
|
||||
if ( $o->get('check-plan') ) {
|
||||
my $expl = explain_statement(
|
||||
sth => $statements->{explain_first_lower_boundary},
|
||||
tbl => $tbl,
|
||||
vals => [],
|
||||
my $idx_len = new IndexLength(Quoter => $q);
|
||||
my ($key_len, $key) = $idx_len->index_length(
|
||||
Cxn => $args{Cxn},
|
||||
tbl => $tbl,
|
||||
index => $nibble_iter->nibble_index(),
|
||||
n_index_cols => $o->get('chunk-index-columns'),
|
||||
);
|
||||
if ( !$expl->{key}
|
||||
|| lc($expl->{key}) ne lc($nibble_iter->nibble_index()) )
|
||||
{
|
||||
if ( !$key || lc($key) ne lc($nibble_iter->nibble_index()) ) {
|
||||
die "Cannot determine the key_len of the chunk index "
|
||||
. "because MySQL chose "
|
||||
. ($expl->{key} ? "the $expl->{key}" : "no") . " index "
|
||||
. ($key ? "the $key" : "no") . " index "
|
||||
. "instead of the " . $nibble_iter->nibble_index()
|
||||
. " index for the first lower boundary statement. "
|
||||
. "See --[no]check-plan in the documentation for more "
|
||||
. "information.";
|
||||
}
|
||||
elsif ( !$expl->{key_len} ) {
|
||||
die "The key_len of the $expl->{key} index is "
|
||||
. (defined $expl->{key_len} ? "zero" : "NULL")
|
||||
elsif ( !$key_len ) {
|
||||
die "The key_len of the $key index is "
|
||||
. (defined $key_len ? "zero" : "NULL")
|
||||
. ", but this should not be possible. "
|
||||
. "See --[no]check-plan in the documentation for more "
|
||||
. "information.";
|
||||
}
|
||||
$tbl->{key_len} = $expl->{key_len};
|
||||
$tbl->{key_len} = $key_len;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6080,6 +6080,158 @@ sub _d {
|
||||
# End WeightedAvgRate package
|
||||
# ###########################################################################
|
||||
|
||||
# ###########################################################################
|
||||
# IndexLength package
|
||||
# This package is a copy without comments from the original. The original
|
||||
# with comments and its test file can be found in the Bazaar repository at,
|
||||
# lib/IndexLength.pm
|
||||
# t/lib/IndexLength.t
|
||||
# See https://launchpad.net/percona-toolkit for more information.
|
||||
# ###########################################################################
|
||||
{
|
||||
|
||||
package IndexLength;
|
||||
|
||||
use strict;
|
||||
use warnings FATAL => 'all';
|
||||
use English qw(-no_match_vars);
|
||||
use constant PTDEBUG => $ENV{PTDEBUG} || 0;
|
||||
|
||||
use Data::Dumper;
|
||||
$Data::Dumper::Indent = 1;
|
||||
$Data::Dumper::Sortkeys = 1;
|
||||
$Data::Dumper::Quotekeys = 0;
|
||||
|
||||
sub new {
|
||||
my ( $class, %args ) = @_;
|
||||
my @required_args = qw(Quoter);
|
||||
foreach my $arg ( @required_args ) {
|
||||
die "I need a $arg argument" unless $args{$arg};
|
||||
}
|
||||
|
||||
my $self = {
|
||||
Quoter => $args{Quoter},
|
||||
};
|
||||
|
||||
return bless $self, $class;
|
||||
}
|
||||
|
||||
sub index_length {
|
||||
my ($self, %args) = @_;
|
||||
my @required_args = qw(Cxn tbl index);
|
||||
foreach my $arg ( @required_args ) {
|
||||
die "I need a $arg argument" unless $args{$arg};
|
||||
}
|
||||
my ($cxn) = @args{@required_args};
|
||||
|
||||
die "The tbl argument does not have a tbl_struct"
|
||||
unless exists $args{tbl}->{tbl_struct};
|
||||
die "Index $args{index} does not exist in table $args{tbl}->{name}"
|
||||
unless $args{tbl}->{tbl_struct}->{keys}->{$args{index}};
|
||||
|
||||
my $index_struct = $args{tbl}->{tbl_struct}->{keys}->{$args{index}};
|
||||
my $index_cols = $index_struct->{cols};
|
||||
my $n_index_cols = $args{n_index_cols};
|
||||
if ( !$n_index_cols || $n_index_cols > @$index_cols ) {
|
||||
$n_index_cols = scalar @$index_cols;
|
||||
}
|
||||
|
||||
my $vals = $self->_get_first_values(
|
||||
%args,
|
||||
n_index_cols => $n_index_cols,
|
||||
);
|
||||
|
||||
my $sql = $self->_make_range_query(
|
||||
%args,
|
||||
n_index_cols => $n_index_cols,
|
||||
vals => $vals,
|
||||
);
|
||||
my $sth = $cxn->dbh()->prepare($sql);
|
||||
PTDEBUG && _d($sth->{Statement}, 'params:', @$vals);
|
||||
$sth->execute(@$vals);
|
||||
my $row = $sth->fetchrow_hashref();
|
||||
$sth->finish();
|
||||
PTDEBUG && _d('Range scan:', Dumper($row));
|
||||
return $row->{key_len}, $row->{key};
|
||||
}
|
||||
|
||||
sub _get_first_values {
|
||||
my ($self, %args) = @_;
|
||||
my @required_args = qw(Cxn tbl index n_index_cols);
|
||||
foreach my $arg ( @required_args ) {
|
||||
die "I need a $arg argument" unless $args{$arg};
|
||||
}
|
||||
my ($cxn, $tbl, $index, $n_index_cols) = @args{@required_args};
|
||||
|
||||
my $q = $self->{Quoter};
|
||||
|
||||
my $index_struct = $tbl->{tbl_struct}->{keys}->{$index};
|
||||
my $index_cols = $index_struct->{cols};
|
||||
my $index_columns = join (', ',
|
||||
map { $q->quote($_) } @{$index_cols}[0..($n_index_cols - 1)]);
|
||||
|
||||
my @where;
|
||||
foreach my $col ( @{$index_cols}[0..($n_index_cols - 1)] ) {
|
||||
push @where, $q->quote($col) . " IS NOT NULL"
|
||||
}
|
||||
|
||||
my $sql = "SELECT /*!40001 SQL_NO_CACHE */ $index_columns "
|
||||
. "FROM $tbl->{name} FORCE INDEX (" . $q->quote($index) . ") "
|
||||
. "WHERE " . join(' AND ', @where)
|
||||
. " ORDER BY $index_columns "
|
||||
. "LIMIT 1 /*key_len*/"; # only need 1 row
|
||||
PTDEBUG && _d($sql);
|
||||
my $vals = $cxn->dbh()->selectrow_arrayref($sql);
|
||||
return $vals;
|
||||
}
|
||||
|
||||
sub _make_range_query {
|
||||
my ($self, %args) = @_;
|
||||
my @required_args = qw(tbl index n_index_cols vals);
|
||||
foreach my $arg ( @required_args ) {
|
||||
die "I need a $arg argument" unless $args{$arg};
|
||||
}
|
||||
my ($tbl, $index, $n_index_cols, $vals) = @args{@required_args};
|
||||
|
||||
my $q = $self->{Quoter};
|
||||
|
||||
my $index_struct = $tbl->{tbl_struct}->{keys}->{$index};
|
||||
my $index_cols = $index_struct->{cols};
|
||||
|
||||
my @where;
|
||||
if ( $n_index_cols > 1 ) {
|
||||
foreach my $n ( 0..($n_index_cols - 2) ) {
|
||||
my $col = $index_cols->[$n];
|
||||
my $val = $vals->[$n];
|
||||
push @where, $q->quote($col) . " = ?";
|
||||
}
|
||||
}
|
||||
|
||||
my $col = $index_cols->[$n_index_cols - 1];
|
||||
my $val = $vals->[-1]; # should only be as many vals as cols
|
||||
push @where, $q->quote($col) . " >= ?";
|
||||
|
||||
my $sql = "EXPLAIN SELECT /*!40001 SQL_NO_CACHE */ * "
|
||||
. "FROM $tbl->{name} FORCE INDEX (" . $q->quote($index) . ") "
|
||||
. "WHERE " . join(' AND ', @where)
|
||||
. " /*key_len*/";
|
||||
return $sql;
|
||||
}
|
||||
|
||||
sub _d {
|
||||
my ($package, undef, $line) = caller 0;
|
||||
@_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; }
|
||||
map { defined $_ ? $_ : 'undef' }
|
||||
@_;
|
||||
print STDERR "# $package:$line $PID ", join(' ', @_), "\n";
|
||||
}
|
||||
|
||||
1;
|
||||
}
|
||||
# ###########################################################################
|
||||
# End IndexLength package
|
||||
# ###########################################################################
|
||||
|
||||
# ###########################################################################
|
||||
# This is a combination of modules and programs in one -- a runnable module.
|
||||
# http://www.perl.com/pub/a/2006/07/13/lightning-articles.html?page=last
|
||||
@@ -6748,30 +6900,30 @@ sub main {
|
||||
}
|
||||
else { # chunking the table
|
||||
if ( $o->get('check-plan') ) {
|
||||
my $expl = explain_statement(
|
||||
sth => $statements->{explain_first_lower_boundary},
|
||||
tbl => $tbl,
|
||||
vals => [],
|
||||
my $idx_len = new IndexLength(Quoter => $q);
|
||||
my ($key_len, $key) = $idx_len->index_length(
|
||||
Cxn => $args{Cxn},
|
||||
tbl => $tbl,
|
||||
index => $nibble_iter->nibble_index(),
|
||||
n_index_cols => $o->get('chunk-index-columns'),
|
||||
);
|
||||
if ( !$expl->{key}
|
||||
|| lc($expl->{key}) ne lc($nibble_iter->nibble_index()) )
|
||||
{
|
||||
if ( !$key || lc($key) ne lc($nibble_iter->nibble_index()) ) {
|
||||
die "Cannot determine the key_len of the chunk index "
|
||||
. "because MySQL chose "
|
||||
. ($expl->{key} ? "the $expl->{key}" : "no") . " index "
|
||||
. ($key ? "the $key" : "no") . " index "
|
||||
. "instead of the " . $nibble_iter->nibble_index()
|
||||
. " index for the first lower boundary statement. "
|
||||
. "See --[no]check-plan in the documentation for more "
|
||||
. "information.";
|
||||
}
|
||||
elsif ( !$expl->{key_len} ) {
|
||||
die "The key_len of the $expl->{key} index is "
|
||||
. (defined $expl->{key_len} ? "zero" : "NULL")
|
||||
elsif ( !$key_len ) {
|
||||
die "The key_len of the $key index is "
|
||||
. (defined $key_len ? "zero" : "NULL")
|
||||
. ", but this should not be possible. "
|
||||
. "See --[no]check-plan in the documentation for more "
|
||||
. "information.";
|
||||
}
|
||||
$tbl->{key_len} = $expl->{key_len};
|
||||
$tbl->{key_len} = $key_len;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
175
lib/IndexLength.pm
Normal file
175
lib/IndexLength.pm
Normal file
@@ -0,0 +1,175 @@
|
||||
# This program is copyright 2012 Percona Inc.
|
||||
# Feedback and improvements are welcome.
|
||||
#
|
||||
# THIS PROGRAM IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
|
||||
# WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
|
||||
# MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify it under
|
||||
# the terms of the GNU General Public License as published by the Free Software
|
||||
# Foundation, version 2; OR the Perl Artistic License. On UNIX and similar
|
||||
# systems, you can issue `man perlgpl' or `man perlartistic' to read these
|
||||
# licenses.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along with
|
||||
# this program; if not, write to the Free Software Foundation, Inc., 59 Temple
|
||||
# Place, Suite 330, Boston, MA 02111-1307 USA.
|
||||
# ###########################################################################
|
||||
# IndexLength package
|
||||
# ###########################################################################
|
||||
{
|
||||
# Package: IndexLength
|
||||
# IndexLength get the key_len of a index.
|
||||
|
||||
package IndexLength;
|
||||
|
||||
use strict;
|
||||
use warnings FATAL => 'all';
|
||||
use English qw(-no_match_vars);
|
||||
use constant PTDEBUG => $ENV{PTDEBUG} || 0;
|
||||
|
||||
use Data::Dumper;
|
||||
$Data::Dumper::Indent = 1;
|
||||
$Data::Dumper::Sortkeys = 1;
|
||||
$Data::Dumper::Quotekeys = 0;
|
||||
|
||||
sub new {
|
||||
my ( $class, %args ) = @_;
|
||||
my @required_args = qw(Quoter);
|
||||
foreach my $arg ( @required_args ) {
|
||||
die "I need a $arg argument" unless $args{$arg};
|
||||
}
|
||||
|
||||
my $self = {
|
||||
Quoter => $args{Quoter},
|
||||
};
|
||||
|
||||
return bless $self, $class;
|
||||
}
|
||||
|
||||
# Returns the length of the index in bytes using only
|
||||
# the first N left-most columns of the index.
|
||||
sub index_length {
|
||||
my ($self, %args) = @_;
|
||||
my @required_args = qw(Cxn tbl index);
|
||||
foreach my $arg ( @required_args ) {
|
||||
die "I need a $arg argument" unless $args{$arg};
|
||||
}
|
||||
my ($cxn) = @args{@required_args};
|
||||
|
||||
die "The tbl argument does not have a tbl_struct"
|
||||
unless exists $args{tbl}->{tbl_struct};
|
||||
die "Index $args{index} does not exist in table $args{tbl}->{name}"
|
||||
unless $args{tbl}->{tbl_struct}->{keys}->{$args{index}};
|
||||
|
||||
my $index_struct = $args{tbl}->{tbl_struct}->{keys}->{$args{index}};
|
||||
my $index_cols = $index_struct->{cols};
|
||||
my $n_index_cols = $args{n_index_cols};
|
||||
if ( !$n_index_cols || $n_index_cols > @$index_cols ) {
|
||||
$n_index_cols = scalar @$index_cols;
|
||||
}
|
||||
|
||||
# Get the first row with non-NULL values.
|
||||
my $vals = $self->_get_first_values(
|
||||
%args,
|
||||
n_index_cols => $n_index_cols,
|
||||
);
|
||||
|
||||
# Make an EXPLAIN query to scan the range and execute it.
|
||||
my $sql = $self->_make_range_query(
|
||||
%args,
|
||||
n_index_cols => $n_index_cols,
|
||||
vals => $vals,
|
||||
);
|
||||
my $sth = $cxn->dbh()->prepare($sql);
|
||||
PTDEBUG && _d($sth->{Statement}, 'params:', @$vals);
|
||||
$sth->execute(@$vals);
|
||||
my $row = $sth->fetchrow_hashref();
|
||||
$sth->finish();
|
||||
PTDEBUG && _d('Range scan:', Dumper($row));
|
||||
return $row->{key_len}, $row->{key};
|
||||
}
|
||||
|
||||
sub _get_first_values {
|
||||
my ($self, %args) = @_;
|
||||
my @required_args = qw(Cxn tbl index n_index_cols);
|
||||
foreach my $arg ( @required_args ) {
|
||||
die "I need a $arg argument" unless $args{$arg};
|
||||
}
|
||||
my ($cxn, $tbl, $index, $n_index_cols) = @args{@required_args};
|
||||
|
||||
my $q = $self->{Quoter};
|
||||
|
||||
# Select just the index columns.
|
||||
my $index_struct = $tbl->{tbl_struct}->{keys}->{$index};
|
||||
my $index_cols = $index_struct->{cols};
|
||||
my $index_columns = join (', ',
|
||||
map { $q->quote($_) } @{$index_cols}[0..($n_index_cols - 1)]);
|
||||
|
||||
# Where no index column is null, because we can't > NULL.
|
||||
my @where;
|
||||
foreach my $col ( @{$index_cols}[0..($n_index_cols - 1)] ) {
|
||||
push @where, $q->quote($col) . " IS NOT NULL"
|
||||
}
|
||||
|
||||
my $sql = "SELECT /*!40001 SQL_NO_CACHE */ $index_columns "
|
||||
. "FROM $tbl->{name} FORCE INDEX (" . $q->quote($index) . ") "
|
||||
. "WHERE " . join(' AND ', @where)
|
||||
. " ORDER BY $index_columns "
|
||||
. "LIMIT 1 /*key_len*/"; # only need 1 row
|
||||
PTDEBUG && _d($sql);
|
||||
my $vals = $cxn->dbh()->selectrow_arrayref($sql);
|
||||
return $vals;
|
||||
}
|
||||
|
||||
sub _make_range_query {
|
||||
my ($self, %args) = @_;
|
||||
my @required_args = qw(tbl index n_index_cols vals);
|
||||
foreach my $arg ( @required_args ) {
|
||||
die "I need a $arg argument" unless $args{$arg};
|
||||
}
|
||||
my ($tbl, $index, $n_index_cols, $vals) = @args{@required_args};
|
||||
|
||||
my $q = $self->{Quoter};
|
||||
|
||||
my $index_struct = $tbl->{tbl_struct}->{keys}->{$index};
|
||||
my $index_cols = $index_struct->{cols};
|
||||
|
||||
# All but the last index col = val.
|
||||
my @where;
|
||||
if ( $n_index_cols > 1 ) {
|
||||
# -1 for zero-index array as usual, then -1 again because
|
||||
# we don't want the last column; that's added below.
|
||||
foreach my $n ( 0..($n_index_cols - 2) ) {
|
||||
my $col = $index_cols->[$n];
|
||||
my $val = $vals->[$n];
|
||||
push @where, $q->quote($col) . " = ?";
|
||||
}
|
||||
}
|
||||
|
||||
# The last index col > val. This causes the range scan using just
|
||||
# the N left-most index columns.
|
||||
my $col = $index_cols->[$n_index_cols - 1];
|
||||
my $val = $vals->[-1]; # should only be as many vals as cols
|
||||
push @where, $q->quote($col) . " >= ?";
|
||||
|
||||
my $sql = "EXPLAIN SELECT /*!40001 SQL_NO_CACHE */ * "
|
||||
. "FROM $tbl->{name} FORCE INDEX (" . $q->quote($index) . ") "
|
||||
. "WHERE " . join(' AND ', @where)
|
||||
. " /*key_len*/";
|
||||
return $sql;
|
||||
}
|
||||
|
||||
sub _d {
|
||||
my ($package, undef, $line) = caller 0;
|
||||
@_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; }
|
||||
map { defined $_ ? $_ : 'undef' }
|
||||
@_;
|
||||
print STDERR "# $package:$line $PID ", join(' ', @_), "\n";
|
||||
}
|
||||
|
||||
1;
|
||||
}
|
||||
# ###########################################################################
|
||||
# End IndexLength package
|
||||
# ###########################################################################
|
||||
135
t/lib/IndexLength.pm
Normal file
135
t/lib/IndexLength.pm
Normal file
@@ -0,0 +1,135 @@
|
||||
#!/usr/bin/perl
|
||||
|
||||
BEGIN {
|
||||
die "The PERCONA_TOOLKIT_BRANCH environment variable is not set.\n"
|
||||
unless $ENV{PERCONA_TOOLKIT_BRANCH} && -d $ENV{PERCONA_TOOLKIT_BRANCH};
|
||||
unshift @INC, "$ENV{PERCONA_TOOLKIT_BRANCH}/lib";
|
||||
};
|
||||
|
||||
use strict;
|
||||
use warnings FATAL => 'all';
|
||||
use English qw(-no_match_vars);
|
||||
use Test::More;
|
||||
|
||||
use PerconaTest;
|
||||
use DSNParser;
|
||||
use Sandbox;
|
||||
|
||||
use Cxn;
|
||||
use Quoter;
|
||||
use TableParser;
|
||||
use OptionParser;
|
||||
use IndexLength;
|
||||
|
||||
use constant PTDEBUG => $ENV{PTDEBUG} || 0;
|
||||
use constant PTDEVDEBUG => $ENV{PTDEBUG} || 0;
|
||||
|
||||
use Data::Dumper;
|
||||
$Data::Dumper::Indent = 1;
|
||||
$Data::Dumper::Sortkeys = 1;
|
||||
$Data::Dumper::Quotekeys = 0;
|
||||
|
||||
my $dp = new DSNParser(opts=>$dsn_opts);
|
||||
my $sb = new Sandbox(basedir => '/tmp', DSNParser => $dp);
|
||||
my $dbh = $sb->get_dbh_for('master');
|
||||
|
||||
if ( !$dbh ) {
|
||||
plan skip_all => 'Cannot connect to sandbox master';
|
||||
}
|
||||
else {
|
||||
plan tests => 7;
|
||||
}
|
||||
|
||||
my $output;
|
||||
my $q = new Quoter();
|
||||
my $tp = new TableParser(Quoter => $q);
|
||||
my $il = new IndexLength(Quoter => $q);
|
||||
my $o = new OptionParser(description => 'IndexLength');
|
||||
$o->get_specs("$trunk/bin/pt-table-checksum");
|
||||
my $cxn = new Cxn(
|
||||
dbh => $dbh,
|
||||
dsn => { h=>'127.1', P=>'12345', n=>'h=127.1,P=12345' },
|
||||
DSNParser => $dp,
|
||||
OptionParser => $o,
|
||||
);
|
||||
|
||||
sub test_index_len {
|
||||
my (%args) = @_;
|
||||
my @required_args = qw(name tbl index len);
|
||||
foreach my $arg ( @required_args ) {
|
||||
die "I need a $arg argument" unless $args{$arg};
|
||||
}
|
||||
|
||||
my ($len, $key) = $il->index_length(
|
||||
Cxn => $cxn,
|
||||
tbl => $args{tbl},
|
||||
index => $args{index},
|
||||
n_index_cols => $args{n_index_cols},
|
||||
);
|
||||
|
||||
is(
|
||||
$len,
|
||||
$args{len},
|
||||
"$args{name}"
|
||||
);
|
||||
}
|
||||
|
||||
# #############################################################################
|
||||
# bad_plan, PK with 4 cols
|
||||
# #############################################################################
|
||||
$sb->load_file('master', "t/pt-table-checksum/samples/bad-plan-bug-1010232.sql");
|
||||
my $tbl_struct = $tp->parse(
|
||||
$tp->get_create_table($dbh, 'bad_plan', 't'));
|
||||
my $tbl = {
|
||||
name => $q->quote('bad_plan', 't'),
|
||||
tbl_struct => $tbl_struct,
|
||||
};
|
||||
|
||||
for my $n ( 1..4 ) {
|
||||
my $len = $n * 2 + ($n >= 2 ? 1 : 0);
|
||||
test_index_len(
|
||||
name => "bad_plan.t $n cols = $len bytes",
|
||||
tbl => $tbl,
|
||||
index => "PRIMARY",
|
||||
n_index_cols => $n,
|
||||
len => $len,
|
||||
);
|
||||
}
|
||||
|
||||
# #############################################################################
|
||||
# Some sakila tables
|
||||
# #############################################################################
|
||||
$tbl_struct = $tp->parse(
|
||||
$tp->get_create_table($dbh, 'sakila', 'film_actor'));
|
||||
$tbl = {
|
||||
name => $q->quote('sakila', 'film_actor'),
|
||||
tbl_struct => $tbl_struct,
|
||||
};
|
||||
|
||||
test_index_len(
|
||||
name => "sakila.film_actor 1 col = 2 bytes",
|
||||
tbl => $tbl,
|
||||
index => "PRIMARY",
|
||||
n_index_cols => 1,
|
||||
len => 2,
|
||||
);
|
||||
|
||||
# #############################################################################
|
||||
# Use full index if no n_index_cols
|
||||
# #############################################################################
|
||||
|
||||
# Use sakila.film_actor stuff from previous tests.
|
||||
|
||||
test_index_len(
|
||||
name => "sakila.film_actor all cols = 4 bytes",
|
||||
tbl => $tbl,
|
||||
index => "PRIMARY",
|
||||
len => 4,
|
||||
);
|
||||
|
||||
# #############################################################################
|
||||
# Done.
|
||||
# #############################################################################
|
||||
$sb->wipe_clean($dbh);
|
||||
ok($sb->ok(), "Sandbox servers") or BAIL_OUT(__FILE__ . " broke the sandbox");
|
||||
exit;
|
||||
@@ -25,7 +25,7 @@ if ( !$dbh ) {
|
||||
plan skip_all => 'Cannot connect to sandbox master';
|
||||
}
|
||||
else {
|
||||
plan tests => 16;
|
||||
plan tests => 17;
|
||||
}
|
||||
|
||||
# The sandbox servers run with lock_wait_timeout=3 and it's not dynamic
|
||||
@@ -175,7 +175,7 @@ is(
|
||||
$exit_status,
|
||||
0,
|
||||
"Bad key_len chunks are not errors"
|
||||
);
|
||||
) or diag($output);
|
||||
|
||||
cmp_ok(
|
||||
PerconaTest::count_checksum_results($output, 'skipped'),
|
||||
@@ -205,19 +205,40 @@ $output = output(
|
||||
sub {
|
||||
$exit_status = pt_table_checksum::main(
|
||||
$master_dsn, '--max-load', '',
|
||||
qw(--lock-wait-timeout 3 --chunk-size 5000 -t sakila.rental),
|
||||
qw(--chunk-index rental_date --chunk-index-columns 5),
|
||||
qw(--explain --explain));
|
||||
qw(--lock-wait-timeout 3 --chunk-size 1000 -t sakila.film_actor),
|
||||
qw(--chunk-index PRIMARY --chunk-index-columns 9),
|
||||
);
|
||||
},
|
||||
stderr => 1,
|
||||
);
|
||||
|
||||
is(
|
||||
$exit_status,
|
||||
0,
|
||||
PerconaTest::count_checksum_results($output, 'rows'),
|
||||
5462,
|
||||
"--chunk-index-columns > number of index columns"
|
||||
) or diag($output);
|
||||
|
||||
$output = output(
|
||||
sub {
|
||||
$exit_status = pt_table_checksum::main(
|
||||
$master_dsn, '--max-load', '',
|
||||
qw(--lock-wait-timeout 3 --chunk-size 1000 -t sakila.film_actor),
|
||||
qw(--chunk-index-columns 1 --chunk-size-limit 3),
|
||||
);
|
||||
},
|
||||
stderr => 1,
|
||||
);
|
||||
|
||||
# Since we're not using the full index, it's basically a non-unique index,
|
||||
# so there are dupes. The table really has 5462 rows, so we must get
|
||||
# at least that many, and probably a few more.
|
||||
cmp_ok(
|
||||
PerconaTest::count_checksum_results($output, 'rows'),
|
||||
'>=',
|
||||
5462,
|
||||
"Initial key_len reflects --chunk-index-columns"
|
||||
) or diag($output);
|
||||
|
||||
# #############################################################################
|
||||
# Done.
|
||||
# #############################################################################
|
||||
|
||||
Reference in New Issue
Block a user