mirror of
https://github.com/percona/percona-toolkit.git
synced 2025-09-02 02:34:19 +00:00
576 lines
17 KiB
Perl
576 lines
17 KiB
Perl
#!/usr/bin/perl
|
|
|
|
BEGIN {
|
|
die "The PERCONA_TOOLKIT_BRANCH environment variable is not set.\n"
|
|
unless $ENV{PERCONA_TOOLKIT_BRANCH} && -d $ENV{PERCONA_TOOLKIT_BRANCH};
|
|
unshift @INC, "$ENV{PERCONA_TOOLKIT_BRANCH}/lib";
|
|
};
|
|
|
|
use constant PTDEBUG => $ENV{PTDEBUG} || 0;
|
|
use strict;
|
|
use warnings FATAL => 'all';
|
|
use English qw(-no_match_vars);
|
|
use Data::Dumper;
|
|
$Data::Dumper::Indent = 1;
|
|
$Data::Dumper::Sortkeys = 1;
|
|
$Data::Dumper::Quotekeys = 0;
|
|
|
|
use Test::More;
|
|
|
|
use ExplainAnalyzer;
|
|
use QueryRewriter;
|
|
use QueryParser;
|
|
use DSNParser;
|
|
use Sandbox;
|
|
use PerconaTest;
|
|
|
|
my $dp = new DSNParser(opts=>$dsn_opts);
|
|
my $sb = new Sandbox(basedir => '/tmp', DSNParser => $dp);
|
|
my $dbh = $sb->get_dbh_for('master', {no_lc=>1});
|
|
|
|
if ( !$dbh ) {
|
|
plan skip_all => "Cannot connect to sandbox master";
|
|
}
|
|
|
|
$dbh->do('use sakila');
|
|
|
|
my $qr = new QueryRewriter();
|
|
my $qp = new QueryParser();
|
|
my $exa = new ExplainAnalyzer(QueryRewriter => $qr, QueryParser => $qp);
|
|
|
|
# #############################################################################
|
|
# Tests for getting an EXPLAIN from a database.
|
|
# #############################################################################
|
|
|
|
is_deeply(
|
|
$exa->explain_query(
|
|
dbh => $dbh,
|
|
query => 'select * from actor where actor_id = 5',
|
|
),
|
|
[
|
|
{ id => 1,
|
|
select_type => 'SIMPLE',
|
|
table => 'actor',
|
|
type => 'const',
|
|
possible_keys => 'PRIMARY',
|
|
key => 'PRIMARY',
|
|
key_len => 2,
|
|
ref => 'const',
|
|
rows => 1,
|
|
Extra => $sandbox_version eq '5.6' ? undef : '',
|
|
},
|
|
],
|
|
'Got a simple EXPLAIN result',
|
|
);
|
|
|
|
is_deeply(
|
|
$exa->explain_query(
|
|
dbh => $dbh,
|
|
query => 'delete from actor where actor_id = 5',
|
|
),
|
|
[
|
|
{ id => 1,
|
|
select_type => 'SIMPLE',
|
|
table => 'actor',
|
|
type => 'const',
|
|
possible_keys => 'PRIMARY',
|
|
key => 'PRIMARY',
|
|
key_len => 2,
|
|
ref => 'const',
|
|
rows => 1,
|
|
Extra => $sandbox_version eq '5.6' ? undef : '',
|
|
},
|
|
],
|
|
'Got EXPLAIN result for a DELETE',
|
|
);
|
|
|
|
is(
|
|
$exa->explain_query(
|
|
dbh => $dbh,
|
|
query => 'insert into t values (1)',
|
|
),
|
|
undef,
|
|
"Doesn't EXPLAIN non-convertable non-SELECT"
|
|
);
|
|
|
|
# #############################################################################
|
|
# NOTE: EXPLAIN will vary between versions, so rely on the database as little as
|
|
# possible for tests. Most things that need an EXPLAIN in the tests below
|
|
# should be using a hard-coded data structure. Thus the following, intended to
|
|
# help prevent $dbh being used too much.
|
|
# #############################################################################
|
|
# XXX $dbh->disconnect;
|
|
|
|
# #############################################################################
|
|
# Tests for normalizing raw EXPLAIN into a format that's easier to work with.
|
|
# #############################################################################
|
|
is_deeply(
|
|
$exa->normalize(
|
|
[
|
|
{ id => 1,
|
|
select_type => 'SIMPLE',
|
|
table => 'film_actor',
|
|
type => 'index_merge',
|
|
possible_keys => 'PRIMARY,idx_fk_film_id',
|
|
key => 'PRIMARY,idx_fk_film_id',
|
|
key_len => '2,2',
|
|
ref => undef,
|
|
rows => 34,
|
|
Extra => 'Using union(PRIMARY,idx_fk_film_id); Using where',
|
|
},
|
|
],
|
|
),
|
|
[
|
|
{ id => 1,
|
|
select_type => 'SIMPLE',
|
|
table => 'film_actor',
|
|
type => 'index_merge',
|
|
possible_keys => [qw(PRIMARY idx_fk_film_id)],
|
|
key => [qw(PRIMARY idx_fk_film_id)],
|
|
key_len => [2,2],
|
|
ref => [qw()],
|
|
rows => 34,
|
|
Extra => {
|
|
'Using union' => [qw(PRIMARY idx_fk_film_id)],
|
|
'Using where' => 1,
|
|
},
|
|
},
|
|
],
|
|
'Normalizes an EXPLAIN',
|
|
);
|
|
|
|
is_deeply(
|
|
$exa->normalize(
|
|
[
|
|
{ id => 1,
|
|
select_type => 'PRIMARY',
|
|
table => undef,
|
|
type => undef,
|
|
possible_keys => undef,
|
|
key => undef,
|
|
key_len => undef,
|
|
ref => undef,
|
|
rows => undef,
|
|
Extra => 'No tables used',
|
|
},
|
|
{ id => 1,
|
|
select_type => 'UNION',
|
|
table => 'a',
|
|
type => 'index',
|
|
possible_keys => undef,
|
|
key => 'PRIMARY',
|
|
key_len => '2',
|
|
ref => undef,
|
|
rows => 200,
|
|
Extra => 'Using index',
|
|
},
|
|
{ id => undef,
|
|
select_type => 'UNION RESULT',
|
|
table => '<union1,2>',
|
|
type => 'ALL',
|
|
possible_keys => undef,
|
|
key => undef,
|
|
key_len => undef,
|
|
ref => undef,
|
|
rows => undef,
|
|
Extra => '',
|
|
},
|
|
],
|
|
),
|
|
[
|
|
{ id => 1,
|
|
select_type => 'PRIMARY',
|
|
table => undef,
|
|
type => undef,
|
|
possible_keys => [],
|
|
key => [],
|
|
key_len => [],
|
|
ref => [],
|
|
rows => undef,
|
|
Extra => {
|
|
'No tables used' => 1,
|
|
},
|
|
},
|
|
{ id => 1,
|
|
select_type => 'UNION',
|
|
table => 'a',
|
|
type => 'index',
|
|
possible_keys => [],
|
|
key => ['PRIMARY'],
|
|
key_len => [2],
|
|
ref => [],
|
|
rows => 200,
|
|
Extra => {
|
|
'Using index' => 1,
|
|
},
|
|
},
|
|
{ id => undef,
|
|
select_type => 'UNION RESULT',
|
|
table => '<union1,2>',
|
|
type => 'ALL',
|
|
possible_keys => [],
|
|
key => [],
|
|
key_len => [],
|
|
ref => [],
|
|
rows => undef,
|
|
Extra => {},
|
|
},
|
|
],
|
|
'Normalizes a more complex EXPLAIN',
|
|
);
|
|
|
|
is_deeply(
|
|
$exa->normalize(
|
|
[
|
|
{ id => 1,
|
|
select_type => 'SIMPLE',
|
|
table => 't1',
|
|
type => 'ALL',
|
|
possible_keys => 'PRIMARY',
|
|
key => undef,
|
|
key_len => undef,
|
|
ref => undef,
|
|
rows => '4',
|
|
# Extra => 'Using where; Using temporary; Using filesort',
|
|
},
|
|
],
|
|
),
|
|
[
|
|
{
|
|
Extra => {}, # is auto-vivified
|
|
id => 1,
|
|
select_type => 'SIMPLE',
|
|
table => 't1',
|
|
type => 'ALL',
|
|
possible_keys => ['PRIMARY'],
|
|
key => [],
|
|
key_len => [],
|
|
ref => [],
|
|
rows => '4',
|
|
}
|
|
],
|
|
"normalize() doesn't crash if EXPLAIN Extra is missing"
|
|
);
|
|
|
|
# #############################################################################
|
|
# Tests for trimming indexes out of possible_keys.
|
|
# #############################################################################
|
|
is_deeply(
|
|
$exa->get_alternate_indexes(
|
|
[qw(index1 index2)],
|
|
[qw(index1 index2 index3 index4)],
|
|
),
|
|
[qw(index3 index4)],
|
|
'Normalizes alternate indexes',
|
|
);
|
|
|
|
# #############################################################################
|
|
# Tests for translating aliased names back to their real names.
|
|
# #############################################################################
|
|
|
|
# Putting it all together: given a query and an EXPLAIN, determine which indexes
|
|
# the query used.
|
|
is_deeply(
|
|
$exa->get_index_usage(
|
|
query => "select * from film_actor as fa inner join sakila.actor as a "
|
|
. "on a.actor_id = fa.actor_id and a.last_name is not null "
|
|
. "where a.actor_id = 5 or film_id = 5",
|
|
db => 'sakila',
|
|
explain => $exa->normalize(
|
|
[
|
|
{ id => 1,
|
|
select_type => 'SIMPLE',
|
|
table => 'fa',
|
|
type => 'index_merge',
|
|
possible_keys => 'PRIMARY,idx_fk_film_id',
|
|
key => 'PRIMARY,idx_fk_film_id',
|
|
key_len => '2,2',
|
|
ref => undef,
|
|
rows => 34,
|
|
Extra => 'Using union(PRIMARY,idx_fk_film_id); Using where',
|
|
},
|
|
{ id => 1,
|
|
select_type => 'SIMPLE',
|
|
table => 'a',
|
|
type => 'eq_ref',
|
|
possible_keys => 'PRIMARY,idx_actor_last_name',
|
|
key => 'PRIMARY',
|
|
key_len => '2',
|
|
ref => 'sakila.fa.actor_id',
|
|
rows => 1,
|
|
Extra => 'Using where',
|
|
},
|
|
],
|
|
),
|
|
),
|
|
[ { db => 'sakila',
|
|
tbl => 'film_actor',
|
|
idx => [qw(PRIMARY idx_fk_film_id)],
|
|
alt => [],
|
|
},
|
|
{ db => 'sakila',
|
|
tbl => 'actor',
|
|
idx => [qw(PRIMARY)],
|
|
alt => [qw(idx_actor_last_name)],
|
|
},
|
|
],
|
|
'Translate an EXPLAIN and a query into simplified index usage',
|
|
);
|
|
|
|
# This is kind of a pathological case.
|
|
is_deeply(
|
|
$exa->get_index_usage(
|
|
query => "select 1 union select count(*) from actor a",
|
|
db => 'sakila',
|
|
explain => $exa->normalize(
|
|
[
|
|
{ id => 1,
|
|
select_type => 'PRIMARY',
|
|
table => undef,
|
|
type => undef,
|
|
possible_keys => undef,
|
|
key => undef,
|
|
key_len => undef,
|
|
ref => undef,
|
|
rows => undef,
|
|
Extra => 'No tables used',
|
|
},
|
|
{ id => 1,
|
|
select_type => 'UNION',
|
|
table => 'a',
|
|
type => 'index',
|
|
possible_keys => undef,
|
|
key => 'PRIMARY',
|
|
key_len => '2',
|
|
ref => undef,
|
|
rows => 200,
|
|
Extra => 'Using index',
|
|
},
|
|
{ id => undef,
|
|
select_type => 'UNION RESULT',
|
|
table => '<union1,2>',
|
|
type => 'ALL',
|
|
possible_keys => undef,
|
|
key => undef,
|
|
key_len => undef,
|
|
ref => undef,
|
|
rows => undef,
|
|
Extra => '',
|
|
},
|
|
],
|
|
),
|
|
),
|
|
[ { db => 'sakila',
|
|
tbl => 'actor',
|
|
idx => [qw(PRIMARY)],
|
|
alt => [],
|
|
},
|
|
],
|
|
'Translate an EXPLAIN and a query for a harder case',
|
|
);
|
|
|
|
# Here's a query that uses a table but no indexes in it.
|
|
is_deeply(
|
|
$exa->get_index_usage(
|
|
query => "select * from film_text",
|
|
db => 'sakila',
|
|
explain => $exa->normalize(
|
|
[
|
|
{ id => 1,
|
|
select_type => 'SIMPLE',
|
|
table => 'film_text',
|
|
type => 'ALL',
|
|
possible_keys => undef,
|
|
key => undef,
|
|
key_len => undef,
|
|
ref => undef,
|
|
rows => 1000,
|
|
Extra => '',
|
|
},
|
|
],
|
|
),
|
|
),
|
|
[ { db => 'sakila',
|
|
tbl => 'film_text',
|
|
idx => [],
|
|
alt => [],
|
|
},
|
|
],
|
|
'Translate an EXPLAIN for a query that uses no indexes',
|
|
);
|
|
|
|
# #############################################################################
|
|
# Methods to save and retrieve index usage for a specific query and database.
|
|
# #############################################################################
|
|
is_deeply(
|
|
$exa->get_usage_for('0xdeadbeef', 'sakila'),
|
|
undef,
|
|
'No usage recorded for 0xdeadbeef');
|
|
|
|
$exa->save_usage_for('0xdeadbeef', 'sakila',
|
|
[ { db => 'sakila',
|
|
tbl => 'actor',
|
|
idx => [qw(PRIMARY)],
|
|
alt => [],
|
|
},
|
|
]);
|
|
|
|
is_deeply(
|
|
$exa->get_usage_for('0xdeadbeef','sakila'),
|
|
[ { db => 'sakila',
|
|
tbl => 'actor',
|
|
idx => [qw(PRIMARY)],
|
|
alt => [],
|
|
},
|
|
],
|
|
'Got saved usage for 0xdeadbeef');
|
|
|
|
# #############################################################################
|
|
# Issue 1141: Add "spark charts" to mk-query-digest profile
|
|
# #############################################################################
|
|
is(
|
|
$exa->sparkline(explain =>
|
|
[
|
|
{ id => 1,
|
|
select_type => 'PRIMARY',
|
|
table => 'foo',
|
|
type => 'eq_ref',
|
|
possible_keys => ['idx'],
|
|
key => ['idx'],
|
|
key_len => [10],
|
|
ref => [],
|
|
rows => 100,
|
|
Extra => {
|
|
'Using index' => 1,
|
|
'Using where' => 1,
|
|
},
|
|
},
|
|
],
|
|
),
|
|
"E",
|
|
"sparkline: basic 1 table eq_ref"
|
|
);
|
|
|
|
is(
|
|
$exa->sparkline(explain =>
|
|
[
|
|
{ id => 1,
|
|
select_type => 'PRIMARY',
|
|
table => 'foo',
|
|
type => 'eq_ref',
|
|
possible_keys => ['idx'],
|
|
key => ['idx'],
|
|
key_len => [10],
|
|
ref => [],
|
|
rows => 100,
|
|
Extra => {
|
|
'Using index' => 1,
|
|
'Using where' => 1,
|
|
'Using filesort' => 1,
|
|
},
|
|
},
|
|
{ id => 2,
|
|
select_type => 'PRIMARY',
|
|
table => 'bar',
|
|
type => 'ref',
|
|
possible_keys => ['idx'],
|
|
key => ['idx'],
|
|
key_len => [10],
|
|
ref => ['foo.col'],
|
|
rows => 100,
|
|
Extra => {
|
|
},
|
|
},
|
|
],
|
|
),
|
|
"F>Er",
|
|
"sparkline: 2 table with filesort at start"
|
|
);
|
|
|
|
is(
|
|
$exa->sparkline(explain =>
|
|
[
|
|
{ id => 1,
|
|
select_type => 'PRIMARY',
|
|
table => 'foo',
|
|
type => 'range',
|
|
possible_keys => ['idx'],
|
|
key => ['idx'],
|
|
key_len => [10],
|
|
ref => [],
|
|
rows => 100,
|
|
Extra => {
|
|
},
|
|
},
|
|
{ id => 2,
|
|
select_type => 'PRIMARY',
|
|
table => 'bar',
|
|
type => 'ref',
|
|
possible_keys => ['idx'],
|
|
key => ['idx'],
|
|
key_len => [10],
|
|
ref => ['foo.col'],
|
|
rows => 100,
|
|
Extra => {
|
|
'Using temporary' => 1,
|
|
'Using filesort' => 1,
|
|
},
|
|
},
|
|
],
|
|
),
|
|
"nr>TF",
|
|
"sparkline: 2 table with temp and filesort at end"
|
|
);
|
|
|
|
is(
|
|
$exa->sparkline(explain =>
|
|
[
|
|
{ id => 1,
|
|
select_type => 'PRIMARY',
|
|
table => undef,
|
|
type => undef,
|
|
possible_keys => [],
|
|
key => [],
|
|
key_len => [],
|
|
ref => [],
|
|
rows => undef,
|
|
Extra => {
|
|
'No tables used' => 1,
|
|
},
|
|
},
|
|
{ id => 1,
|
|
select_type => 'UNION',
|
|
table => 'a',
|
|
type => 'index',
|
|
possible_keys => [],
|
|
key => ['PRIMARY'],
|
|
key_len => [2],
|
|
ref => [],
|
|
rows => 200,
|
|
Extra => {
|
|
'Using index' => 1,
|
|
},
|
|
},
|
|
{ id => undef,
|
|
select_type => 'UNION RESULT',
|
|
table => '<union1,2>',
|
|
type => 'ALL',
|
|
possible_keys => [],
|
|
key => [],
|
|
key_len => [],
|
|
ref => [],
|
|
rows => undef,
|
|
Extra => {},
|
|
},
|
|
],
|
|
),
|
|
"-Ia",
|
|
"sparkline: 3 tables, using index"
|
|
);
|
|
|
|
# #############################################################################
|
|
# Done.
|
|
# #############################################################################
|
|
ok($sb->ok(), "Sandbox servers") or BAIL_OUT(__FILE__ . " broke the sandbox");
|
|
done_testing;
|