Merge lp:~percona-toolkit-dev/percona-toolkit/explain-checksum-chunks into lp:percona-toolkit/2.1
- explain-checksum-chunks
- Merge into 2.1
Proposed by
Daniel Nichter
Status: | Merged |
---|---|
Merged at revision: | 279 |
Proposed branch: | lp:~percona-toolkit-dev/percona-toolkit/explain-checksum-chunks |
Merge into: | lp:percona-toolkit/2.1 |
Diff against target: |
1357 lines (+767/-194) 8 files modified
bin/pt-online-schema-change (+304/-86) bin/pt-table-checksum (+323/-85) lib/NibbleIterator.pm (+24/-20) lib/TableNibbler.pm (+6/-1) t/lib/TableNibbler.t (+29/-1) t/pt-table-checksum/chunk_index.t (+45/-1) t/pt-table-checksum/samples/bad-plan-bug-1010232.sql (+17/-0) t/pt-table-checksum/samples/n-chunk-index-cols.txt (+19/-0) |
To merge this branch: | bzr merge lp:~percona-toolkit-dev/percona-toolkit/explain-checksum-chunks |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Baron Schwartz (community) | Approve | ||
Brian Fraser (community) | Approve | ||
Daniel Nichter | Approve | ||
Review via email:
|
Commit message
Description of the change
To post a comment you must log in.
Revision history for this message
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Daniel Nichter (daniel-nichter) : | # |
review:
Approve
- 286. By Daniel Nichter
-
Fix docs about skipping chunks with bad plans.
Revision history for this message
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Brian Fraser (fraserbn) : | # |
review:
Approve
- 287. By Daniel Nichter
-
Clarify --chunk-index name:n validation.
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-05-30 17:27:07 +0000 | |||
3 | +++ bin/pt-online-schema-change 2012-06-10 17:15:24 +0000 | |||
4 | @@ -1828,8 +1828,12 @@ | |||
5 | 1828 | 1828 | ||
6 | 1829 | my @asc_cols = @{$tbl_struct->{keys}->{$index}->{cols}}; | 1829 | my @asc_cols = @{$tbl_struct->{keys}->{$index}->{cols}}; |
7 | 1830 | if ( $args{asc_first} ) { | 1830 | if ( $args{asc_first} ) { |
8 | 1831 | PTDEBUG && _d('Ascending only first column'); | ||
9 | 1831 | @asc_cols = $asc_cols[0]; | 1832 | @asc_cols = $asc_cols[0]; |
11 | 1832 | PTDEBUG && _d('Ascending only first column'); | 1833 | } |
12 | 1834 | elsif ( my $n = $args{n_index_cols} ) { | ||
13 | 1835 | PTDEBUG && _d('Ascending only first', $n, 'columns'); | ||
14 | 1836 | @asc_cols = @asc_cols[0..($n-1)]; | ||
15 | 1833 | } | 1837 | } |
16 | 1834 | PTDEBUG && _d('Will ascend columns', join(', ', @asc_cols)); | 1838 | PTDEBUG && _d('Will ascend columns', join(', ', @asc_cols)); |
17 | 1835 | 1839 | ||
18 | @@ -4029,10 +4033,11 @@ | |||
19 | 4029 | 4033 | ||
20 | 4030 | my $asc = $args{TableNibbler}->generate_asc_stmt( | 4034 | my $asc = $args{TableNibbler}->generate_asc_stmt( |
21 | 4031 | %args, | 4035 | %args, |
26 | 4032 | tbl_struct => $tbl->{tbl_struct}, | 4036 | tbl_struct => $tbl->{tbl_struct}, |
27 | 4033 | index => $index, | 4037 | index => $index, |
28 | 4034 | cols => \@cols, | 4038 | n_index_cols => $args{n_chunk_index_cols}, |
29 | 4035 | asc_only => 1, | 4039 | cols => \@cols, |
30 | 4040 | asc_only => 1, | ||
31 | 4036 | ); | 4041 | ); |
32 | 4037 | PTDEBUG && _d('Ascend params:', Dumper($asc)); | 4042 | PTDEBUG && _d('Ascend params:', Dumper($asc)); |
33 | 4038 | 4043 | ||
34 | @@ -4114,16 +4119,17 @@ | |||
35 | 4114 | 4119 | ||
36 | 4115 | $self = { | 4120 | $self = { |
37 | 4116 | %args, | 4121 | %args, |
48 | 4117 | index => $index, | 4122 | index => $index, |
49 | 4118 | limit => $limit, | 4123 | limit => $limit, |
50 | 4119 | first_lb_sql => $first_lb_sql, | 4124 | first_lb_sql => $first_lb_sql, |
51 | 4120 | last_ub_sql => $last_ub_sql, | 4125 | last_ub_sql => $last_ub_sql, |
52 | 4121 | ub_sql => $ub_sql, | 4126 | ub_sql => $ub_sql, |
53 | 4122 | nibble_sql => $nibble_sql, | 4127 | nibble_sql => $nibble_sql, |
54 | 4123 | explain_ub_sql => "EXPLAIN $ub_sql", | 4128 | explain_first_lb_sql => "EXPLAIN $first_lb_sql", |
55 | 4124 | explain_nibble_sql => $explain_nibble_sql, | 4129 | explain_ub_sql => "EXPLAIN $ub_sql", |
56 | 4125 | resume_lb_sql => $resume_lb_sql, | 4130 | explain_nibble_sql => $explain_nibble_sql, |
57 | 4126 | sql => { | 4131 | resume_lb_sql => $resume_lb_sql, |
58 | 4132 | sql => { | ||
59 | 4127 | columns => $asc->{scols}, | 4133 | columns => $asc->{scols}, |
60 | 4128 | from => $from, | 4134 | from => $from, |
61 | 4129 | where => $where, | 4135 | where => $where, |
62 | @@ -4231,10 +4237,11 @@ | |||
63 | 4231 | sub statements { | 4237 | sub statements { |
64 | 4232 | my ($self) = @_; | 4238 | my ($self) = @_; |
65 | 4233 | return { | 4239 | return { |
70 | 4234 | nibble => $self->{nibble_sth}, | 4240 | explain_first_lower_boundary => $self->{explain_first_lb_sth}, |
71 | 4235 | explain_nibble => $self->{explain_nibble_sth}, | 4241 | nibble => $self->{nibble_sth}, |
72 | 4236 | upper_boundary => $self->{ub_sth}, | 4242 | explain_nibble => $self->{explain_nibble_sth}, |
73 | 4237 | explain_upper_boundary => $self->{explain_ub_sth}, | 4243 | upper_boundary => $self->{ub_sth}, |
74 | 4244 | explain_upper_boundary => $self->{explain_ub_sth}, | ||
75 | 4238 | } | 4245 | } |
76 | 4239 | } | 4246 | } |
77 | 4240 | 4247 | ||
78 | @@ -4463,8 +4470,9 @@ | |||
79 | 4463 | $self->{explain_nibble_sth} = $dbh->prepare($self->{explain_nibble_sql}); | 4470 | $self->{explain_nibble_sth} = $dbh->prepare($self->{explain_nibble_sql}); |
80 | 4464 | 4471 | ||
81 | 4465 | if ( !$self->{one_nibble} ) { | 4472 | if ( !$self->{one_nibble} ) { |
84 | 4466 | $self->{ub_sth} = $dbh->prepare($self->{ub_sql}); | 4473 | $self->{explain_first_lb_sth} = $dbh->prepare($self->{explain_first_lb_sql}); |
85 | 4467 | $self->{explain_ub_sth} = $dbh->prepare($self->{explain_ub_sql}); | 4474 | $self->{ub_sth} = $dbh->prepare($self->{ub_sql}); |
86 | 4475 | $self->{explain_ub_sth} = $dbh->prepare($self->{explain_ub_sql}); | ||
87 | 4468 | } | 4476 | } |
88 | 4469 | 4477 | ||
89 | 4470 | return; | 4478 | return; |
90 | @@ -4652,6 +4660,7 @@ | |||
91 | 4652 | 4660 | ||
92 | 4653 | use Time::Local qw(timegm timelocal); | 4661 | use Time::Local qw(timegm timelocal); |
93 | 4654 | use Digest::MD5 qw(md5_hex); | 4662 | use Digest::MD5 qw(md5_hex); |
94 | 4663 | use B qw(); | ||
95 | 4655 | 4664 | ||
96 | 4656 | require Exporter; | 4665 | require Exporter; |
97 | 4657 | our @ISA = qw(Exporter); | 4666 | our @ISA = qw(Exporter); |
98 | @@ -4669,6 +4678,7 @@ | |||
99 | 4669 | any_unix_timestamp | 4678 | any_unix_timestamp |
100 | 4670 | make_checksum | 4679 | make_checksum |
101 | 4671 | crc32 | 4680 | crc32 |
102 | 4681 | encode_json | ||
103 | 4672 | ); | 4682 | ); |
104 | 4673 | 4683 | ||
105 | 4674 | our $mysql_ts = qr/(\d\d)(\d\d)(\d\d) +(\d+):(\d+):(\d+)(\.\d+)?/; | 4684 | our $mysql_ts = qr/(\d\d)(\d\d)(\d\d) +(\d+):(\d+):(\d+)(\.\d+)?/; |
106 | @@ -4876,6 +4886,96 @@ | |||
107 | 4876 | return $crc ^ 0xFFFFFFFF; | 4886 | return $crc ^ 0xFFFFFFFF; |
108 | 4877 | } | 4887 | } |
109 | 4878 | 4888 | ||
110 | 4889 | my $got_json = eval { require JSON }; | ||
111 | 4890 | sub encode_json { | ||
112 | 4891 | return JSON::encode_json(@_) if $got_json; | ||
113 | 4892 | my ( $data ) = @_; | ||
114 | 4893 | return (object_to_json($data) || ''); | ||
115 | 4894 | } | ||
116 | 4895 | |||
117 | 4896 | |||
118 | 4897 | sub object_to_json { | ||
119 | 4898 | my ($obj) = @_; | ||
120 | 4899 | my $type = ref($obj); | ||
121 | 4900 | |||
122 | 4901 | if($type eq 'HASH'){ | ||
123 | 4902 | return hash_to_json($obj); | ||
124 | 4903 | } | ||
125 | 4904 | elsif($type eq 'ARRAY'){ | ||
126 | 4905 | return array_to_json($obj); | ||
127 | 4906 | } | ||
128 | 4907 | else { | ||
129 | 4908 | return value_to_json($obj); | ||
130 | 4909 | } | ||
131 | 4910 | } | ||
132 | 4911 | |||
133 | 4912 | sub hash_to_json { | ||
134 | 4913 | my ($obj) = @_; | ||
135 | 4914 | my @res; | ||
136 | 4915 | for my $k ( sort { $a cmp $b } keys %$obj ) { | ||
137 | 4916 | push @res, string_to_json( $k ) | ||
138 | 4917 | . ":" | ||
139 | 4918 | . ( object_to_json( $obj->{$k} ) || value_to_json( $obj->{$k} ) ); | ||
140 | 4919 | } | ||
141 | 4920 | return '{' . ( @res ? join( ",", @res ) : '' ) . '}'; | ||
142 | 4921 | } | ||
143 | 4922 | |||
144 | 4923 | sub array_to_json { | ||
145 | 4924 | my ($obj) = @_; | ||
146 | 4925 | my @res; | ||
147 | 4926 | |||
148 | 4927 | for my $v (@$obj) { | ||
149 | 4928 | push @res, object_to_json($v) || value_to_json($v); | ||
150 | 4929 | } | ||
151 | 4930 | |||
152 | 4931 | return '[' . ( @res ? join( ",", @res ) : '' ) . ']'; | ||
153 | 4932 | } | ||
154 | 4933 | |||
155 | 4934 | sub value_to_json { | ||
156 | 4935 | my ($value) = @_; | ||
157 | 4936 | |||
158 | 4937 | return 'null' if(!defined $value); | ||
159 | 4938 | |||
160 | 4939 | my $b_obj = B::svref_2object(\$value); # for round trip problem | ||
161 | 4940 | my $flags = $b_obj->FLAGS; | ||
162 | 4941 | return $value # as is | ||
163 | 4942 | if $flags & ( B::SVp_IOK | B::SVp_NOK ) and !( $flags & B::SVp_POK ); # SvTYPE is IV or NV? | ||
164 | 4943 | |||
165 | 4944 | my $type = ref($value); | ||
166 | 4945 | |||
167 | 4946 | if( !$type ) { | ||
168 | 4947 | return string_to_json($value); | ||
169 | 4948 | } | ||
170 | 4949 | else { | ||
171 | 4950 | return 'null'; | ||
172 | 4951 | } | ||
173 | 4952 | |||
174 | 4953 | } | ||
175 | 4954 | |||
176 | 4955 | my %esc = ( | ||
177 | 4956 | "\n" => '\n', | ||
178 | 4957 | "\r" => '\r', | ||
179 | 4958 | "\t" => '\t', | ||
180 | 4959 | "\f" => '\f', | ||
181 | 4960 | "\b" => '\b', | ||
182 | 4961 | "\"" => '\"', | ||
183 | 4962 | "\\" => '\\\\', | ||
184 | 4963 | "\'" => '\\\'', | ||
185 | 4964 | ); | ||
186 | 4965 | |||
187 | 4966 | sub string_to_json { | ||
188 | 4967 | my ($arg) = @_; | ||
189 | 4968 | |||
190 | 4969 | $arg =~ s/([\x22\x5c\n\r\t\f\b])/$esc{$1}/g; | ||
191 | 4970 | $arg =~ s/\//\\\//g; | ||
192 | 4971 | $arg =~ s/([\x00-\x08\x0b\x0e-\x1f])/'\\u00' . unpack('H2', $1)/eg; | ||
193 | 4972 | |||
194 | 4973 | utf8::upgrade($arg); | ||
195 | 4974 | utf8::encode($arg); | ||
196 | 4975 | |||
197 | 4976 | return '"' . $arg . '"'; | ||
198 | 4977 | } | ||
199 | 4978 | |||
200 | 4879 | sub _d { | 4979 | sub _d { |
201 | 4880 | my ($package, undef, $line) = caller 0; | 4980 | my ($package, undef, $line) = caller 0; |
202 | 4881 | @_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; } | 4981 | @_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; } |
203 | @@ -5027,6 +5127,22 @@ | |||
204 | 5027 | } | 5127 | } |
205 | 5028 | } | 5128 | } |
206 | 5029 | 5129 | ||
207 | 5130 | # Parse --chunk-index INDEX:N where N is the number of | ||
208 | 5131 | # left-most columns of INDEX to use. | ||
209 | 5132 | # https://bugs.launchpad.net/percona-toolkit/+bug/1010232 | ||
210 | 5133 | my ($chunk_index, $n_chunk_index_cols) | ||
211 | 5134 | = split(':', $o->get('chunk-index') || ''); | ||
212 | 5135 | if ( defined $chunk_index && $chunk_index eq '' ) { | ||
213 | 5136 | $o->save_error('--chunk-index cannot be an empty string'); | ||
214 | 5137 | } | ||
215 | 5138 | if ( defined $n_chunk_index_cols | ||
216 | 5139 | && (!$n_chunk_index_cols | ||
217 | 5140 | || $n_chunk_index_cols =~ m/\D/ | ||
218 | 5141 | || $n_chunk_index_cols < 1) ) { | ||
219 | 5142 | $o->save_error('Invalid number of --chunk-index columns: ' | ||
220 | 5143 | . $n_chunk_index_cols); | ||
221 | 5144 | } | ||
222 | 5145 | |||
223 | 5030 | if ( !$o->get('help') ) { | 5146 | if ( !$o->get('help') ) { |
224 | 5031 | if ( @ARGV ) { | 5147 | if ( @ARGV ) { |
225 | 5032 | $o->save_error('Specify only one DSN on the command line'); | 5148 | $o->save_error('Specify only one DSN on the command line'); |
226 | @@ -5641,6 +5757,7 @@ | |||
227 | 5641 | my (%args) = @_; | 5757 | my (%args) = @_; |
228 | 5642 | my $tbl = $args{tbl}; | 5758 | my $tbl = $args{tbl}; |
229 | 5643 | my $nibble_iter = $args{NibbleIterator}; | 5759 | my $nibble_iter = $args{NibbleIterator}; |
230 | 5760 | my $statements = $nibble_iter->statements(); | ||
231 | 5644 | 5761 | ||
232 | 5645 | if ( $o->get('dry-run') ) { | 5762 | if ( $o->get('dry-run') ) { |
233 | 5646 | print "Not copying rows because this is a dry run.\n"; | 5763 | print "Not copying rows because this is a dry run.\n"; |
234 | @@ -5652,7 +5769,6 @@ | |||
235 | 5652 | 5769 | ||
236 | 5653 | if ( $o->get('print') ) { | 5770 | if ( $o->get('print') ) { |
237 | 5654 | # Print the checksum and next boundary statements. | 5771 | # Print the checksum and next boundary statements. |
238 | 5655 | my $statements = $nibble_iter->statements(); | ||
239 | 5656 | foreach my $sth ( sort keys %$statements ) { | 5772 | foreach my $sth ( sort keys %$statements ) { |
240 | 5657 | next if $sth =~ m/^explain/; | 5773 | next if $sth =~ m/^explain/; |
241 | 5658 | if ( $statements->{$sth} ) { | 5774 | if ( $statements->{$sth} ) { |
242 | @@ -5696,6 +5812,34 @@ | |||
243 | 5696 | die $msg; | 5812 | die $msg; |
244 | 5697 | } | 5813 | } |
245 | 5698 | } | 5814 | } |
246 | 5815 | else { # chunking the table | ||
247 | 5816 | if ( $o->get('check-plan') ) { | ||
248 | 5817 | my $expl = explain_statement( | ||
249 | 5818 | sth => $statements->{explain_first_lower_boundary}, | ||
250 | 5819 | tbl => $tbl, | ||
251 | 5820 | vals => [], | ||
252 | 5821 | ); | ||
253 | 5822 | if ( !$expl->{key} | ||
254 | 5823 | || lc($expl->{key}) ne lc($nibble_iter->nibble_index()) ) | ||
255 | 5824 | { | ||
256 | 5825 | die "Cannot determine the key_len of the chunk index " | ||
257 | 5826 | . "because MySQL chose " | ||
258 | 5827 | . ($expl->{key} ? "the $expl->{key}" : "no") . " index " | ||
259 | 5828 | . "instead of the " . $nibble_iter->nibble_index() | ||
260 | 5829 | . " index for the first lower boundary statement. " | ||
261 | 5830 | . "See --[no]check-plan in the documentation for more " | ||
262 | 5831 | . "information."; | ||
263 | 5832 | } | ||
264 | 5833 | elsif ( !$expl->{key_len} ) { | ||
265 | 5834 | die "The key_len of the $expl->{key} index is " | ||
266 | 5835 | . (defined $expl->{key_len} ? "zero" : "NULL") | ||
267 | 5836 | . ", but this should not be possible. " | ||
268 | 5837 | . "See --[no]check-plan in the documentation for more " | ||
269 | 5838 | . "information."; | ||
270 | 5839 | } | ||
271 | 5840 | $tbl->{key_len} = $expl->{key_len}; | ||
272 | 5841 | } | ||
273 | 5842 | } | ||
274 | 5699 | 5843 | ||
275 | 5700 | return 1; # continue nibbling table | 5844 | return 1; # continue nibbling table |
276 | 5701 | }, | 5845 | }, |
277 | @@ -5755,58 +5899,11 @@ | |||
278 | 5755 | # Count every chunk, even if it's ultimately skipped, etc. | 5899 | # Count every chunk, even if it's ultimately skipped, etc. |
279 | 5756 | $tbl->{results}->{n_chunks}++; | 5900 | $tbl->{results}->{n_chunks}++; |
280 | 5757 | 5901 | ||
333 | 5758 | # If the table is being chunk (i.e., it's not small enough to be | 5902 | # Die unless the nibble is safe. |
334 | 5759 | # consumed by one nibble), then check index usage and chunk size. | 5903 | nibble_is_safe( |
335 | 5760 | if ( !$nibble_iter->one_nibble() ) { | 5904 | %args, |
336 | 5761 | my $expl = explain_statement( | 5905 | OptionParser => $o, |
337 | 5762 | tbl => $tbl, | 5906 | ); |
286 | 5763 | sth => $sth->{explain_nibble}, | ||
287 | 5764 | vals => [ @{$boundary->{lower}}, @{$boundary->{upper}} ], | ||
288 | 5765 | ); | ||
289 | 5766 | |||
290 | 5767 | # Ensure that MySQL is using the chunk index. | ||
291 | 5768 | if ( lc($expl->{key} || '') | ||
292 | 5769 | ne lc($nibble_iter->nibble_index() || '') ) { | ||
293 | 5770 | my $msg | ||
294 | 5771 | = "Aborting copying table $tbl->{name} at chunk " | ||
295 | 5772 | . $nibble_iter->nibble_number() | ||
296 | 5773 | . " because it is not safe to chunk. Chunking should " | ||
297 | 5774 | . "use the " | ||
298 | 5775 | . ($nibble_iter->nibble_index() || '?') | ||
299 | 5776 | . " index, but MySQL EXPLAIN reports that " | ||
300 | 5777 | . ($expl->{key} ? "the $expl->{key}" : "no") | ||
301 | 5778 | . " index will be used for " | ||
302 | 5779 | . $sth->{explain_nibble}->{Statement} | ||
303 | 5780 | . " with values " | ||
304 | 5781 | . join(", ", map { defined $_ ? $_ : "NULL" } | ||
305 | 5782 | (@{$boundary->{lower}}, @{$boundary->{upper}})) | ||
306 | 5783 | . "\n"; | ||
307 | 5784 | die $msg; | ||
308 | 5785 | } | ||
309 | 5786 | |||
310 | 5787 | # Check chunk size limit if the upper boundary and next lower | ||
311 | 5788 | # boundary are identical. | ||
312 | 5789 | if ( $limit ) { | ||
313 | 5790 | my $boundary = $nibble_iter->boundaries(); | ||
314 | 5791 | my $oversize_chunk | ||
315 | 5792 | = $limit ? ($expl->{rows} || 0) >= $tbl->{chunk_size} * $limit | ||
316 | 5793 | : 0; | ||
317 | 5794 | if ( $oversize_chunk | ||
318 | 5795 | && $nibble_iter->identical_boundaries( | ||
319 | 5796 | $boundary->{upper}, $boundary->{next_lower}) ) | ||
320 | 5797 | { | ||
321 | 5798 | my $msg | ||
322 | 5799 | = "Aborting copying table $tbl->{name} at chunk " | ||
323 | 5800 | . $nibble_iter->nibble_number() | ||
324 | 5801 | . " because the chunk is too large: MySQL estimates " | ||
325 | 5802 | . ($expl->{rows} || 0) . "rows. The current chunk " | ||
326 | 5803 | . "size limit is " . ($tbl->{chunk_size} * $limit) | ||
327 | 5804 | . " rows (chunk size=$tbl->{chunk_size}" | ||
328 | 5805 | . " * chunk size limit=$limit).\n"; | ||
329 | 5806 | die $msg; | ||
330 | 5807 | } | ||
331 | 5808 | } | ||
332 | 5809 | } | ||
338 | 5810 | 5907 | ||
339 | 5811 | # Exec and time the chunk checksum query. | 5908 | # Exec and time the chunk checksum query. |
340 | 5812 | $tbl->{nibble_time} = exec_nibble( | 5909 | $tbl->{nibble_time} = exec_nibble( |
341 | @@ -5909,18 +6006,19 @@ | |||
342 | 5909 | # This won't (shouldn't) fail because we already verified in | 6006 | # This won't (shouldn't) fail because we already verified in |
343 | 5910 | # check_orig_table() table we can NibbleIterator::can_nibble(). | 6007 | # check_orig_table() table we can NibbleIterator::can_nibble(). |
344 | 5911 | my $nibble_iter = new NibbleIterator( | 6008 | my $nibble_iter = new NibbleIterator( |
357 | 5912 | Cxn => $cxn, | 6009 | Cxn => $cxn, |
358 | 5913 | tbl => $orig_tbl, | 6010 | tbl => $orig_tbl, |
359 | 5914 | chunk_size => $orig_tbl->{chunk_size}, | 6011 | chunk_size => $orig_tbl->{chunk_size}, |
360 | 5915 | chunk_index => $o->get('chunk-index'), | 6012 | chunk_index => $chunk_index, |
361 | 5916 | dml => $dml, | 6013 | n_chunk_index_cols => $n_chunk_index_cols, |
362 | 5917 | select => $select, | 6014 | dml => $dml, |
363 | 5918 | callbacks => $callbacks, | 6015 | select => $select, |
364 | 5919 | OptionParser => $o, | 6016 | callbacks => $callbacks, |
365 | 5920 | Quoter => $q, | 6017 | OptionParser => $o, |
366 | 5921 | TableParser => $tp, | 6018 | Quoter => $q, |
367 | 5922 | TableNibbler => new TableNibbler(TableParser => $tp, Quoter => $q), | 6019 | TableParser => $tp, |
368 | 5923 | comments => { | 6020 | TableNibbler => new TableNibbler(TableParser => $tp, Quoter => $q), |
369 | 6021 | comments => { | ||
370 | 5924 | bite => "pt-online-schema-change $PID copy table", | 6022 | bite => "pt-online-schema-change $PID copy table", |
371 | 5925 | nibble => "pt-online-schema-change $PID copy nibble", | 6023 | nibble => "pt-online-schema-change $PID copy nibble", |
372 | 5926 | }, | 6024 | }, |
373 | @@ -6110,6 +6208,82 @@ | |||
374 | 6110 | # ############################################################################ | 6208 | # ############################################################################ |
375 | 6111 | # Subroutines. | 6209 | # Subroutines. |
376 | 6112 | # ############################################################################ | 6210 | # ############################################################################ |
377 | 6211 | |||
378 | 6212 | sub nibble_is_safe { | ||
379 | 6213 | my (%args) = @_; | ||
380 | 6214 | my @required_args = qw(Cxn tbl NibbleIterator OptionParser); | ||
381 | 6215 | foreach my $arg ( @required_args ) { | ||
382 | 6216 | die "I need a $arg argument" unless $args{$arg}; | ||
383 | 6217 | } | ||
384 | 6218 | my ($cxn, $tbl, $nibble_iter, $o)= @args{@required_args}; | ||
385 | 6219 | |||
386 | 6220 | # EXPLAIN the checksum chunk query to get its row estimate and index. | ||
387 | 6221 | # XXX This call and others like it are relying on a Perl oddity. | ||
388 | 6222 | # See https://bugs.launchpad.net/percona-toolkit/+bug/987393 | ||
389 | 6223 | my $sth = $nibble_iter->statements(); | ||
390 | 6224 | my $boundary = $nibble_iter->boundaries(); | ||
391 | 6225 | my $expl = explain_statement( | ||
392 | 6226 | tbl => $tbl, | ||
393 | 6227 | sth => $sth->{explain_nibble}, | ||
394 | 6228 | vals => [ @{$boundary->{lower}}, @{$boundary->{upper}} ], | ||
395 | 6229 | ); | ||
396 | 6230 | |||
397 | 6231 | # Ensure that MySQL is using the chunk index if the table is being chunked. | ||
398 | 6232 | if ( !$nibble_iter->one_nibble() | ||
399 | 6233 | && lc($expl->{key} || '') ne lc($nibble_iter->nibble_index() || '') ) { | ||
400 | 6234 | if ( !$tbl->{warned}->{not_using_chunk_index}++ | ||
401 | 6235 | && $o->get('quiet') < 2 ) { | ||
402 | 6236 | die "Error copying rows at chunk " . $nibble_iter->nibble_number() | ||
403 | 6237 | . " of $tbl->{db}.$tbl->{tbl} because MySQL chose " | ||
404 | 6238 | . ($expl->{key} ? "the $expl->{key}" : "no") . " index " | ||
405 | 6239 | . " instead of the " . $nibble_iter->nibble_index() . "index.\n"; | ||
406 | 6240 | } | ||
407 | 6241 | } | ||
408 | 6242 | |||
409 | 6243 | # Ensure that the chunk isn't too large if there's a --chunk-size-limit. | ||
410 | 6244 | # If single-chunking the table, this has already been checked, so it | ||
411 | 6245 | # shouldn't have changed. If chunking the table with a non-unique key, | ||
412 | 6246 | # oversize chunks are possible. | ||
413 | 6247 | if ( my $limit = $o->get('chunk-size-limit') ) { | ||
414 | 6248 | my $oversize_chunk | ||
415 | 6249 | = $limit ? ($expl->{rows} || 0) >= $tbl->{chunk_size} * $limit | ||
416 | 6250 | : 0; | ||
417 | 6251 | if ( $oversize_chunk | ||
418 | 6252 | && $nibble_iter->identical_boundaries($boundary->{upper}, | ||
419 | 6253 | $boundary->{next_lower}) ) { | ||
420 | 6254 | if ( !$tbl->{warned}->{oversize_chunk}++ | ||
421 | 6255 | && $o->get('quiet') < 2 ) { | ||
422 | 6256 | die "Error copying rows at chunk " . $nibble_iter->nibble_number() | ||
423 | 6257 | . " of $tbl->{db}.$tbl->{tbl} because it is oversized. " | ||
424 | 6258 | . "The current chunk size limit is " | ||
425 | 6259 | . ($tbl->{chunk_size} * $limit) | ||
426 | 6260 | . " rows (chunk size=$tbl->{chunk_size}" | ||
427 | 6261 | . " * chunk size limit=$limit), but MySQL estimates " | ||
428 | 6262 | . "that there are " . ($expl->{rows} || 0) | ||
429 | 6263 | . " rows in the chunk.\n"; | ||
430 | 6264 | } | ||
431 | 6265 | } | ||
432 | 6266 | } | ||
433 | 6267 | |||
434 | 6268 | # Ensure that MySQL is still using the entire index. | ||
435 | 6269 | # https://bugs.launchpad.net/percona-toolkit/+bug/1010232 | ||
436 | 6270 | if ( !$nibble_iter->one_nibble() | ||
437 | 6271 | && $tbl->{key_len} | ||
438 | 6272 | && ($expl->{key_len} || 0) < $tbl->{key_len} ) { | ||
439 | 6273 | if ( !$tbl->{warned}->{key_len}++ | ||
440 | 6274 | && $o->get('quiet') < 2 ) { | ||
441 | 6275 | die "Error copying rows at chunk " . $nibble_iter->nibble_number() | ||
442 | 6276 | . " of $tbl->{db}.$tbl->{tbl} because MySQL used " | ||
443 | 6277 | . "only " . ($expl->{key_len} || 0) . " bytes " | ||
444 | 6278 | . "of the " . ($expl->{key} || '?') . " index instead of " | ||
445 | 6279 | . $tbl->{key_len} . ". See the --[no]check-plan documentation " | ||
446 | 6280 | . "for more information.\n"; | ||
447 | 6281 | } | ||
448 | 6282 | } | ||
449 | 6283 | |||
450 | 6284 | return 1; # safe | ||
451 | 6285 | } | ||
452 | 6286 | |||
453 | 6113 | sub create_new_table{ | 6287 | sub create_new_table{ |
454 | 6114 | my (%args) = @_; | 6288 | my (%args) = @_; |
455 | 6115 | my @required_args = qw(orig_tbl Cxn Quoter OptionParser TableParser); | 6289 | my @required_args = qw(orig_tbl Cxn Quoter OptionParser TableParser); |
456 | @@ -7109,6 +7283,39 @@ | |||
457 | 7109 | 7283 | ||
458 | 7110 | Sleep time between checks for L<"--max-lag">. | 7284 | Sleep time between checks for L<"--max-lag">. |
459 | 7111 | 7285 | ||
460 | 7286 | =item --[no]check-plan | ||
461 | 7287 | |||
462 | 7288 | default: yes | ||
463 | 7289 | |||
464 | 7290 | Check query execution plans for safety. By default, this option causes | ||
465 | 7291 | the tool to run EXPLAIN before running queries that are meant to access | ||
466 | 7292 | a small amount of data, but which could access many rows if MySQL chooses a bad | ||
467 | 7293 | execution plan. These include the queries to determine chunk boundaries and the | ||
468 | 7294 | chunk queries themselves. If it appears that MySQL will use a bad query | ||
469 | 7295 | execution plan, the tool will skip the chunk of the table. | ||
470 | 7296 | |||
471 | 7297 | The tool uses several heuristics to determine whether an execution plan is bad. | ||
472 | 7298 | The first is whether EXPLAIN reports that MySQL intends to use the desired index | ||
473 | 7299 | to access the rows. If MySQL chooses a different index, the tool considers the | ||
474 | 7300 | query unsafe. | ||
475 | 7301 | |||
476 | 7302 | The tool also checks how much of the index MySQL reports that it will use for | ||
477 | 7303 | the query. The EXPLAIN output shows this in the key_len column. The tool | ||
478 | 7304 | remembers the largest key_len seen, and skips chunks where MySQL reports that it | ||
479 | 7305 | will use a smaller prefix of the index. This heuristic can be understood as | ||
480 | 7306 | skipping chunks that have a worse execution plan than other chunks. | ||
481 | 7307 | |||
482 | 7308 | The tool prints a warning the first time a chunk is skipped due to | ||
483 | 7309 | a bad execution plan in each table. Subsequent chunks are skipped silently, | ||
484 | 7310 | although you can see the count of skipped chunks in the SKIPPED column in | ||
485 | 7311 | the tool's output. | ||
486 | 7312 | |||
487 | 7313 | This option adds some setup work to each table and chunk. Although the work is | ||
488 | 7314 | not intrusive for MySQL, it results in more round-trips to the server, which | ||
489 | 7315 | consumes time. Making chunks too small will cause the overhead to become | ||
490 | 7316 | relatively larger. It is therefore recommended that you not make chunks too | ||
491 | 7317 | small, because the tool may take a very long time to complete if you do. | ||
492 | 7318 | |||
493 | 7112 | =item --[no]check-replication-filters | 7319 | =item --[no]check-replication-filters |
494 | 7113 | 7320 | ||
495 | 7114 | default: yes | 7321 | default: yes |
496 | @@ -7145,6 +7352,17 @@ | |||
497 | 7145 | a C<FORCE INDEX> clause. Be careful when using this option; a poor choice of | 7352 | a C<FORCE INDEX> clause. Be careful when using this option; a poor choice of |
498 | 7146 | index could cause bad performance. | 7353 | index could cause bad performance. |
499 | 7147 | 7354 | ||
500 | 7355 | This option supports a special syntax to select a prefix of the index instead of | ||
501 | 7356 | the entire index. The syntax is NAME:N, where NAME is the name of the index, and | ||
502 | 7357 | N is the number of columns you wish to use. This works only for compound | ||
503 | 7358 | indexes, and is useful in cases where a bug in the MySQL query optimizer | ||
504 | 7359 | (planner) causes it to scan a large range of rows instead of using the index to | ||
505 | 7360 | locate starting and ending points precisely. This problem sometimes occurs on | ||
506 | 7361 | indexes with many columns, such as 4 or more. If this happens, the tool might | ||
507 | 7362 | print a warning related to the L<"--[no]check-plan"> option. Instructing | ||
508 | 7363 | the tool to use only the first N columns from the index is a workaround for | ||
509 | 7364 | the bug in some cases. | ||
510 | 7365 | |||
511 | 7148 | =item --chunk-size | 7366 | =item --chunk-size |
512 | 7149 | 7367 | ||
513 | 7150 | type: size; default: 1000 | 7368 | type: size; default: 1000 |
514 | 7151 | 7369 | ||
515 | === modified file 'bin/pt-table-checksum' | |||
516 | --- bin/pt-table-checksum 2012-05-25 21:34:58 +0000 | |||
517 | +++ bin/pt-table-checksum 2012-06-10 17:15:24 +0000 | |||
518 | @@ -2208,8 +2208,12 @@ | |||
519 | 2208 | 2208 | ||
520 | 2209 | my @asc_cols = @{$tbl_struct->{keys}->{$index}->{cols}}; | 2209 | my @asc_cols = @{$tbl_struct->{keys}->{$index}->{cols}}; |
521 | 2210 | if ( $args{asc_first} ) { | 2210 | if ( $args{asc_first} ) { |
522 | 2211 | PTDEBUG && _d('Ascending only first column'); | ||
523 | 2211 | @asc_cols = $asc_cols[0]; | 2212 | @asc_cols = $asc_cols[0]; |
525 | 2212 | PTDEBUG && _d('Ascending only first column'); | 2213 | } |
526 | 2214 | elsif ( my $n = $args{n_index_cols} ) { | ||
527 | 2215 | PTDEBUG && _d('Ascending only first', $n, 'columns'); | ||
528 | 2216 | @asc_cols = @asc_cols[0..($n-1)]; | ||
529 | 2213 | } | 2217 | } |
530 | 2214 | PTDEBUG && _d('Will ascend columns', join(', ', @asc_cols)); | 2218 | PTDEBUG && _d('Will ascend columns', join(', ', @asc_cols)); |
531 | 2215 | 2219 | ||
532 | @@ -3594,10 +3598,11 @@ | |||
533 | 3594 | 3598 | ||
534 | 3595 | my $asc = $args{TableNibbler}->generate_asc_stmt( | 3599 | my $asc = $args{TableNibbler}->generate_asc_stmt( |
535 | 3596 | %args, | 3600 | %args, |
540 | 3597 | tbl_struct => $tbl->{tbl_struct}, | 3601 | tbl_struct => $tbl->{tbl_struct}, |
541 | 3598 | index => $index, | 3602 | index => $index, |
542 | 3599 | cols => \@cols, | 3603 | n_index_cols => $args{n_chunk_index_cols}, |
543 | 3600 | asc_only => 1, | 3604 | cols => \@cols, |
544 | 3605 | asc_only => 1, | ||
545 | 3601 | ); | 3606 | ); |
546 | 3602 | PTDEBUG && _d('Ascend params:', Dumper($asc)); | 3607 | PTDEBUG && _d('Ascend params:', Dumper($asc)); |
547 | 3603 | 3608 | ||
548 | @@ -3679,16 +3684,17 @@ | |||
549 | 3679 | 3684 | ||
550 | 3680 | $self = { | 3685 | $self = { |
551 | 3681 | %args, | 3686 | %args, |
562 | 3682 | index => $index, | 3687 | index => $index, |
563 | 3683 | limit => $limit, | 3688 | limit => $limit, |
564 | 3684 | first_lb_sql => $first_lb_sql, | 3689 | first_lb_sql => $first_lb_sql, |
565 | 3685 | last_ub_sql => $last_ub_sql, | 3690 | last_ub_sql => $last_ub_sql, |
566 | 3686 | ub_sql => $ub_sql, | 3691 | ub_sql => $ub_sql, |
567 | 3687 | nibble_sql => $nibble_sql, | 3692 | nibble_sql => $nibble_sql, |
568 | 3688 | explain_ub_sql => "EXPLAIN $ub_sql", | 3693 | explain_first_lb_sql => "EXPLAIN $first_lb_sql", |
569 | 3689 | explain_nibble_sql => $explain_nibble_sql, | 3694 | explain_ub_sql => "EXPLAIN $ub_sql", |
570 | 3690 | resume_lb_sql => $resume_lb_sql, | 3695 | explain_nibble_sql => $explain_nibble_sql, |
571 | 3691 | sql => { | 3696 | resume_lb_sql => $resume_lb_sql, |
572 | 3697 | sql => { | ||
573 | 3692 | columns => $asc->{scols}, | 3698 | columns => $asc->{scols}, |
574 | 3693 | from => $from, | 3699 | from => $from, |
575 | 3694 | where => $where, | 3700 | where => $where, |
576 | @@ -3796,10 +3802,11 @@ | |||
577 | 3796 | sub statements { | 3802 | sub statements { |
578 | 3797 | my ($self) = @_; | 3803 | my ($self) = @_; |
579 | 3798 | return { | 3804 | return { |
584 | 3799 | nibble => $self->{nibble_sth}, | 3805 | explain_first_lower_boundary => $self->{explain_first_lb_sth}, |
585 | 3800 | explain_nibble => $self->{explain_nibble_sth}, | 3806 | nibble => $self->{nibble_sth}, |
586 | 3801 | upper_boundary => $self->{ub_sth}, | 3807 | explain_nibble => $self->{explain_nibble_sth}, |
587 | 3802 | explain_upper_boundary => $self->{explain_ub_sth}, | 3808 | upper_boundary => $self->{ub_sth}, |
588 | 3809 | explain_upper_boundary => $self->{explain_ub_sth}, | ||
589 | 3803 | } | 3810 | } |
590 | 3804 | } | 3811 | } |
591 | 3805 | 3812 | ||
592 | @@ -4028,8 +4035,9 @@ | |||
593 | 4028 | $self->{explain_nibble_sth} = $dbh->prepare($self->{explain_nibble_sql}); | 4035 | $self->{explain_nibble_sth} = $dbh->prepare($self->{explain_nibble_sql}); |
594 | 4029 | 4036 | ||
595 | 4030 | if ( !$self->{one_nibble} ) { | 4037 | if ( !$self->{one_nibble} ) { |
598 | 4031 | $self->{ub_sth} = $dbh->prepare($self->{ub_sql}); | 4038 | $self->{explain_first_lb_sth} = $dbh->prepare($self->{explain_first_lb_sql}); |
599 | 4032 | $self->{explain_ub_sth} = $dbh->prepare($self->{explain_ub_sql}); | 4039 | $self->{ub_sth} = $dbh->prepare($self->{ub_sql}); |
600 | 4040 | $self->{explain_ub_sth} = $dbh->prepare($self->{explain_ub_sql}); | ||
601 | 4033 | } | 4041 | } |
602 | 4034 | 4042 | ||
603 | 4035 | return; | 4043 | return; |
604 | @@ -5113,6 +5121,7 @@ | |||
605 | 5113 | 5121 | ||
606 | 5114 | use Time::Local qw(timegm timelocal); | 5122 | use Time::Local qw(timegm timelocal); |
607 | 5115 | use Digest::MD5 qw(md5_hex); | 5123 | use Digest::MD5 qw(md5_hex); |
608 | 5124 | use B qw(); | ||
609 | 5116 | 5125 | ||
610 | 5117 | require Exporter; | 5126 | require Exporter; |
611 | 5118 | our @ISA = qw(Exporter); | 5127 | our @ISA = qw(Exporter); |
612 | @@ -5130,6 +5139,7 @@ | |||
613 | 5130 | any_unix_timestamp | 5139 | any_unix_timestamp |
614 | 5131 | make_checksum | 5140 | make_checksum |
615 | 5132 | crc32 | 5141 | crc32 |
616 | 5142 | encode_json | ||
617 | 5133 | ); | 5143 | ); |
618 | 5134 | 5144 | ||
619 | 5135 | our $mysql_ts = qr/(\d\d)(\d\d)(\d\d) +(\d+):(\d+):(\d+)(\.\d+)?/; | 5145 | our $mysql_ts = qr/(\d\d)(\d\d)(\d\d) +(\d+):(\d+):(\d+)(\.\d+)?/; |
620 | @@ -5337,6 +5347,96 @@ | |||
621 | 5337 | return $crc ^ 0xFFFFFFFF; | 5347 | return $crc ^ 0xFFFFFFFF; |
622 | 5338 | } | 5348 | } |
623 | 5339 | 5349 | ||
624 | 5350 | my $got_json = eval { require JSON }; | ||
625 | 5351 | sub encode_json { | ||
626 | 5352 | return JSON::encode_json(@_) if $got_json; | ||
627 | 5353 | my ( $data ) = @_; | ||
628 | 5354 | return (object_to_json($data) || ''); | ||
629 | 5355 | } | ||
630 | 5356 | |||
631 | 5357 | |||
632 | 5358 | sub object_to_json { | ||
633 | 5359 | my ($obj) = @_; | ||
634 | 5360 | my $type = ref($obj); | ||
635 | 5361 | |||
636 | 5362 | if($type eq 'HASH'){ | ||
637 | 5363 | return hash_to_json($obj); | ||
638 | 5364 | } | ||
639 | 5365 | elsif($type eq 'ARRAY'){ | ||
640 | 5366 | return array_to_json($obj); | ||
641 | 5367 | } | ||
642 | 5368 | else { | ||
643 | 5369 | return value_to_json($obj); | ||
644 | 5370 | } | ||
645 | 5371 | } | ||
646 | 5372 | |||
647 | 5373 | sub hash_to_json { | ||
648 | 5374 | my ($obj) = @_; | ||
649 | 5375 | my @res; | ||
650 | 5376 | for my $k ( sort { $a cmp $b } keys %$obj ) { | ||
651 | 5377 | push @res, string_to_json( $k ) | ||
652 | 5378 | . ":" | ||
653 | 5379 | . ( object_to_json( $obj->{$k} ) || value_to_json( $obj->{$k} ) ); | ||
654 | 5380 | } | ||
655 | 5381 | return '{' . ( @res ? join( ",", @res ) : '' ) . '}'; | ||
656 | 5382 | } | ||
657 | 5383 | |||
658 | 5384 | sub array_to_json { | ||
659 | 5385 | my ($obj) = @_; | ||
660 | 5386 | my @res; | ||
661 | 5387 | |||
662 | 5388 | for my $v (@$obj) { | ||
663 | 5389 | push @res, object_to_json($v) || value_to_json($v); | ||
664 | 5390 | } | ||
665 | 5391 | |||
666 | 5392 | return '[' . ( @res ? join( ",", @res ) : '' ) . ']'; | ||
667 | 5393 | } | ||
668 | 5394 | |||
669 | 5395 | sub value_to_json { | ||
670 | 5396 | my ($value) = @_; | ||
671 | 5397 | |||
672 | 5398 | return 'null' if(!defined $value); | ||
673 | 5399 | |||
674 | 5400 | my $b_obj = B::svref_2object(\$value); # for round trip problem | ||
675 | 5401 | my $flags = $b_obj->FLAGS; | ||
676 | 5402 | return $value # as is | ||
677 | 5403 | if $flags & ( B::SVp_IOK | B::SVp_NOK ) and !( $flags & B::SVp_POK ); # SvTYPE is IV or NV? | ||
678 | 5404 | |||
679 | 5405 | my $type = ref($value); | ||
680 | 5406 | |||
681 | 5407 | if( !$type ) { | ||
682 | 5408 | return string_to_json($value); | ||
683 | 5409 | } | ||
684 | 5410 | else { | ||
685 | 5411 | return 'null'; | ||
686 | 5412 | } | ||
687 | 5413 | |||
688 | 5414 | } | ||
689 | 5415 | |||
690 | 5416 | my %esc = ( | ||
691 | 5417 | "\n" => '\n', | ||
692 | 5418 | "\r" => '\r', | ||
693 | 5419 | "\t" => '\t', | ||
694 | 5420 | "\f" => '\f', | ||
695 | 5421 | "\b" => '\b', | ||
696 | 5422 | "\"" => '\"', | ||
697 | 5423 | "\\" => '\\\\', | ||
698 | 5424 | "\'" => '\\\'', | ||
699 | 5425 | ); | ||
700 | 5426 | |||
701 | 5427 | sub string_to_json { | ||
702 | 5428 | my ($arg) = @_; | ||
703 | 5429 | |||
704 | 5430 | $arg =~ s/([\x22\x5c\n\r\t\f\b])/$esc{$1}/g; | ||
705 | 5431 | $arg =~ s/\//\\\//g; | ||
706 | 5432 | $arg =~ s/([\x00-\x08\x0b\x0e-\x1f])/'\\u00' . unpack('H2', $1)/eg; | ||
707 | 5433 | |||
708 | 5434 | utf8::upgrade($arg); | ||
709 | 5435 | utf8::encode($arg); | ||
710 | 5436 | |||
711 | 5437 | return '"' . $arg . '"'; | ||
712 | 5438 | } | ||
713 | 5439 | |||
714 | 5340 | sub _d { | 5440 | sub _d { |
715 | 5341 | my ($package, undef, $line) = caller 0; | 5441 | my ($package, undef, $line) = caller 0; |
716 | 5342 | @_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; } | 5442 | @_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; } |
717 | @@ -5988,6 +6088,22 @@ | |||
718 | 5988 | } | 6088 | } |
719 | 5989 | } | 6089 | } |
720 | 5990 | 6090 | ||
721 | 6091 | # Parse --chunk-index INDEX:N where N is the number of | ||
722 | 6092 | # left-most columns of INDEX to use. | ||
723 | 6093 | # https://bugs.launchpad.net/percona-toolkit/+bug/1010232 | ||
724 | 6094 | my ($chunk_index, $n_chunk_index_cols) | ||
725 | 6095 | = split(':', $o->get('chunk-index') || ''); | ||
726 | 6096 | if ( defined $chunk_index && $chunk_index eq '' ) { | ||
727 | 6097 | $o->save_error('--chunk-index cannot be an empty string'); | ||
728 | 6098 | } | ||
729 | 6099 | if ( defined $n_chunk_index_cols | ||
730 | 6100 | && (!$n_chunk_index_cols | ||
731 | 6101 | || $n_chunk_index_cols =~ m/\D/ | ||
732 | 6102 | || $n_chunk_index_cols < 1) ) { | ||
733 | 6103 | $o->save_error('Invalid number of --chunk-index columns: ' | ||
734 | 6104 | . $n_chunk_index_cols); | ||
735 | 6105 | } | ||
736 | 6106 | |||
737 | 5991 | if ( !$o->get('help') ) { | 6107 | if ( !$o->get('help') ) { |
738 | 5992 | if ( @ARGV > 1 ) { | 6108 | if ( @ARGV > 1 ) { |
739 | 5993 | $o->save_error("More than one host specified; only one allowed"); | 6109 | $o->save_error("More than one host specified; only one allowed"); |
740 | @@ -6465,6 +6581,7 @@ | |||
741 | 6465 | my (%args) = @_; | 6581 | my (%args) = @_; |
742 | 6466 | my $tbl = $args{tbl}; | 6582 | my $tbl = $args{tbl}; |
743 | 6467 | my $nibble_iter = $args{NibbleIterator}; | 6583 | my $nibble_iter = $args{NibbleIterator}; |
744 | 6584 | my $statements = $nibble_iter->statements(); | ||
745 | 6468 | my $oktonibble = 1; | 6585 | my $oktonibble = 1; |
746 | 6469 | 6586 | ||
747 | 6470 | if ( $last_chunk ) { # resuming | 6587 | if ( $last_chunk ) { # resuming |
748 | @@ -6493,7 +6610,7 @@ | |||
749 | 6493 | print "--\n", | 6610 | print "--\n", |
750 | 6494 | "-- $tbl->{db}.$tbl->{tbl}\n", | 6611 | "-- $tbl->{db}.$tbl->{tbl}\n", |
751 | 6495 | "--\n\n"; | 6612 | "--\n\n"; |
753 | 6496 | my $statements = $nibble_iter->statements(); | 6613 | |
754 | 6497 | foreach my $sth ( sort keys %$statements ) { | 6614 | foreach my $sth ( sort keys %$statements ) { |
755 | 6498 | next if $sth =~ m/^explain/; | 6615 | next if $sth =~ m/^explain/; |
756 | 6499 | if ( $statements->{$sth} ) { | 6616 | if ( $statements->{$sth} ) { |
757 | @@ -6551,6 +6668,34 @@ | |||
758 | 6551 | $oktonibble = 0; | 6668 | $oktonibble = 0; |
759 | 6552 | } | 6669 | } |
760 | 6553 | } | 6670 | } |
761 | 6671 | else { # chunking the table | ||
762 | 6672 | if ( $o->get('check-plan') ) { | ||
763 | 6673 | my $expl = explain_statement( | ||
764 | 6674 | sth => $statements->{explain_first_lower_boundary}, | ||
765 | 6675 | tbl => $tbl, | ||
766 | 6676 | vals => [], | ||
767 | 6677 | ); | ||
768 | 6678 | if ( !$expl->{key} | ||
769 | 6679 | || lc($expl->{key}) ne lc($nibble_iter->nibble_index()) ) | ||
770 | 6680 | { | ||
771 | 6681 | die "Cannot determine the key_len of the chunk index " | ||
772 | 6682 | . "because MySQL chose " | ||
773 | 6683 | . ($expl->{key} ? "the $expl->{key}" : "no") . " index " | ||
774 | 6684 | . "instead of the " . $nibble_iter->nibble_index() | ||
775 | 6685 | . " index for the first lower boundary statement. " | ||
776 | 6686 | . "See --[no]check-plan in the documentation for more " | ||
777 | 6687 | . "information."; | ||
778 | 6688 | } | ||
779 | 6689 | elsif ( !$expl->{key_len} ) { | ||
780 | 6690 | die "The key_len of the $expl->{key} index is " | ||
781 | 6691 | . (defined $expl->{key_len} ? "zero" : "NULL") | ||
782 | 6692 | . ", but this should not be possible. " | ||
783 | 6693 | . "See --[no]check-plan in the documentation for more " | ||
784 | 6694 | . "information."; | ||
785 | 6695 | } | ||
786 | 6696 | $tbl->{key_len} = $expl->{key_len}; | ||
787 | 6697 | } | ||
788 | 6698 | } | ||
789 | 6554 | 6699 | ||
790 | 6555 | if ( $oktonibble && $o->get('empty-replicate-table') ) { | 6700 | if ( $oktonibble && $o->get('empty-replicate-table') ) { |
791 | 6556 | use_repl_db( | 6701 | use_repl_db( |
792 | @@ -6604,16 +6749,14 @@ | |||
793 | 6604 | ne lc($nibble_iter->nibble_index() || '') ) { | 6749 | ne lc($nibble_iter->nibble_index() || '') ) { |
794 | 6605 | PTDEBUG && _d('Cannot nibble next chunk, aborting table'); | 6750 | PTDEBUG && _d('Cannot nibble next chunk, aborting table'); |
795 | 6606 | if ( $o->get('quiet') < 2 ) { | 6751 | if ( $o->get('quiet') < 2 ) { |
798 | 6607 | my $msg | 6752 | warn ts("Aborting table $tbl->{db}.$tbl->{tbl} at chunk " |
797 | 6608 | = "Aborting table $tbl->{db}.$tbl->{tbl} at chunk " | ||
799 | 6609 | . ($nibble_iter->nibble_number() + 1) | 6753 | . ($nibble_iter->nibble_number() + 1) |
800 | 6610 | . " because it is not safe to chunk. Chunking should " | 6754 | . " because it is not safe to chunk. Chunking should " |
801 | 6611 | . "use the " | 6755 | . "use the " |
802 | 6612 | . ($nibble_iter->nibble_index() || '?') | 6756 | . ($nibble_iter->nibble_index() || '?') |
804 | 6613 | . " index, but MySQL EXPLAIN reports that " | 6757 | . " index, but MySQL chose " |
805 | 6614 | . ($expl->{key} ? "the $expl->{key}" : "no") | 6758 | . ($expl->{key} ? "the $expl->{key}" : "no") |
808 | 6615 | . " index will be used.\n"; | 6759 | . " index.\n"); |
807 | 6616 | warn ts($msg); | ||
809 | 6617 | } | 6760 | } |
810 | 6618 | $tbl->{checksum_results}->{errors}++; | 6761 | $tbl->{checksum_results}->{errors}++; |
811 | 6619 | return 0; # stop nibbling table | 6762 | return 0; # stop nibbling table |
812 | @@ -6658,43 +6801,13 @@ | |||
813 | 6658 | return 0; # next boundary | 6801 | return 0; # next boundary |
814 | 6659 | } | 6802 | } |
815 | 6660 | 6803 | ||
853 | 6661 | # If the table is being chunk (i.e., it's not small enough to be | 6804 | # Skip this nibble unless it's safe. |
854 | 6662 | # consumed by one nibble), then check index usage and chunk size. | 6805 | return 0 unless nibble_is_safe( |
855 | 6663 | # XXX This call and others like it are relying on a Perl oddity. | 6806 | %args, |
856 | 6664 | # See https://bugs.launchpad.net/percona-toolkit/+bug/987393 | 6807 | OptionParser => $o, |
857 | 6665 | if ( !$nibble_iter->one_nibble() ) { | 6808 | ); |
858 | 6666 | my $expl = explain_statement( | 6809 | |
859 | 6667 | tbl => $tbl, | 6810 | # Exec and time the nibble. |
823 | 6668 | sth => $sth->{explain_nibble}, | ||
824 | 6669 | vals => [ @{$boundary->{lower}}, @{$boundary->{upper}} ], | ||
825 | 6670 | ); | ||
826 | 6671 | my $oversize_chunk | ||
827 | 6672 | = $limit ? ($expl->{rows} || 0) >= $tbl->{chunk_size} * $limit | ||
828 | 6673 | : 0; | ||
829 | 6674 | |||
830 | 6675 | # Ensure that MySQL is using the chunk index. | ||
831 | 6676 | if ( lc($expl->{key} || '') | ||
832 | 6677 | ne lc($nibble_iter->nibble_index() || '') ) { | ||
833 | 6678 | PTDEBUG && _d('Chunk', $args{nibbleno}, 'of table', | ||
834 | 6679 | "$tbl->{db}.$tbl->{tbl} not using chunk index, skipping"); | ||
835 | 6680 | return 0; # next boundary | ||
836 | 6681 | } | ||
837 | 6682 | |||
838 | 6683 | # Check chunk size limit if the upper boundary and next lower | ||
839 | 6684 | # boundary are identical. | ||
840 | 6685 | if ( $limit ) { | ||
841 | 6686 | my $boundary = $nibble_iter->boundaries(); | ||
842 | 6687 | if ( $nibble_iter->identical_boundaries( | ||
843 | 6688 | $boundary->{upper}, $boundary->{next_lower}) | ||
844 | 6689 | && $oversize_chunk ) { | ||
845 | 6690 | PTDEBUG && _d('Chunk', $args{nibbleno}, 'of table', | ||
846 | 6691 | "$tbl->{db}.$tbl->{tbl} is too large, skipping"); | ||
847 | 6692 | return 0; # next boundary | ||
848 | 6693 | } | ||
849 | 6694 | } | ||
850 | 6695 | } | ||
851 | 6696 | |||
852 | 6697 | # Exec and time the chunk checksum query. | ||
860 | 6698 | $tbl->{nibble_time} = exec_nibble( | 6811 | $tbl->{nibble_time} = exec_nibble( |
861 | 6699 | %args, | 6812 | %args, |
862 | 6700 | Retry => $retry, | 6813 | Retry => $retry, |
863 | @@ -6776,7 +6889,7 @@ | |||
864 | 6776 | $tbl->{chunk_size} = 1; | 6889 | $tbl->{chunk_size} = 1; |
865 | 6777 | 6890 | ||
866 | 6778 | # This warning is printed once per table. | 6891 | # This warning is printed once per table. |
868 | 6779 | if ( !$tbl->{warned_slow} && $o->get('quiet') < 2 ) { | 6892 | if ( !$tbl->{warned}->{slow}++ && $o->get('quiet') < 2 ) { |
869 | 6780 | warn ts("Checksum queries for table " | 6893 | warn ts("Checksum queries for table " |
870 | 6781 | . "$tbl->{db}.$tbl->{tbl} are executing very slowly. " | 6894 | . "$tbl->{db}.$tbl->{tbl} are executing very slowly. " |
871 | 6782 | . "--chunk-size has been automatically reduced to 1. " | 6895 | . "--chunk-size has been automatically reduced to 1. " |
872 | @@ -6786,7 +6899,6 @@ | |||
873 | 6786 | . "selected $cnt rows and took " | 6899 | . "selected $cnt rows and took " |
874 | 6787 | . sprintf('%.3f', $tbl->{nibble_time}) | 6900 | . sprintf('%.3f', $tbl->{nibble_time}) |
875 | 6788 | . " seconds to execute.\n"); | 6901 | . " seconds to execute.\n"); |
876 | 6789 | $tbl->{warned_slow} = 1; | ||
877 | 6790 | } | 6902 | } |
878 | 6791 | } | 6903 | } |
879 | 6792 | 6904 | ||
880 | @@ -6918,22 +7030,23 @@ | |||
881 | 6918 | my $nibble_iter; | 7030 | my $nibble_iter; |
882 | 6919 | eval { | 7031 | eval { |
883 | 6920 | $nibble_iter = new OobNibbleIterator( | 7032 | $nibble_iter = new OobNibbleIterator( |
900 | 6921 | Cxn => $master_cxn, | 7033 | Cxn => $master_cxn, |
901 | 6922 | tbl => $tbl, | 7034 | tbl => $tbl, |
902 | 6923 | chunk_size => $tbl->{chunk_size}, | 7035 | chunk_size => $tbl->{chunk_size}, |
903 | 6924 | chunk_index => $o->get('chunk-index'), | 7036 | chunk_index => $chunk_index, |
904 | 6925 | dml => $checksum_dml, | 7037 | n_chunk_index_cols => $n_chunk_index_cols, |
905 | 6926 | select => $checksum_cols, | 7038 | dml => $checksum_dml, |
906 | 6927 | past_dml => $checksum_dml, | 7039 | select => $checksum_cols, |
907 | 6928 | past_select => $past_cols, | 7040 | past_dml => $checksum_dml, |
908 | 6929 | callbacks => $callbacks, | 7041 | past_select => $past_cols, |
909 | 6930 | resume => $last_chunk, | 7042 | callbacks => $callbacks, |
910 | 6931 | OptionParser => $o, | 7043 | resume => $last_chunk, |
911 | 6932 | Quoter => $q, | 7044 | OptionParser => $o, |
912 | 6933 | TableNibbler => $tn, | 7045 | Quoter => $q, |
913 | 6934 | TableParser => $tp, | 7046 | TableNibbler => $tn, |
914 | 6935 | RowChecksum => $rc, | 7047 | TableParser => $tp, |
915 | 6936 | comments => { | 7048 | RowChecksum => $rc, |
916 | 7049 | comments => { | ||
917 | 6937 | bite => "checksum table", | 7050 | bite => "checksum table", |
918 | 6938 | nibble => "checksum chunk", | 7051 | nibble => "checksum chunk", |
919 | 6939 | }, | 7052 | }, |
920 | @@ -7008,6 +7121,84 @@ | |||
921 | 7008 | return $msg ? "$ts $msg" : $ts; | 7121 | return $msg ? "$ts $msg" : $ts; |
922 | 7009 | } | 7122 | } |
923 | 7010 | 7123 | ||
924 | 7124 | sub nibble_is_safe { | ||
925 | 7125 | my (%args) = @_; | ||
926 | 7126 | my @required_args = qw(Cxn tbl NibbleIterator OptionParser); | ||
927 | 7127 | foreach my $arg ( @required_args ) { | ||
928 | 7128 | die "I need a $arg argument" unless $args{$arg}; | ||
929 | 7129 | } | ||
930 | 7130 | my ($cxn, $tbl, $nibble_iter, $o)= @args{@required_args}; | ||
931 | 7131 | |||
932 | 7132 | # EXPLAIN the checksum chunk query to get its row estimate and index. | ||
933 | 7133 | # XXX This call and others like it are relying on a Perl oddity. | ||
934 | 7134 | # See https://bugs.launchpad.net/percona-toolkit/+bug/987393 | ||
935 | 7135 | my $sth = $nibble_iter->statements(); | ||
936 | 7136 | my $boundary = $nibble_iter->boundaries(); | ||
937 | 7137 | my $expl = explain_statement( | ||
938 | 7138 | tbl => $tbl, | ||
939 | 7139 | sth => $sth->{explain_nibble}, | ||
940 | 7140 | vals => [ @{$boundary->{lower}}, @{$boundary->{upper}} ], | ||
941 | 7141 | ); | ||
942 | 7142 | |||
943 | 7143 | # Ensure that MySQL is using the chunk index if the table is being chunked. | ||
944 | 7144 | if ( !$nibble_iter->one_nibble() | ||
945 | 7145 | && lc($expl->{key} || '') ne lc($nibble_iter->nibble_index() || '') ) { | ||
946 | 7146 | if ( !$tbl->{warned}->{not_using_chunk_index}++ | ||
947 | 7147 | && $o->get('quiet') < 2 ) { | ||
948 | 7148 | warn ts("Skipping chunk " . $nibble_iter->nibble_number() | ||
949 | 7149 | . " of $tbl->{db}.$tbl->{tbl} because MySQL chose " | ||
950 | 7150 | . ($expl->{key} ? "the $expl->{key}" : "no") . " index " | ||
951 | 7151 | . " instead of the " . $nibble_iter->nibble_index() . "index.\n"); | ||
952 | 7152 | } | ||
953 | 7153 | return 0; # not safe | ||
954 | 7154 | } | ||
955 | 7155 | |||
956 | 7156 | # Ensure that the chunk isn't too large if there's a --chunk-size-limit. | ||
957 | 7157 | # If single-chunking the table, this has already been checked, so it | ||
958 | 7158 | # shouldn't have changed. If chunking the table with a non-unique key, | ||
959 | 7159 | # oversize chunks are possible. | ||
960 | 7160 | if ( my $limit = $o->get('chunk-size-limit') ) { | ||
961 | 7161 | my $oversize_chunk | ||
962 | 7162 | = $limit ? ($expl->{rows} || 0) >= $tbl->{chunk_size} * $limit | ||
963 | 7163 | : 0; | ||
964 | 7164 | if ( $oversize_chunk | ||
965 | 7165 | && $nibble_iter->identical_boundaries($boundary->{upper}, | ||
966 | 7166 | $boundary->{next_lower}) ) { | ||
967 | 7167 | if ( !$tbl->{warned}->{oversize_chunk}++ | ||
968 | 7168 | && $o->get('quiet') < 2 ) { | ||
969 | 7169 | warn ts("Skipping chunk " . $nibble_iter->nibble_number() | ||
970 | 7170 | . " of $tbl->{db}.$tbl->{tbl} because it is oversized. " | ||
971 | 7171 | . "The current chunk size limit is " | ||
972 | 7172 | . ($tbl->{chunk_size} * $limit) | ||
973 | 7173 | . " rows (chunk size=$tbl->{chunk_size}" | ||
974 | 7174 | . " * chunk size limit=$limit), but MySQL estimates " | ||
975 | 7175 | . "that there are " . ($expl->{rows} || 0) | ||
976 | 7176 | . " rows in the chunk.\n"); | ||
977 | 7177 | } | ||
978 | 7178 | return 0; # not safe | ||
979 | 7179 | } | ||
980 | 7180 | } | ||
981 | 7181 | |||
982 | 7182 | # Ensure that MySQL is still using the entire index. | ||
983 | 7183 | # https://bugs.launchpad.net/percona-toolkit/+bug/1010232 | ||
984 | 7184 | if ( !$nibble_iter->one_nibble() | ||
985 | 7185 | && $tbl->{key_len} | ||
986 | 7186 | && ($expl->{key_len} || 0) < $tbl->{key_len} ) { | ||
987 | 7187 | if ( !$tbl->{warned}->{key_len}++ | ||
988 | 7188 | && $o->get('quiet') < 2 ) { | ||
989 | 7189 | warn ts("Skipping chunk " . $nibble_iter->nibble_number() | ||
990 | 7190 | . " of $tbl->{db}.$tbl->{tbl} because MySQL used " | ||
991 | 7191 | . "only " . ($expl->{key_len} || 0) . " bytes " | ||
992 | 7192 | . "of the " . ($expl->{key} || '?') . " index instead of " | ||
993 | 7193 | . $tbl->{key_len} . ". See the --[no]check-plan documentation " | ||
994 | 7194 | . "for more information.\n"); | ||
995 | 7195 | } | ||
996 | 7196 | return 0; # not safe | ||
997 | 7197 | } | ||
998 | 7198 | |||
999 | 7199 | return 1; # safe | ||
1000 | 7200 | } | ||
1001 | 7201 | |||
1002 | 7011 | sub exec_nibble { | 7202 | sub exec_nibble { |
1003 | 7012 | my (%args) = @_; | 7203 | my (%args) = @_; |
1004 | 7013 | my @required_args = qw(Cxn tbl NibbleIterator Retry Quoter OptionParser); | 7204 | my @required_args = qw(Cxn tbl NibbleIterator Retry Quoter OptionParser); |
1005 | @@ -7074,7 +7265,7 @@ | |||
1006 | 7074 | && (!$warn_code{$code}->{pattern} | 7265 | && (!$warn_code{$code}->{pattern} |
1007 | 7075 | || $message =~ m/$warn_code{$code}->{pattern}/) ) | 7266 | || $message =~ m/$warn_code{$code}->{pattern}/) ) |
1008 | 7076 | { | 7267 | { |
1010 | 7077 | if ( !$tbl->{"warned_code_$code"} ) { # warn once per table | 7268 | if ( !$tbl->{warned}->{$code}++ ) { # warn once per table |
1011 | 7078 | if ( $o->get('quiet') < 2 ) { | 7269 | if ( $o->get('quiet') < 2 ) { |
1012 | 7079 | warn ts("Checksum query for table $tbl->{db}.$tbl->{tbl} " | 7270 | warn ts("Checksum query for table $tbl->{db}.$tbl->{tbl} " |
1013 | 7080 | . "caused MySQL error $code: " | 7271 | . "caused MySQL error $code: " |
1014 | @@ -7083,7 +7274,6 @@ | |||
1015 | 7083 | : $message) | 7274 | : $message) |
1016 | 7084 | . "\n"); | 7275 | . "\n"); |
1017 | 7085 | } | 7276 | } |
1018 | 7086 | $tbl->{"warned_code_$code"} = 1; | ||
1019 | 7087 | $tbl->{checksum_results}->{errors}++; | 7277 | $tbl->{checksum_results}->{errors}++; |
1020 | 7088 | } | 7278 | } |
1021 | 7089 | } | 7279 | } |
1022 | @@ -7910,6 +8100,39 @@ | |||
1023 | 7910 | 8100 | ||
1024 | 7911 | Sleep time between checks for L<"--max-lag">. | 8101 | Sleep time between checks for L<"--max-lag">. |
1025 | 7912 | 8102 | ||
1026 | 8103 | =item --[no]check-plan | ||
1027 | 8104 | |||
1028 | 8105 | default: yes | ||
1029 | 8106 | |||
1030 | 8107 | Check query execution plans for safety. By default, this option causes | ||
1031 | 8108 | pt-table-checksum to run EXPLAIN before running queries that are meant to access | ||
1032 | 8109 | a small amount of data, but which could access many rows if MySQL chooses a bad | ||
1033 | 8110 | execution plan. These include the queries to determine chunk boundaries and the | ||
1034 | 8111 | chunk queries themselves. If it appears that MySQL will use a bad query | ||
1035 | 8112 | execution plan, the tool will skip the chunk of the table. | ||
1036 | 8113 | |||
1037 | 8114 | The tool uses several heuristics to determine whether an execution plan is bad. | ||
1038 | 8115 | The first is whether EXPLAIN reports that MySQL intends to use the desired index | ||
1039 | 8116 | to access the rows. If MySQL chooses a different index, the tool considers the | ||
1040 | 8117 | query unsafe. | ||
1041 | 8118 | |||
1042 | 8119 | The tool also checks how much of the index MySQL reports that it will use for | ||
1043 | 8120 | the query. The EXPLAIN output shows this in the key_len column. The tool | ||
1044 | 8121 | remembers the largest key_len seen, and skips chunks where MySQL reports that it | ||
1045 | 8122 | will use a smaller prefix of the index. This heuristic can be understood as | ||
1046 | 8123 | skipping chunks that have a worse execution plan than other chunks. | ||
1047 | 8124 | |||
1048 | 8125 | The tool prints a warning the first time a chunk is skipped due to | ||
1049 | 8126 | a bad execution plan in each table. Subsequent chunks are skipped silently, | ||
1050 | 8127 | although you can see the count of skipped chunks in the SKIPPED column in | ||
1051 | 8128 | the tool's output. | ||
1052 | 8129 | |||
1053 | 8130 | This option adds some setup work to each table and chunk. Although the work is | ||
1054 | 8131 | not intrusive for MySQL, it results in more round-trips to the server, which | ||
1055 | 8132 | consumes time. Making chunks too small will cause the overhead to become | ||
1056 | 8133 | relatively larger. It is therefore recommended that you not make chunks too | ||
1057 | 8134 | small, because the tool may take a very long time to complete if you do. | ||
1058 | 8135 | |||
1059 | 7913 | =item --[no]check-replication-filters | 8136 | =item --[no]check-replication-filters |
1060 | 7914 | 8137 | ||
1061 | 7915 | default: yes; group: Safety | 8138 | default: yes; group: Safety |
1062 | @@ -7959,12 +8182,24 @@ | |||
1063 | 7959 | This is probably best to use when you are checksumming only a single table, not | 8182 | This is probably best to use when you are checksumming only a single table, not |
1064 | 7960 | an entire server. | 8183 | an entire server. |
1065 | 7961 | 8184 | ||
1066 | 8185 | This option supports a special syntax to select a prefix of the index instead of | ||
1067 | 8186 | the entire index. The syntax is NAME:N, where NAME is the name of the index, and | ||
1068 | 8187 | N is the number of columns you wish to use. This works only for compound | ||
1069 | 8188 | indexes, and is useful in cases where a bug in the MySQL query optimizer | ||
1070 | 8189 | (planner) causes it to scan a large range of rows instead of using the index to | ||
1071 | 8190 | locate starting and ending points precisely. This problem sometimes occurs on | ||
1072 | 8191 | indexes with many columns, such as 4 or more. If this happens, the tool might | ||
1073 | 8192 | print a warning related to the L<"--[no]check-plan"> option. Instructing | ||
1074 | 8193 | the tool to use only the first N columns from the index is a workaround for | ||
1075 | 8194 | the bug in some cases. | ||
1076 | 8195 | |||
1077 | 7962 | =item --chunk-size | 8196 | =item --chunk-size |
1078 | 7963 | 8197 | ||
1079 | 7964 | type: size; default: 1000 | 8198 | type: size; default: 1000 |
1080 | 7965 | 8199 | ||
1081 | 7966 | Number of rows to select for each checksum query. Allowable suffixes are | 8200 | Number of rows to select for each checksum query. Allowable suffixes are |
1083 | 7967 | k, M, G. | 8201 | k, M, G. You should not use this option in most cases; prefer L<"--chunk-time"> |
1084 | 8202 | instead. | ||
1085 | 7968 | 8203 | ||
1086 | 7969 | This option can override the default behavior, which is to adjust chunk size | 8204 | This option can override the default behavior, which is to adjust chunk size |
1087 | 7970 | dynamically to try to make chunks run in exactly L<"--chunk-time"> seconds. | 8205 | dynamically to try to make chunks run in exactly L<"--chunk-time"> seconds. |
1088 | @@ -7980,6 +8215,9 @@ | |||
1089 | 7980 | 10,000 rows large. Such a chunk will probably be skipped because of | 8215 | 10,000 rows large. Such a chunk will probably be skipped because of |
1090 | 7981 | L<"--chunk-size-limit">. | 8216 | L<"--chunk-size-limit">. |
1091 | 7982 | 8217 | ||
1092 | 8218 | Selecting a small chunk size will cause the tool to become much slower, in part | ||
1093 | 8219 | because of the setup work required for L<"--[no]check-plan">. | ||
1094 | 8220 | |||
1095 | 7983 | =item --chunk-size-limit | 8221 | =item --chunk-size-limit |
1096 | 7984 | 8222 | ||
1097 | 7985 | type: float; default: 2.0; group: Safety | 8223 | type: float; default: 2.0; group: Safety |
1098 | 7986 | 8224 | ||
1099 | === modified file 'lib/NibbleIterator.pm' | |||
1100 | --- lib/NibbleIterator.pm 2012-05-10 16:21:44 +0000 | |||
1101 | +++ lib/NibbleIterator.pm 2012-06-10 17:15:24 +0000 | |||
1102 | @@ -124,10 +124,11 @@ | |||
1103 | 124 | # Figure out how to nibble the table with the index. | 124 | # Figure out how to nibble the table with the index. |
1104 | 125 | my $asc = $args{TableNibbler}->generate_asc_stmt( | 125 | my $asc = $args{TableNibbler}->generate_asc_stmt( |
1105 | 126 | %args, | 126 | %args, |
1110 | 127 | tbl_struct => $tbl->{tbl_struct}, | 127 | tbl_struct => $tbl->{tbl_struct}, |
1111 | 128 | index => $index, | 128 | index => $index, |
1112 | 129 | cols => \@cols, | 129 | n_index_cols => $args{n_chunk_index_cols}, |
1113 | 130 | asc_only => 1, | 130 | cols => \@cols, |
1114 | 131 | asc_only => 1, | ||
1115 | 131 | ); | 132 | ); |
1116 | 132 | PTDEBUG && _d('Ascend params:', Dumper($asc)); | 133 | PTDEBUG && _d('Ascend params:', Dumper($asc)); |
1117 | 133 | 134 | ||
1118 | @@ -229,16 +230,17 @@ | |||
1119 | 229 | 230 | ||
1120 | 230 | $self = { | 231 | $self = { |
1121 | 231 | %args, | 232 | %args, |
1132 | 232 | index => $index, | 233 | index => $index, |
1133 | 233 | limit => $limit, | 234 | limit => $limit, |
1134 | 234 | first_lb_sql => $first_lb_sql, | 235 | first_lb_sql => $first_lb_sql, |
1135 | 235 | last_ub_sql => $last_ub_sql, | 236 | last_ub_sql => $last_ub_sql, |
1136 | 236 | ub_sql => $ub_sql, | 237 | ub_sql => $ub_sql, |
1137 | 237 | nibble_sql => $nibble_sql, | 238 | nibble_sql => $nibble_sql, |
1138 | 238 | explain_ub_sql => "EXPLAIN $ub_sql", | 239 | explain_first_lb_sql => "EXPLAIN $first_lb_sql", |
1139 | 239 | explain_nibble_sql => $explain_nibble_sql, | 240 | explain_ub_sql => "EXPLAIN $ub_sql", |
1140 | 240 | resume_lb_sql => $resume_lb_sql, | 241 | explain_nibble_sql => $explain_nibble_sql, |
1141 | 241 | sql => { | 242 | resume_lb_sql => $resume_lb_sql, |
1142 | 243 | sql => { | ||
1143 | 242 | columns => $asc->{scols}, | 244 | columns => $asc->{scols}, |
1144 | 243 | from => $from, | 245 | from => $from, |
1145 | 244 | where => $where, | 246 | where => $where, |
1146 | @@ -357,10 +359,11 @@ | |||
1147 | 357 | sub statements { | 359 | sub statements { |
1148 | 358 | my ($self) = @_; | 360 | my ($self) = @_; |
1149 | 359 | return { | 361 | return { |
1154 | 360 | nibble => $self->{nibble_sth}, | 362 | explain_first_lower_boundary => $self->{explain_first_lb_sth}, |
1155 | 361 | explain_nibble => $self->{explain_nibble_sth}, | 363 | nibble => $self->{nibble_sth}, |
1156 | 362 | upper_boundary => $self->{ub_sth}, | 364 | explain_nibble => $self->{explain_nibble_sth}, |
1157 | 363 | explain_upper_boundary => $self->{explain_ub_sth}, | 365 | upper_boundary => $self->{ub_sth}, |
1158 | 366 | explain_upper_boundary => $self->{explain_ub_sth}, | ||
1159 | 364 | } | 367 | } |
1160 | 365 | } | 368 | } |
1161 | 366 | 369 | ||
1162 | @@ -613,8 +616,9 @@ | |||
1163 | 613 | $self->{explain_nibble_sth} = $dbh->prepare($self->{explain_nibble_sql}); | 616 | $self->{explain_nibble_sth} = $dbh->prepare($self->{explain_nibble_sql}); |
1164 | 614 | 617 | ||
1165 | 615 | if ( !$self->{one_nibble} ) { | 618 | if ( !$self->{one_nibble} ) { |
1168 | 616 | $self->{ub_sth} = $dbh->prepare($self->{ub_sql}); | 619 | $self->{explain_first_lb_sth} = $dbh->prepare($self->{explain_first_lb_sql}); |
1169 | 617 | $self->{explain_ub_sth} = $dbh->prepare($self->{explain_ub_sql}); | 620 | $self->{ub_sth} = $dbh->prepare($self->{ub_sql}); |
1170 | 621 | $self->{explain_ub_sth} = $dbh->prepare($self->{explain_ub_sql}); | ||
1171 | 618 | } | 622 | } |
1172 | 619 | 623 | ||
1173 | 620 | return; | 624 | return; |
1174 | 621 | 625 | ||
1175 | === modified file 'lib/TableNibbler.pm' | |||
1176 | --- lib/TableNibbler.pm 2012-01-19 19:46:56 +0000 | |||
1177 | +++ lib/TableNibbler.pm 2012-06-10 17:15:24 +0000 | |||
1178 | @@ -41,6 +41,7 @@ | |||
1179 | 41 | # * tbl_struct Hashref returned from TableParser::parse(). | 41 | # * tbl_struct Hashref returned from TableParser::parse(). |
1180 | 42 | # * cols Arrayref of columns to SELECT from the table | 42 | # * cols Arrayref of columns to SELECT from the table |
1181 | 43 | # * index Which index to ascend; optional. | 43 | # * index Which index to ascend; optional. |
1182 | 44 | # * n_index_cols The number of left-most index columns to use. | ||
1183 | 44 | # * asc_only Whether to ascend strictly, that is, the WHERE clause for | 45 | # * asc_only Whether to ascend strictly, that is, the WHERE clause for |
1184 | 45 | # the asc_stmt will fetch the next row > the given arguments. | 46 | # the asc_stmt will fetch the next row > the given arguments. |
1185 | 46 | # The option is to fetch the row >=, which could loop | 47 | # The option is to fetch the row >=, which could loop |
1186 | @@ -77,8 +78,12 @@ | |||
1187 | 77 | # These are the columns we'll ascend. | 78 | # These are the columns we'll ascend. |
1188 | 78 | my @asc_cols = @{$tbl_struct->{keys}->{$index}->{cols}}; | 79 | my @asc_cols = @{$tbl_struct->{keys}->{$index}->{cols}}; |
1189 | 79 | if ( $args{asc_first} ) { | 80 | if ( $args{asc_first} ) { |
1190 | 81 | PTDEBUG && _d('Ascending only first column'); | ||
1191 | 80 | @asc_cols = $asc_cols[0]; | 82 | @asc_cols = $asc_cols[0]; |
1193 | 81 | PTDEBUG && _d('Ascending only first column'); | 83 | } |
1194 | 84 | elsif ( my $n = $args{n_index_cols} ) { | ||
1195 | 85 | PTDEBUG && _d('Ascending only first', $n, 'columns'); | ||
1196 | 86 | @asc_cols = @asc_cols[0..($n-1)]; | ||
1197 | 82 | } | 87 | } |
1198 | 83 | PTDEBUG && _d('Will ascend columns', join(', ', @asc_cols)); | 88 | PTDEBUG && _d('Will ascend columns', join(', ', @asc_cols)); |
1199 | 84 | 89 | ||
1200 | 85 | 90 | ||
1201 | === modified file 't/lib/TableNibbler.t' | |||
1202 | --- t/lib/TableNibbler.t 2012-03-06 13:56:08 +0000 | |||
1203 | +++ t/lib/TableNibbler.t 2012-06-10 17:15:24 +0000 | |||
1204 | @@ -9,7 +9,7 @@ | |||
1205 | 9 | use strict; | 9 | use strict; |
1206 | 10 | use warnings FATAL => 'all'; | 10 | use warnings FATAL => 'all'; |
1207 | 11 | use English qw(-no_match_vars); | 11 | use English qw(-no_match_vars); |
1209 | 12 | use Test::More tests => 24; | 12 | use Test::More tests => 25; |
1210 | 13 | 13 | ||
1211 | 14 | use TableParser; | 14 | use TableParser; |
1212 | 15 | use TableNibbler; | 15 | use TableNibbler; |
1213 | @@ -299,6 +299,34 @@ | |||
1214 | 299 | 299 | ||
1215 | 300 | is_deeply( | 300 | is_deeply( |
1216 | 301 | $n->generate_asc_stmt( | 301 | $n->generate_asc_stmt( |
1217 | 302 | tbl_struct => $t, | ||
1218 | 303 | cols => $t->{cols}, | ||
1219 | 304 | index => 'rental_date', | ||
1220 | 305 | n_index_cols => 2, | ||
1221 | 306 | ), | ||
1222 | 307 | { | ||
1223 | 308 | cols => [qw(rental_id rental_date inventory_id customer_id | ||
1224 | 309 | return_date staff_id last_update)], | ||
1225 | 310 | index => 'rental_date', | ||
1226 | 311 | where => '((`rental_date` > ?) OR (`rental_date` = ? AND `inventory_id` >= ?))', | ||
1227 | 312 | slice => [qw(1 1 2)], | ||
1228 | 313 | scols => [qw(rental_date rental_date inventory_id)], | ||
1229 | 314 | boundaries => { | ||
1230 | 315 | '<' => | ||
1231 | 316 | '((`rental_date` < ?) OR (`rental_date` = ? AND `inventory_id` < ?))', | ||
1232 | 317 | '<=' => | ||
1233 | 318 | '((`rental_date` < ?) OR (`rental_date` = ? AND `inventory_id` <= ?))', | ||
1234 | 319 | '>' => | ||
1235 | 320 | '((`rental_date` > ?) OR (`rental_date` = ? AND `inventory_id` > ?))', | ||
1236 | 321 | '>=' => | ||
1237 | 322 | '((`rental_date` > ?) OR (`rental_date` = ? AND `inventory_id` >= ?))' | ||
1238 | 323 | }, | ||
1239 | 324 | }, | ||
1240 | 325 | 'Use only N left-most columns of the index', | ||
1241 | 326 | ); | ||
1242 | 327 | |||
1243 | 328 | is_deeply( | ||
1244 | 329 | $n->generate_asc_stmt( | ||
1245 | 302 | tbl_struct => $t, | 330 | tbl_struct => $t, |
1246 | 303 | cols => $t->{cols}, | 331 | cols => $t->{cols}, |
1247 | 304 | index => 'rental_date', | 332 | index => 'rental_date', |
1248 | 305 | 333 | ||
1249 | === modified file 't/pt-table-checksum/chunk_index.t' | |||
1250 | --- t/pt-table-checksum/chunk_index.t 2012-05-04 23:46:34 +0000 | |||
1251 | +++ t/pt-table-checksum/chunk_index.t 2012-06-10 17:15:24 +0000 | |||
1252 | @@ -25,7 +25,7 @@ | |||
1253 | 25 | plan skip_all => 'Cannot connect to sandbox master'; | 25 | plan skip_all => 'Cannot connect to sandbox master'; |
1254 | 26 | } | 26 | } |
1255 | 27 | else { | 27 | else { |
1257 | 28 | plan tests => 11; | 28 | plan tests => 14; |
1258 | 29 | } | 29 | } |
1259 | 30 | 30 | ||
1260 | 31 | # The sandbox servers run with lock_wait_timeout=3 and it's not dynamic | 31 | # The sandbox servers run with lock_wait_timeout=3 and it's not dynamic |
1261 | @@ -158,6 +158,50 @@ | |||
1262 | 158 | ); | 158 | ); |
1263 | 159 | 159 | ||
1264 | 160 | # ############################################################################# | 160 | # ############################################################################# |
1265 | 161 | # PK but bad explain plan. | ||
1266 | 162 | # https://bugs.launchpad.net/percona-toolkit/+bug/1010232 | ||
1267 | 163 | # ############################################################################# | ||
1268 | 164 | $sb->load_file('master', "t/pt-table-checksum/samples/bad-plan-bug-1010232.sql"); | ||
1269 | 165 | PerconaTest::wait_for_table($dbh, "bad_plan.t", "(c1,c2,c3,c4)=(1,1,2,100)"); | ||
1270 | 166 | |||
1271 | 167 | $output = output(sub { | ||
1272 | 168 | $exit_status = pt_table_checksum::main( | ||
1273 | 169 | $master_dsn, '--max-load', '', | ||
1274 | 170 | qw(--lock-wait-timeout 3 --chunk-size 10 -t bad_plan.t) | ||
1275 | 171 | ) }, | ||
1276 | 172 | stderr => 1, | ||
1277 | 173 | ); | ||
1278 | 174 | |||
1279 | 175 | is( | ||
1280 | 176 | $exit_status, | ||
1281 | 177 | 0, | ||
1282 | 178 | "Bad key_len chunks are not errors" | ||
1283 | 179 | ); | ||
1284 | 180 | |||
1285 | 181 | cmp_ok( | ||
1286 | 182 | PerconaTest::count_checksum_results($output, 'skipped'), | ||
1287 | 183 | '>', | ||
1288 | 184 | 1, | ||
1289 | 185 | "Skipped bad key_len chunks" | ||
1290 | 186 | ); | ||
1291 | 187 | |||
1292 | 188 | # Use --chunk-index:3 to use only the first 3 left-most columns of the index. | ||
1293 | 189 | # Can't use bad_plan.t, however, because its row are almost all identical, | ||
1294 | 190 | # so using 3 of 4 pk cols creates an infinite loop. | ||
1295 | 191 | ok( | ||
1296 | 192 | no_diff( | ||
1297 | 193 | sub { | ||
1298 | 194 | pt_table_checksum::main( | ||
1299 | 195 | $master_dsn, '--max-load', '', | ||
1300 | 196 | qw(--lock-wait-timeout 3 --chunk-size 5000 -t sakila.rental), | ||
1301 | 197 | qw(--chunk-index rental_date:2 --explain --explain)); | ||
1302 | 198 | }, | ||
1303 | 199 | "t/pt-table-checksum/samples/n-chunk-index-cols.txt", | ||
1304 | 200 | ), | ||
1305 | 201 | "--chunk-index index:n" | ||
1306 | 202 | ); | ||
1307 | 203 | |||
1308 | 204 | # ############################################################################# | ||
1309 | 161 | # Done. | 205 | # Done. |
1310 | 162 | # ############################################################################# | 206 | # ############################################################################# |
1311 | 163 | $sb->wipe_clean($dbh); | 207 | $sb->wipe_clean($dbh); |
1312 | 164 | 208 | ||
1313 | === added file 't/pt-table-checksum/samples/bad-plan-bug-1010232.sql' | |||
1314 | --- t/pt-table-checksum/samples/bad-plan-bug-1010232.sql 1970-01-01 00:00:00 +0000 | |||
1315 | +++ t/pt-table-checksum/samples/bad-plan-bug-1010232.sql 2012-06-10 17:15:24 +0000 | |||
1316 | @@ -0,0 +1,17 @@ | |||
1317 | 1 | DROP DATABASE IF EXISTS bad_plan; | ||
1318 | 2 | CREATE DATABASE bad_plan; | ||
1319 | 3 | USE bad_plan; | ||
1320 | 4 | |||
1321 | 5 | CREATE TABLE t ( | ||
1322 | 6 | `c1` smallint(5) unsigned NOT NULL, | ||
1323 | 7 | `c2` mediumint(8) unsigned NOT NULL DEFAULT '0', | ||
1324 | 8 | `c3` smallint(5) unsigned NOT NULL DEFAULT '0', | ||
1325 | 9 | `c4` smallint(5) unsigned NOT NULL DEFAULT '0', | ||
1326 | 10 | PRIMARY KEY (`c1`,`c2`,`c3`,`c4`) | ||
1327 | 11 | ) ENGINE=InnoDB; | ||
1328 | 12 | |||
1329 | 13 | INSERT INTO t VALUES | ||
1330 | 14 | (1,1,1,1),(1,1,1,2),(1,1,1,3),(1,1,1,4),(1,1,1,5),(1,1,1,6),(1,1,1,7),(1,1,1,8),(1,1,1,9),(1,1,1,10),(1,1,1,11),(1,1,1,12),(1,1,1,13),(1,1,1,14),(1,1,1,15),(1,1,1,16),(1,1,1,17),(1,1,1,18),(1,1,1,19),(1,1,1,20),(1,1,1,21),(1,1,1,22),(1,1,1,23),(1,1,1,24),(1,1,1,25),(1,1,1,26),(1,1,1,27),(1,1,1,28),(1,1,1,29),(1,1,1,30),(1,1,1,31),(1,1,1,32),(1,1,1,33),(1,1,1,34),(1,1,1,35),(1,1,1,36),(1,1,1,37),(1,1,1,38),(1,1,1,39),(1,1,1,40),(1,1,1,41),(1,1,1,42),(1,1,1,43),(1,1,1,44),(1,1,1,45),(1,1,1,46),(1,1,1,47),(1,1,1,48),(1,1,1,49),(1,1,1,50),(1,1,1,51),(1,1,1,52),(1,1,1,53),(1,1,1,54),(1,1,1,55),(1,1,1,56),(1,1,1,57),(1,1,1,58),(1,1,1,59),(1,1,1,60),(1,1,1,61),(1,1,1,62),(1,1,1,63),(1,1,1,64),(1,1,1,65),(1,1,1,66),(1,1,1,67),(1,1,1,68),(1,1,1,69),(1,1,1,70),(1,1,1,71),(1,1,1,72),(1,1,1,73),(1,1,1,74),(1,1,1,75),(1,1,1,76),(1,1,1,77),(1,1,1,78),(1,1,1,79),(1,1,1,80),(1,1,1,81),(1,1,1,82),(1,1,1,83),(1,1,1,84),(1,1,1,85),(1,1,1,86),(1,1,1,87),(1,1,1,88),(1,1,1,89),(1,1,1,90),(1,1,1,91),(1,1,1,92),(1,1,1,93),(1,1,1,94),(1,1,1,95),(1,1,1,96),(1,1,1,97),(1,1,1,98),(1,1,1,99),(1,1,1,100), | ||
1331 | 15 | (1,1,2,1),(1,1,2,2),(1,1,2,3),(1,1,2,4),(1,1,2,5),(1,1,2,6),(1,1,2,7),(1,1,2,8),(1,1,2,9),(1,1,2,10),(1,1,2,11),(1,1,2,12),(1,1,2,13),(1,1,2,14),(1,1,2,15),(1,1,2,16),(1,1,2,17),(1,1,2,18),(1,1,2,19),(1,1,2,20),(1,1,2,21),(1,1,2,22),(1,1,2,23),(1,1,2,24),(1,1,2,25),(1,1,2,26),(1,1,2,27),(1,1,2,28),(1,1,2,29),(1,1,2,30),(1,1,2,31),(1,1,2,32),(1,1,2,33),(1,1,2,34),(1,1,2,35),(1,1,2,36),(1,1,2,37),(1,1,2,38),(1,1,2,39),(1,1,2,40),(1,1,2,41),(1,1,2,42),(1,1,2,43),(1,1,2,44),(1,1,2,45),(1,1,2,46),(1,1,2,47),(1,1,2,48),(1,1,2,49),(1,1,2,50),(1,1,2,51),(1,1,2,52),(1,1,2,53),(1,1,2,54),(1,1,2,55),(1,1,2,56),(1,1,2,57),(1,1,2,58),(1,1,2,59),(1,1,2,60),(1,1,2,61),(1,1,2,62),(1,1,2,63),(1,1,2,64),(1,1,2,65),(1,1,2,66),(1,1,2,67),(1,1,2,68),(1,1,2,69),(1,1,2,70),(1,1,2,71),(1,1,2,72),(1,1,2,73),(1,1,2,74),(1,1,2,75),(1,1,2,76),(1,1,2,77),(1,1,2,78),(1,1,2,79),(1,1,2,80),(1,1,2,81),(1,1,2,82),(1,1,2,83),(1,1,2,84),(1,1,2,85),(1,1,2,86),(1,1,2,87),(1,1,2,88),(1,1,2,89),(1,1,2,90),(1,1,2,91),(1,1,2,92),(1,1,2,93),(1,1,2,94),(1,1,2,95),(1,1,2,96),(1,1,2,97),(1,1,2,98),(1,1,2,99),(1,1,2,100); | ||
1332 | 16 | |||
1333 | 17 | ANALYZE TABLE bad_plan.t; | ||
1334 | 0 | 18 | ||
1335 | === added file 't/pt-table-checksum/samples/n-chunk-index-cols.txt' | |||
1336 | --- t/pt-table-checksum/samples/n-chunk-index-cols.txt 1970-01-01 00:00:00 +0000 | |||
1337 | +++ t/pt-table-checksum/samples/n-chunk-index-cols.txt 2012-06-10 17:15:24 +0000 | |||
1338 | @@ -0,0 +1,19 @@ | |||
1339 | 1 | -- | ||
1340 | 2 | -- sakila.rental | ||
1341 | 3 | -- | ||
1342 | 4 | |||
1343 | 5 | 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('#', `rental_id`, `rental_date`, `inventory_id`, `customer_id`, `return_date`, `staff_id`, `last_update` + 0, CONCAT(ISNULL(`return_date`)))) AS UNSIGNED)), 10, 16)), 0) AS crc FROM `sakila`.`rental` FORCE INDEX(`rental_date`) WHERE ((`rental_date` > ?) OR (`rental_date` = ? AND `inventory_id` >= ?)) AND ((`rental_date` < ?) OR (`rental_date` = ? AND `inventory_id` <= ?)) /*checksum chunk*/ | ||
1344 | 6 | |||
1345 | 7 | REPLACE INTO `percona`.`checksums` (db, tbl, chunk, chunk_index, lower_boundary, upper_boundary, this_cnt, this_crc) SELECT ?, ?, ?, ?, ?, ?, COUNT(*), '0' FROM `sakila`.`rental` FORCE INDEX(`rental_date`) WHERE ((`rental_date` < ?) OR (`rental_date` = ? AND `inventory_id` < ?)) ORDER BY `rental_date`, `inventory_id`, `customer_id` /*past lower chunk*/ | ||
1346 | 8 | |||
1347 | 9 | REPLACE INTO `percona`.`checksums` (db, tbl, chunk, chunk_index, lower_boundary, upper_boundary, this_cnt, this_crc) SELECT ?, ?, ?, ?, ?, ?, COUNT(*), '0' FROM `sakila`.`rental` FORCE INDEX(`rental_date`) WHERE ((`rental_date` > ?) OR (`rental_date` = ? AND `inventory_id` > ?)) ORDER BY `rental_date`, `inventory_id`, `customer_id` /*past upper chunk*/ | ||
1348 | 10 | |||
1349 | 11 | SELECT /*!40001 SQL_NO_CACHE */ `rental_date`, `rental_date`, `inventory_id` FROM `sakila`.`rental` FORCE INDEX(`rental_date`) WHERE ((`rental_date` > ?) OR (`rental_date` = ? AND `inventory_id` >= ?)) ORDER BY `rental_date`, `inventory_id`, `customer_id` LIMIT ?, 2 /*next chunk boundary*/ | ||
1350 | 12 | |||
1351 | 13 | 1 2005-05-24 22:53:30,2005-05-24 22:53:30,367 2005-07-09 01:17:08,2005-07-09 01:17:08,2388 | ||
1352 | 14 | 2 2005-07-09 01:19:03,2005-07-09 01:19:03,3438 2005-07-31 17:51:23,2005-07-31 17:51:23,2396 | ||
1353 | 15 | 3 2005-07-31 17:53:51,2005-07-31 17:53:51,928 2005-08-22 13:58:23,2005-08-22 13:58:23,2553 | ||
1354 | 16 | 4 2005-08-22 13:59:19,2005-08-22 13:59:19,3704 2006-02-14 15:16:03,2006-02-14 15:16:03,4568 | ||
1355 | 17 | 5 2005-05-24 22:53:30,2005-05-24 22:53:30,367 | ||
1356 | 18 | 6 2006-02-14 15:16:03,2006-02-14 15:16:03,4568 | ||
1357 | 19 |
OK.