Merge lp:~percona-toolkit-dev/percona-toolkit/detect-key-len-with-range-scan into lp:percona-toolkit/2.1
- detect-key-len-with-range-scan
- Merge into 2.1
Proposed by
Daniel Nichter
Status: | Merged |
---|---|
Merged at revision: | 284 |
Proposed branch: | lp:~percona-toolkit-dev/percona-toolkit/detect-key-len-with-range-scan |
Merge into: | lp:percona-toolkit/2.1 |
Diff against target: |
799 lines (+666/-31) 5 files modified
bin/pt-online-schema-change (+164/-12) bin/pt-table-checksum (+164/-12) lib/IndexLength.pm (+175/-0) t/lib/IndexLength.pm (+135/-0) t/pt-table-checksum/chunk_index.t (+28/-7) |
To merge this branch: | bzr merge lp:~percona-toolkit-dev/percona-toolkit/detect-key-len-with-range-scan |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Daniel Nichter | Approve | ||
Review via email: mp+109679@code.launchpad.net |
Commit message
Description of the change
To post a comment you must log in.
Revision history for this message
Daniel Nichter (daniel-nichter) : | # |
review:
Approve
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'bin/pt-online-schema-change' |
2 | --- bin/pt-online-schema-change 2012-06-11 12:15:48 +0000 |
3 | +++ bin/pt-online-schema-change 2012-06-11 16:59:23 +0000 |
4 | @@ -5128,6 +5128,158 @@ |
5 | # ########################################################################### |
6 | |
7 | # ########################################################################### |
8 | +# IndexLength package |
9 | +# This package is a copy without comments from the original. The original |
10 | +# with comments and its test file can be found in the Bazaar repository at, |
11 | +# lib/IndexLength.pm |
12 | +# t/lib/IndexLength.t |
13 | +# See https://launchpad.net/percona-toolkit for more information. |
14 | +# ########################################################################### |
15 | +{ |
16 | + |
17 | +package IndexLength; |
18 | + |
19 | +use strict; |
20 | +use warnings FATAL => 'all'; |
21 | +use English qw(-no_match_vars); |
22 | +use constant PTDEBUG => $ENV{PTDEBUG} || 0; |
23 | + |
24 | +use Data::Dumper; |
25 | +$Data::Dumper::Indent = 1; |
26 | +$Data::Dumper::Sortkeys = 1; |
27 | +$Data::Dumper::Quotekeys = 0; |
28 | + |
29 | +sub new { |
30 | + my ( $class, %args ) = @_; |
31 | + my @required_args = qw(Quoter); |
32 | + foreach my $arg ( @required_args ) { |
33 | + die "I need a $arg argument" unless $args{$arg}; |
34 | + } |
35 | + |
36 | + my $self = { |
37 | + Quoter => $args{Quoter}, |
38 | + }; |
39 | + |
40 | + return bless $self, $class; |
41 | +} |
42 | + |
43 | +sub index_length { |
44 | + my ($self, %args) = @_; |
45 | + my @required_args = qw(Cxn tbl index); |
46 | + foreach my $arg ( @required_args ) { |
47 | + die "I need a $arg argument" unless $args{$arg}; |
48 | + } |
49 | + my ($cxn) = @args{@required_args}; |
50 | + |
51 | + die "The tbl argument does not have a tbl_struct" |
52 | + unless exists $args{tbl}->{tbl_struct}; |
53 | + die "Index $args{index} does not exist in table $args{tbl}->{name}" |
54 | + unless $args{tbl}->{tbl_struct}->{keys}->{$args{index}}; |
55 | + |
56 | + my $index_struct = $args{tbl}->{tbl_struct}->{keys}->{$args{index}}; |
57 | + my $index_cols = $index_struct->{cols}; |
58 | + my $n_index_cols = $args{n_index_cols}; |
59 | + if ( !$n_index_cols || $n_index_cols > @$index_cols ) { |
60 | + $n_index_cols = scalar @$index_cols; |
61 | + } |
62 | + |
63 | + my $vals = $self->_get_first_values( |
64 | + %args, |
65 | + n_index_cols => $n_index_cols, |
66 | + ); |
67 | + |
68 | + my $sql = $self->_make_range_query( |
69 | + %args, |
70 | + n_index_cols => $n_index_cols, |
71 | + vals => $vals, |
72 | + ); |
73 | + my $sth = $cxn->dbh()->prepare($sql); |
74 | + PTDEBUG && _d($sth->{Statement}, 'params:', @$vals); |
75 | + $sth->execute(@$vals); |
76 | + my $row = $sth->fetchrow_hashref(); |
77 | + $sth->finish(); |
78 | + PTDEBUG && _d('Range scan:', Dumper($row)); |
79 | + return $row->{key_len}, $row->{key}; |
80 | +} |
81 | + |
82 | +sub _get_first_values { |
83 | + my ($self, %args) = @_; |
84 | + my @required_args = qw(Cxn tbl index n_index_cols); |
85 | + foreach my $arg ( @required_args ) { |
86 | + die "I need a $arg argument" unless $args{$arg}; |
87 | + } |
88 | + my ($cxn, $tbl, $index, $n_index_cols) = @args{@required_args}; |
89 | + |
90 | + my $q = $self->{Quoter}; |
91 | + |
92 | + my $index_struct = $tbl->{tbl_struct}->{keys}->{$index}; |
93 | + my $index_cols = $index_struct->{cols}; |
94 | + my $index_columns = join (', ', |
95 | + map { $q->quote($_) } @{$index_cols}[0..($n_index_cols - 1)]); |
96 | + |
97 | + my @where; |
98 | + foreach my $col ( @{$index_cols}[0..($n_index_cols - 1)] ) { |
99 | + push @where, $q->quote($col) . " IS NOT NULL" |
100 | + } |
101 | + |
102 | + my $sql = "SELECT /*!40001 SQL_NO_CACHE */ $index_columns " |
103 | + . "FROM $tbl->{name} FORCE INDEX (" . $q->quote($index) . ") " |
104 | + . "WHERE " . join(' AND ', @where) |
105 | + . " ORDER BY $index_columns " |
106 | + . "LIMIT 1 /*key_len*/"; # only need 1 row |
107 | + PTDEBUG && _d($sql); |
108 | + my $vals = $cxn->dbh()->selectrow_arrayref($sql); |
109 | + return $vals; |
110 | +} |
111 | + |
112 | +sub _make_range_query { |
113 | + my ($self, %args) = @_; |
114 | + my @required_args = qw(tbl index n_index_cols vals); |
115 | + foreach my $arg ( @required_args ) { |
116 | + die "I need a $arg argument" unless $args{$arg}; |
117 | + } |
118 | + my ($tbl, $index, $n_index_cols, $vals) = @args{@required_args}; |
119 | + |
120 | + my $q = $self->{Quoter}; |
121 | + |
122 | + my $index_struct = $tbl->{tbl_struct}->{keys}->{$index}; |
123 | + my $index_cols = $index_struct->{cols}; |
124 | + |
125 | + my @where; |
126 | + if ( $n_index_cols > 1 ) { |
127 | + foreach my $n ( 0..($n_index_cols - 2) ) { |
128 | + my $col = $index_cols->[$n]; |
129 | + my $val = $vals->[$n]; |
130 | + push @where, $q->quote($col) . " = ?"; |
131 | + } |
132 | + } |
133 | + |
134 | + my $col = $index_cols->[$n_index_cols - 1]; |
135 | + my $val = $vals->[-1]; # should only be as many vals as cols |
136 | + push @where, $q->quote($col) . " >= ?"; |
137 | + |
138 | + my $sql = "EXPLAIN SELECT /*!40001 SQL_NO_CACHE */ * " |
139 | + . "FROM $tbl->{name} FORCE INDEX (" . $q->quote($index) . ") " |
140 | + . "WHERE " . join(' AND ', @where) |
141 | + . " /*key_len*/"; |
142 | + return $sql; |
143 | +} |
144 | + |
145 | +sub _d { |
146 | + my ($package, undef, $line) = caller 0; |
147 | + @_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; } |
148 | + map { defined $_ ? $_ : 'undef' } |
149 | + @_; |
150 | + print STDERR "# $package:$line $PID ", join(' ', @_), "\n"; |
151 | +} |
152 | + |
153 | +1; |
154 | +} |
155 | +# ########################################################################### |
156 | +# End IndexLength package |
157 | +# ########################################################################### |
158 | + |
159 | +# ########################################################################### |
160 | # This is a combination of modules and programs in one -- a runnable module. |
161 | # http://www.perl.com/pub/a/2006/07/13/lightning-articles.html?page=last |
162 | # Or, look it up in the Camel book on pages 642 and 643 in the 3rd edition. |
163 | @@ -5908,30 +6060,30 @@ |
164 | } |
165 | else { # chunking the table |
166 | if ( $o->get('check-plan') ) { |
167 | - my $expl = explain_statement( |
168 | - sth => $statements->{explain_first_lower_boundary}, |
169 | - tbl => $tbl, |
170 | - vals => [], |
171 | + my $idx_len = new IndexLength(Quoter => $q); |
172 | + my ($key_len, $key) = $idx_len->index_length( |
173 | + Cxn => $args{Cxn}, |
174 | + tbl => $tbl, |
175 | + index => $nibble_iter->nibble_index(), |
176 | + n_index_cols => $o->get('chunk-index-columns'), |
177 | ); |
178 | - if ( !$expl->{key} |
179 | - || lc($expl->{key}) ne lc($nibble_iter->nibble_index()) ) |
180 | - { |
181 | + if ( !$key || lc($key) ne lc($nibble_iter->nibble_index()) ) { |
182 | die "Cannot determine the key_len of the chunk index " |
183 | . "because MySQL chose " |
184 | - . ($expl->{key} ? "the $expl->{key}" : "no") . " index " |
185 | + . ($key ? "the $key" : "no") . " index " |
186 | . "instead of the " . $nibble_iter->nibble_index() |
187 | . " index for the first lower boundary statement. " |
188 | . "See --[no]check-plan in the documentation for more " |
189 | . "information."; |
190 | } |
191 | - elsif ( !$expl->{key_len} ) { |
192 | - die "The key_len of the $expl->{key} index is " |
193 | - . (defined $expl->{key_len} ? "zero" : "NULL") |
194 | + elsif ( !$key_len ) { |
195 | + die "The key_len of the $key index is " |
196 | + . (defined $key_len ? "zero" : "NULL") |
197 | . ", but this should not be possible. " |
198 | . "See --[no]check-plan in the documentation for more " |
199 | . "information."; |
200 | } |
201 | - $tbl->{key_len} = $expl->{key_len}; |
202 | + $tbl->{key_len} = $key_len; |
203 | } |
204 | } |
205 | |
206 | |
207 | === modified file 'bin/pt-table-checksum' |
208 | --- bin/pt-table-checksum 2012-06-11 12:15:48 +0000 |
209 | +++ bin/pt-table-checksum 2012-06-11 16:59:23 +0000 |
210 | @@ -6081,6 +6081,158 @@ |
211 | # ########################################################################### |
212 | |
213 | # ########################################################################### |
214 | +# IndexLength package |
215 | +# This package is a copy without comments from the original. The original |
216 | +# with comments and its test file can be found in the Bazaar repository at, |
217 | +# lib/IndexLength.pm |
218 | +# t/lib/IndexLength.t |
219 | +# See https://launchpad.net/percona-toolkit for more information. |
220 | +# ########################################################################### |
221 | +{ |
222 | + |
223 | +package IndexLength; |
224 | + |
225 | +use strict; |
226 | +use warnings FATAL => 'all'; |
227 | +use English qw(-no_match_vars); |
228 | +use constant PTDEBUG => $ENV{PTDEBUG} || 0; |
229 | + |
230 | +use Data::Dumper; |
231 | +$Data::Dumper::Indent = 1; |
232 | +$Data::Dumper::Sortkeys = 1; |
233 | +$Data::Dumper::Quotekeys = 0; |
234 | + |
235 | +sub new { |
236 | + my ( $class, %args ) = @_; |
237 | + my @required_args = qw(Quoter); |
238 | + foreach my $arg ( @required_args ) { |
239 | + die "I need a $arg argument" unless $args{$arg}; |
240 | + } |
241 | + |
242 | + my $self = { |
243 | + Quoter => $args{Quoter}, |
244 | + }; |
245 | + |
246 | + return bless $self, $class; |
247 | +} |
248 | + |
249 | +sub index_length { |
250 | + my ($self, %args) = @_; |
251 | + my @required_args = qw(Cxn tbl index); |
252 | + foreach my $arg ( @required_args ) { |
253 | + die "I need a $arg argument" unless $args{$arg}; |
254 | + } |
255 | + my ($cxn) = @args{@required_args}; |
256 | + |
257 | + die "The tbl argument does not have a tbl_struct" |
258 | + unless exists $args{tbl}->{tbl_struct}; |
259 | + die "Index $args{index} does not exist in table $args{tbl}->{name}" |
260 | + unless $args{tbl}->{tbl_struct}->{keys}->{$args{index}}; |
261 | + |
262 | + my $index_struct = $args{tbl}->{tbl_struct}->{keys}->{$args{index}}; |
263 | + my $index_cols = $index_struct->{cols}; |
264 | + my $n_index_cols = $args{n_index_cols}; |
265 | + if ( !$n_index_cols || $n_index_cols > @$index_cols ) { |
266 | + $n_index_cols = scalar @$index_cols; |
267 | + } |
268 | + |
269 | + my $vals = $self->_get_first_values( |
270 | + %args, |
271 | + n_index_cols => $n_index_cols, |
272 | + ); |
273 | + |
274 | + my $sql = $self->_make_range_query( |
275 | + %args, |
276 | + n_index_cols => $n_index_cols, |
277 | + vals => $vals, |
278 | + ); |
279 | + my $sth = $cxn->dbh()->prepare($sql); |
280 | + PTDEBUG && _d($sth->{Statement}, 'params:', @$vals); |
281 | + $sth->execute(@$vals); |
282 | + my $row = $sth->fetchrow_hashref(); |
283 | + $sth->finish(); |
284 | + PTDEBUG && _d('Range scan:', Dumper($row)); |
285 | + return $row->{key_len}, $row->{key}; |
286 | +} |
287 | + |
288 | +sub _get_first_values { |
289 | + my ($self, %args) = @_; |
290 | + my @required_args = qw(Cxn tbl index n_index_cols); |
291 | + foreach my $arg ( @required_args ) { |
292 | + die "I need a $arg argument" unless $args{$arg}; |
293 | + } |
294 | + my ($cxn, $tbl, $index, $n_index_cols) = @args{@required_args}; |
295 | + |
296 | + my $q = $self->{Quoter}; |
297 | + |
298 | + my $index_struct = $tbl->{tbl_struct}->{keys}->{$index}; |
299 | + my $index_cols = $index_struct->{cols}; |
300 | + my $index_columns = join (', ', |
301 | + map { $q->quote($_) } @{$index_cols}[0..($n_index_cols - 1)]); |
302 | + |
303 | + my @where; |
304 | + foreach my $col ( @{$index_cols}[0..($n_index_cols - 1)] ) { |
305 | + push @where, $q->quote($col) . " IS NOT NULL" |
306 | + } |
307 | + |
308 | + my $sql = "SELECT /*!40001 SQL_NO_CACHE */ $index_columns " |
309 | + . "FROM $tbl->{name} FORCE INDEX (" . $q->quote($index) . ") " |
310 | + . "WHERE " . join(' AND ', @where) |
311 | + . " ORDER BY $index_columns " |
312 | + . "LIMIT 1 /*key_len*/"; # only need 1 row |
313 | + PTDEBUG && _d($sql); |
314 | + my $vals = $cxn->dbh()->selectrow_arrayref($sql); |
315 | + return $vals; |
316 | +} |
317 | + |
318 | +sub _make_range_query { |
319 | + my ($self, %args) = @_; |
320 | + my @required_args = qw(tbl index n_index_cols vals); |
321 | + foreach my $arg ( @required_args ) { |
322 | + die "I need a $arg argument" unless $args{$arg}; |
323 | + } |
324 | + my ($tbl, $index, $n_index_cols, $vals) = @args{@required_args}; |
325 | + |
326 | + my $q = $self->{Quoter}; |
327 | + |
328 | + my $index_struct = $tbl->{tbl_struct}->{keys}->{$index}; |
329 | + my $index_cols = $index_struct->{cols}; |
330 | + |
331 | + my @where; |
332 | + if ( $n_index_cols > 1 ) { |
333 | + foreach my $n ( 0..($n_index_cols - 2) ) { |
334 | + my $col = $index_cols->[$n]; |
335 | + my $val = $vals->[$n]; |
336 | + push @where, $q->quote($col) . " = ?"; |
337 | + } |
338 | + } |
339 | + |
340 | + my $col = $index_cols->[$n_index_cols - 1]; |
341 | + my $val = $vals->[-1]; # should only be as many vals as cols |
342 | + push @where, $q->quote($col) . " >= ?"; |
343 | + |
344 | + my $sql = "EXPLAIN SELECT /*!40001 SQL_NO_CACHE */ * " |
345 | + . "FROM $tbl->{name} FORCE INDEX (" . $q->quote($index) . ") " |
346 | + . "WHERE " . join(' AND ', @where) |
347 | + . " /*key_len*/"; |
348 | + return $sql; |
349 | +} |
350 | + |
351 | +sub _d { |
352 | + my ($package, undef, $line) = caller 0; |
353 | + @_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; } |
354 | + map { defined $_ ? $_ : 'undef' } |
355 | + @_; |
356 | + print STDERR "# $package:$line $PID ", join(' ', @_), "\n"; |
357 | +} |
358 | + |
359 | +1; |
360 | +} |
361 | +# ########################################################################### |
362 | +# End IndexLength package |
363 | +# ########################################################################### |
364 | + |
365 | +# ########################################################################### |
366 | # This is a combination of modules and programs in one -- a runnable module. |
367 | # http://www.perl.com/pub/a/2006/07/13/lightning-articles.html?page=last |
368 | # Or, look it up in the Camel book on pages 642 and 643 in the 3rd edition. |
369 | @@ -6748,30 +6900,30 @@ |
370 | } |
371 | else { # chunking the table |
372 | if ( $o->get('check-plan') ) { |
373 | - my $expl = explain_statement( |
374 | - sth => $statements->{explain_first_lower_boundary}, |
375 | - tbl => $tbl, |
376 | - vals => [], |
377 | + my $idx_len = new IndexLength(Quoter => $q); |
378 | + my ($key_len, $key) = $idx_len->index_length( |
379 | + Cxn => $args{Cxn}, |
380 | + tbl => $tbl, |
381 | + index => $nibble_iter->nibble_index(), |
382 | + n_index_cols => $o->get('chunk-index-columns'), |
383 | ); |
384 | - if ( !$expl->{key} |
385 | - || lc($expl->{key}) ne lc($nibble_iter->nibble_index()) ) |
386 | - { |
387 | + if ( !$key || lc($key) ne lc($nibble_iter->nibble_index()) ) { |
388 | die "Cannot determine the key_len of the chunk index " |
389 | . "because MySQL chose " |
390 | - . ($expl->{key} ? "the $expl->{key}" : "no") . " index " |
391 | + . ($key ? "the $key" : "no") . " index " |
392 | . "instead of the " . $nibble_iter->nibble_index() |
393 | . " index for the first lower boundary statement. " |
394 | . "See --[no]check-plan in the documentation for more " |
395 | . "information."; |
396 | } |
397 | - elsif ( !$expl->{key_len} ) { |
398 | - die "The key_len of the $expl->{key} index is " |
399 | - . (defined $expl->{key_len} ? "zero" : "NULL") |
400 | + elsif ( !$key_len ) { |
401 | + die "The key_len of the $key index is " |
402 | + . (defined $key_len ? "zero" : "NULL") |
403 | . ", but this should not be possible. " |
404 | . "See --[no]check-plan in the documentation for more " |
405 | . "information."; |
406 | } |
407 | - $tbl->{key_len} = $expl->{key_len}; |
408 | + $tbl->{key_len} = $key_len; |
409 | } |
410 | } |
411 | |
412 | |
413 | === added file 'lib/IndexLength.pm' |
414 | --- lib/IndexLength.pm 1970-01-01 00:00:00 +0000 |
415 | +++ lib/IndexLength.pm 2012-06-11 16:59:23 +0000 |
416 | @@ -0,0 +1,175 @@ |
417 | +# This program is copyright 2012 Percona Inc. |
418 | +# Feedback and improvements are welcome. |
419 | +# |
420 | +# THIS PROGRAM IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED |
421 | +# WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF |
422 | +# MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. |
423 | +# |
424 | +# This program is free software; you can redistribute it and/or modify it under |
425 | +# the terms of the GNU General Public License as published by the Free Software |
426 | +# Foundation, version 2; OR the Perl Artistic License. On UNIX and similar |
427 | +# systems, you can issue `man perlgpl' or `man perlartistic' to read these |
428 | +# licenses. |
429 | +# |
430 | +# You should have received a copy of the GNU General Public License along with |
431 | +# this program; if not, write to the Free Software Foundation, Inc., 59 Temple |
432 | +# Place, Suite 330, Boston, MA 02111-1307 USA. |
433 | +# ########################################################################### |
434 | +# IndexLength package |
435 | +# ########################################################################### |
436 | +{ |
437 | +# Package: IndexLength |
438 | +# IndexLength get the key_len of a index. |
439 | + |
440 | +package IndexLength; |
441 | + |
442 | +use strict; |
443 | +use warnings FATAL => 'all'; |
444 | +use English qw(-no_match_vars); |
445 | +use constant PTDEBUG => $ENV{PTDEBUG} || 0; |
446 | + |
447 | +use Data::Dumper; |
448 | +$Data::Dumper::Indent = 1; |
449 | +$Data::Dumper::Sortkeys = 1; |
450 | +$Data::Dumper::Quotekeys = 0; |
451 | + |
452 | +sub new { |
453 | + my ( $class, %args ) = @_; |
454 | + my @required_args = qw(Quoter); |
455 | + foreach my $arg ( @required_args ) { |
456 | + die "I need a $arg argument" unless $args{$arg}; |
457 | + } |
458 | + |
459 | + my $self = { |
460 | + Quoter => $args{Quoter}, |
461 | + }; |
462 | + |
463 | + return bless $self, $class; |
464 | +} |
465 | + |
466 | +# Returns the length of the index in bytes using only |
467 | +# the first N left-most columns of the index. |
468 | +sub index_length { |
469 | + my ($self, %args) = @_; |
470 | + my @required_args = qw(Cxn tbl index); |
471 | + foreach my $arg ( @required_args ) { |
472 | + die "I need a $arg argument" unless $args{$arg}; |
473 | + } |
474 | + my ($cxn) = @args{@required_args}; |
475 | + |
476 | + die "The tbl argument does not have a tbl_struct" |
477 | + unless exists $args{tbl}->{tbl_struct}; |
478 | + die "Index $args{index} does not exist in table $args{tbl}->{name}" |
479 | + unless $args{tbl}->{tbl_struct}->{keys}->{$args{index}}; |
480 | + |
481 | + my $index_struct = $args{tbl}->{tbl_struct}->{keys}->{$args{index}}; |
482 | + my $index_cols = $index_struct->{cols}; |
483 | + my $n_index_cols = $args{n_index_cols}; |
484 | + if ( !$n_index_cols || $n_index_cols > @$index_cols ) { |
485 | + $n_index_cols = scalar @$index_cols; |
486 | + } |
487 | + |
488 | + # Get the first row with non-NULL values. |
489 | + my $vals = $self->_get_first_values( |
490 | + %args, |
491 | + n_index_cols => $n_index_cols, |
492 | + ); |
493 | + |
494 | + # Make an EXPLAIN query to scan the range and execute it. |
495 | + my $sql = $self->_make_range_query( |
496 | + %args, |
497 | + n_index_cols => $n_index_cols, |
498 | + vals => $vals, |
499 | + ); |
500 | + my $sth = $cxn->dbh()->prepare($sql); |
501 | + PTDEBUG && _d($sth->{Statement}, 'params:', @$vals); |
502 | + $sth->execute(@$vals); |
503 | + my $row = $sth->fetchrow_hashref(); |
504 | + $sth->finish(); |
505 | + PTDEBUG && _d('Range scan:', Dumper($row)); |
506 | + return $row->{key_len}, $row->{key}; |
507 | +} |
508 | + |
509 | +sub _get_first_values { |
510 | + my ($self, %args) = @_; |
511 | + my @required_args = qw(Cxn tbl index n_index_cols); |
512 | + foreach my $arg ( @required_args ) { |
513 | + die "I need a $arg argument" unless $args{$arg}; |
514 | + } |
515 | + my ($cxn, $tbl, $index, $n_index_cols) = @args{@required_args}; |
516 | + |
517 | + my $q = $self->{Quoter}; |
518 | + |
519 | + # Select just the index columns. |
520 | + my $index_struct = $tbl->{tbl_struct}->{keys}->{$index}; |
521 | + my $index_cols = $index_struct->{cols}; |
522 | + my $index_columns = join (', ', |
523 | + map { $q->quote($_) } @{$index_cols}[0..($n_index_cols - 1)]); |
524 | + |
525 | + # Where no index column is null, because we can't > NULL. |
526 | + my @where; |
527 | + foreach my $col ( @{$index_cols}[0..($n_index_cols - 1)] ) { |
528 | + push @where, $q->quote($col) . " IS NOT NULL" |
529 | + } |
530 | + |
531 | + my $sql = "SELECT /*!40001 SQL_NO_CACHE */ $index_columns " |
532 | + . "FROM $tbl->{name} FORCE INDEX (" . $q->quote($index) . ") " |
533 | + . "WHERE " . join(' AND ', @where) |
534 | + . " ORDER BY $index_columns " |
535 | + . "LIMIT 1 /*key_len*/"; # only need 1 row |
536 | + PTDEBUG && _d($sql); |
537 | + my $vals = $cxn->dbh()->selectrow_arrayref($sql); |
538 | + return $vals; |
539 | +} |
540 | + |
541 | +sub _make_range_query { |
542 | + my ($self, %args) = @_; |
543 | + my @required_args = qw(tbl index n_index_cols vals); |
544 | + foreach my $arg ( @required_args ) { |
545 | + die "I need a $arg argument" unless $args{$arg}; |
546 | + } |
547 | + my ($tbl, $index, $n_index_cols, $vals) = @args{@required_args}; |
548 | + |
549 | + my $q = $self->{Quoter}; |
550 | + |
551 | + my $index_struct = $tbl->{tbl_struct}->{keys}->{$index}; |
552 | + my $index_cols = $index_struct->{cols}; |
553 | + |
554 | + # All but the last index col = val. |
555 | + my @where; |
556 | + if ( $n_index_cols > 1 ) { |
557 | + # -1 for zero-index array as usual, then -1 again because |
558 | + # we don't want the last column; that's added below. |
559 | + foreach my $n ( 0..($n_index_cols - 2) ) { |
560 | + my $col = $index_cols->[$n]; |
561 | + my $val = $vals->[$n]; |
562 | + push @where, $q->quote($col) . " = ?"; |
563 | + } |
564 | + } |
565 | + |
566 | + # The last index col > val. This causes the range scan using just |
567 | + # the N left-most index columns. |
568 | + my $col = $index_cols->[$n_index_cols - 1]; |
569 | + my $val = $vals->[-1]; # should only be as many vals as cols |
570 | + push @where, $q->quote($col) . " >= ?"; |
571 | + |
572 | + my $sql = "EXPLAIN SELECT /*!40001 SQL_NO_CACHE */ * " |
573 | + . "FROM $tbl->{name} FORCE INDEX (" . $q->quote($index) . ") " |
574 | + . "WHERE " . join(' AND ', @where) |
575 | + . " /*key_len*/"; |
576 | + return $sql; |
577 | +} |
578 | + |
579 | +sub _d { |
580 | + my ($package, undef, $line) = caller 0; |
581 | + @_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; } |
582 | + map { defined $_ ? $_ : 'undef' } |
583 | + @_; |
584 | + print STDERR "# $package:$line $PID ", join(' ', @_), "\n"; |
585 | +} |
586 | + |
587 | +1; |
588 | +} |
589 | +# ########################################################################### |
590 | +# End IndexLength package |
591 | +# ########################################################################### |
592 | |
593 | === added file 't/lib/IndexLength.pm' |
594 | --- t/lib/IndexLength.pm 1970-01-01 00:00:00 +0000 |
595 | +++ t/lib/IndexLength.pm 2012-06-11 16:59:23 +0000 |
596 | @@ -0,0 +1,135 @@ |
597 | +#!/usr/bin/perl |
598 | + |
599 | +BEGIN { |
600 | + die "The PERCONA_TOOLKIT_BRANCH environment variable is not set.\n" |
601 | + unless $ENV{PERCONA_TOOLKIT_BRANCH} && -d $ENV{PERCONA_TOOLKIT_BRANCH}; |
602 | + unshift @INC, "$ENV{PERCONA_TOOLKIT_BRANCH}/lib"; |
603 | +}; |
604 | + |
605 | +use strict; |
606 | +use warnings FATAL => 'all'; |
607 | +use English qw(-no_match_vars); |
608 | +use Test::More; |
609 | + |
610 | +use PerconaTest; |
611 | +use DSNParser; |
612 | +use Sandbox; |
613 | + |
614 | +use Cxn; |
615 | +use Quoter; |
616 | +use TableParser; |
617 | +use OptionParser; |
618 | +use IndexLength; |
619 | + |
620 | +use constant PTDEBUG => $ENV{PTDEBUG} || 0; |
621 | +use constant PTDEVDEBUG => $ENV{PTDEBUG} || 0; |
622 | + |
623 | +use Data::Dumper; |
624 | +$Data::Dumper::Indent = 1; |
625 | +$Data::Dumper::Sortkeys = 1; |
626 | +$Data::Dumper::Quotekeys = 0; |
627 | + |
628 | +my $dp = new DSNParser(opts=>$dsn_opts); |
629 | +my $sb = new Sandbox(basedir => '/tmp', DSNParser => $dp); |
630 | +my $dbh = $sb->get_dbh_for('master'); |
631 | + |
632 | +if ( !$dbh ) { |
633 | + plan skip_all => 'Cannot connect to sandbox master'; |
634 | +} |
635 | +else { |
636 | + plan tests => 7; |
637 | +} |
638 | + |
639 | +my $output; |
640 | +my $q = new Quoter(); |
641 | +my $tp = new TableParser(Quoter => $q); |
642 | +my $il = new IndexLength(Quoter => $q); |
643 | +my $o = new OptionParser(description => 'IndexLength'); |
644 | +$o->get_specs("$trunk/bin/pt-table-checksum"); |
645 | +my $cxn = new Cxn( |
646 | + dbh => $dbh, |
647 | + dsn => { h=>'127.1', P=>'12345', n=>'h=127.1,P=12345' }, |
648 | + DSNParser => $dp, |
649 | + OptionParser => $o, |
650 | +); |
651 | + |
652 | +sub test_index_len { |
653 | + my (%args) = @_; |
654 | + my @required_args = qw(name tbl index len); |
655 | + foreach my $arg ( @required_args ) { |
656 | + die "I need a $arg argument" unless $args{$arg}; |
657 | + } |
658 | + |
659 | + my ($len, $key) = $il->index_length( |
660 | + Cxn => $cxn, |
661 | + tbl => $args{tbl}, |
662 | + index => $args{index}, |
663 | + n_index_cols => $args{n_index_cols}, |
664 | + ); |
665 | + |
666 | + is( |
667 | + $len, |
668 | + $args{len}, |
669 | + "$args{name}" |
670 | + ); |
671 | +} |
672 | + |
673 | +# ############################################################################# |
674 | +# bad_plan, PK with 4 cols |
675 | +# ############################################################################# |
676 | +$sb->load_file('master', "t/pt-table-checksum/samples/bad-plan-bug-1010232.sql"); |
677 | +my $tbl_struct = $tp->parse( |
678 | + $tp->get_create_table($dbh, 'bad_plan', 't')); |
679 | +my $tbl = { |
680 | + name => $q->quote('bad_plan', 't'), |
681 | + tbl_struct => $tbl_struct, |
682 | +}; |
683 | + |
684 | +for my $n ( 1..4 ) { |
685 | + my $len = $n * 2 + ($n >= 2 ? 1 : 0); |
686 | + test_index_len( |
687 | + name => "bad_plan.t $n cols = $len bytes", |
688 | + tbl => $tbl, |
689 | + index => "PRIMARY", |
690 | + n_index_cols => $n, |
691 | + len => $len, |
692 | + ); |
693 | +} |
694 | + |
695 | +# ############################################################################# |
696 | +# Some sakila tables |
697 | +# ############################################################################# |
698 | +$tbl_struct = $tp->parse( |
699 | + $tp->get_create_table($dbh, 'sakila', 'film_actor')); |
700 | +$tbl = { |
701 | + name => $q->quote('sakila', 'film_actor'), |
702 | + tbl_struct => $tbl_struct, |
703 | +}; |
704 | + |
705 | +test_index_len( |
706 | + name => "sakila.film_actor 1 col = 2 bytes", |
707 | + tbl => $tbl, |
708 | + index => "PRIMARY", |
709 | + n_index_cols => 1, |
710 | + len => 2, |
711 | +); |
712 | + |
713 | +# ############################################################################# |
714 | +# Use full index if no n_index_cols |
715 | +# ############################################################################# |
716 | + |
717 | +# Use sakila.film_actor stuff from previous tests. |
718 | + |
719 | +test_index_len( |
720 | + name => "sakila.film_actor all cols = 4 bytes", |
721 | + tbl => $tbl, |
722 | + index => "PRIMARY", |
723 | + len => 4, |
724 | +); |
725 | + |
726 | +# ############################################################################# |
727 | +# Done. |
728 | +# ############################################################################# |
729 | +$sb->wipe_clean($dbh); |
730 | +ok($sb->ok(), "Sandbox servers") or BAIL_OUT(__FILE__ . " broke the sandbox"); |
731 | +exit; |
732 | |
733 | === modified file 't/pt-table-checksum/chunk_index.t' |
734 | --- t/pt-table-checksum/chunk_index.t 2012-06-11 12:07:18 +0000 |
735 | +++ t/pt-table-checksum/chunk_index.t 2012-06-11 16:59:23 +0000 |
736 | @@ -25,7 +25,7 @@ |
737 | plan skip_all => 'Cannot connect to sandbox master'; |
738 | } |
739 | else { |
740 | - plan tests => 16; |
741 | + plan tests => 17; |
742 | } |
743 | |
744 | # The sandbox servers run with lock_wait_timeout=3 and it's not dynamic |
745 | @@ -175,7 +175,7 @@ |
746 | $exit_status, |
747 | 0, |
748 | "Bad key_len chunks are not errors" |
749 | -); |
750 | +) or diag($output); |
751 | |
752 | cmp_ok( |
753 | PerconaTest::count_checksum_results($output, 'skipped'), |
754 | @@ -205,19 +205,40 @@ |
755 | sub { |
756 | $exit_status = pt_table_checksum::main( |
757 | $master_dsn, '--max-load', '', |
758 | - qw(--lock-wait-timeout 3 --chunk-size 5000 -t sakila.rental), |
759 | - qw(--chunk-index rental_date --chunk-index-columns 5), |
760 | - qw(--explain --explain)); |
761 | + qw(--lock-wait-timeout 3 --chunk-size 1000 -t sakila.film_actor), |
762 | + qw(--chunk-index PRIMARY --chunk-index-columns 9), |
763 | + ); |
764 | }, |
765 | stderr => 1, |
766 | ); |
767 | |
768 | is( |
769 | - $exit_status, |
770 | - 0, |
771 | + PerconaTest::count_checksum_results($output, 'rows'), |
772 | + 5462, |
773 | "--chunk-index-columns > number of index columns" |
774 | +) or diag($output); |
775 | + |
776 | +$output = output( |
777 | + sub { |
778 | + $exit_status = pt_table_checksum::main( |
779 | + $master_dsn, '--max-load', '', |
780 | + qw(--lock-wait-timeout 3 --chunk-size 1000 -t sakila.film_actor), |
781 | + qw(--chunk-index-columns 1 --chunk-size-limit 3), |
782 | + ); |
783 | + }, |
784 | + stderr => 1, |
785 | ); |
786 | |
787 | +# Since we're not using the full index, it's basically a non-unique index, |
788 | +# so there are dupes. The table really has 5462 rows, so we must get |
789 | +# at least that many, and probably a few more. |
790 | +cmp_ok( |
791 | + PerconaTest::count_checksum_results($output, 'rows'), |
792 | + '>=', |
793 | + 5462, |
794 | + "Initial key_len reflects --chunk-index-columns" |
795 | +) or diag($output); |
796 | + |
797 | # ############################################################################# |
798 | # Done. |
799 | # ############################################################################# |