检测不再使用的索引--check-unused-keys

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
RDS MySQL Serverless 高可用系列,价值2615元额度,1个月
云数据库 RDS MySQL,高可用系列 2核4GB
简介:

脚本

#!/usr/bin/perl

################################################################################
# check-unused-keys - Perl Script to check unused indexes
# 
# @author Ryan Lowe <ryan.a.lowe@percona.com>
################################################################################

use strict;
use warnings FATAL => 'all';
use Pod::Usage;
use Getopt::Long;
use English qw(-no_match_vars);
use DBI;

my $VERSION = '0.0.2';
my %OPTIONS;
$OPTIONS{'summary'} = 1;

################################################################################
# Get configuration information
################################################################################

# Parse command line opts
my $gop=new Getopt::Long::Parser;
$gop->configure('no_ignore_case','bundling');
if (!$gop->getoptions(
    'create-alter!'        => \$OPTIONS{'createalter'},
    'databases|d=s'        => \$OPTIONS{'database' },
    'help|h'               => \$OPTIONS{'help'     },
    'hostname|H=s'         => \$OPTIONS{'host'     },
    'ignore-databases=s'   => \$OPTIONS{'ignoredb' },
    'ignore-indexes=s'     => \$OPTIONS{'ignoreidx'},
    'ignore-primary-key!'  => \$OPTIONS{'ignorepk'},   
    'ignore-tables=s'      => \$OPTIONS{'ignoretbl'},
    'ignore-unique-index!' => \$OPTIONS{'ignoreuniq'},
    'print-unused-tables!' => \$OPTIONS{'printunusedtbl'},
    'options-file=s'       => \$OPTIONS{'def'      },
    'password|p=s'         => \$OPTIONS{'password' },
    'port=i'               => \$OPTIONS{'port'     },
    'socket|s=s'           => \$OPTIONS{'socket'   },
    'summary!'             => \$OPTIONS{'summary'  },
    'tables|t=s'           => \$OPTIONS{'tables'   },
    'username|u=s'         => \$OPTIONS{'user'     },
    'verbose|v+'           => \$OPTIONS{'verbose'  },
    'version|V'            => \$OPTIONS{'version'  } ) ) {

    pod2usage(2);
}

# Yay for versions
if ($OPTIONS{'version'}) {
    print "$VERSION\n";
    exit 0;
}

# Help if asked for or no check given
pod2usage(2) if     ($OPTIONS{'help'});

# Set global defaults/validate options
$OPTIONS{'timeout'} = $OPTIONS{'timeout'} ? $OPTIONS{'timeout'} : 10;
$OPTIONS{'verbose'} = $OPTIONS{'verbose'} ? $OPTIONS{'verbose'} : 0;

################################################################################
# Begin the main program
################################################################################

# Set db defaults/validate options
$OPTIONS{'host'} = $OPTIONS{'host'} ? $OPTIONS{'host'} : 'localhost';
$OPTIONS{'port'} = $OPTIONS{'port'} ? $OPTIONS{'port'} : '3306';
$OPTIONS{'def' } = $OPTIONS{'def' } ? $OPTIONS{'def' } : $ENV{'HOME'}.'/.my.cnf';

# Set some default behaviour
$OPTIONS{'createalter'}     = defined($OPTIONS{'createalter'})     ? $OPTIONS{'createalter'} : 0;
$OPTIONS{'ignorepk'}        = defined($OPTIONS{'ignorepk'})        ? $OPTIONS{'ignorepk'} : 1;
$OPTIONS{'ignoreuniq'}      = defined($OPTIONS{'ignoreuniq'})      ? $OPTIONS{'ignoreuniq'} : 1;
$OPTIONS{'printunusedtbl'}  = defined($OPTIONS{'printunusedtbl'})  ? $OPTIONS{'printunusedtbl'} : 0;


# Attempt db connection
my $connection_string  = 'DBI:mysql:';
$connection_string    .= "host=$OPTIONS{'host'};";
$connection_string    .= "database=$OPTIONS{'database'};"
    if $OPTIONS{'database'};
$connection_string    .= "mysql_socket=$OPTIONS{'socket'};"
    if $OPTIONS{'socket'} and $OPTIONS{'host'} eq 'localhost';
$connection_string    .= "port=$OPTIONS{'port'};";
$connection_string    .= "mysql_read_default_file=$OPTIONS{'def'};";
$connection_string    .= "mysql_read_default_group=client;";
$connection_string    .= "mysql_multi_statements=1";
my $dbh;
eval {
    $dbh = DBI->connect (
        $connection_string,
        $OPTIONS{'user'},
        $OPTIONS{'password'},
        { RaiseError => 1, PrintError => 0 }
    );
};

if ( $@ ) {
    print "Could not connect to MySQL\n";
    print "\n";
    print $@ if ($OPTIONS{'verbose'} > 0);
    exit 1;
}

# Check to make sure userstats is actually enabled:)

my $sanity_query = 'SHOW GLOBAL VARIABLES LIKE "userstat_running"';
my $sth = $dbh->prepare($sanity_query);
$sth->execute();

my $status = $sth->fetchrow_hashref();
die('userstat is NOT running') unless ($status->{'Value'} eq 'ON'); 

################################################################################
# Build The Query
################################################################################

my $query = '
SELECT DISTINCT `s`.`TABLE_SCHEMA`, `s`.`TABLE_NAME`, `s`.`INDEX_NAME`,
      `s`.`NON_UNIQUE`, `s`.`INDEX_NAME`, `t`.`ROWS_READ` AS TBL_READ, `i`.`ROWS_READ` AS IDX_READ
  FROM `information_schema`.`statistics` AS `s` 
  LEFT JOIN `information_schema`.`index_statistics` AS `i`
    ON (`s`.`TABLE_SCHEMA` = `i`.`TABLE_SCHEMA` AND 
        `s`.`TABLE_NAME`   = `i`.`TABLE_NAME` AND
        `s`.`INDEX_NAME`   = `i`.`INDEX_NAME`)
  LEFT JOIN `information_schema`.`table_statistics` AS `t`
    ON (`s`.`TABLE_SCHEMA` = `t`.`TABLE_SCHEMA` AND 
        `s`.`TABLE_NAME`   = `t`.`TABLE_NAME`)
  WHERE `i`.`TABLE_SCHEMA` IS NULL
';

if ($OPTIONS{'database'}) {
    my @dbs = split(',', $OPTIONS{'database'});
    $query .= '    AND `s`.`TABLE_SCHEMA` IN ("'.join('","',@dbs).'")
';
}

if ($OPTIONS{'ignoredb'}) {
    my @dbs = split(',', $OPTIONS{'ignoredb'});
    $query .= '    AND `s`.`TABLE_SCHEMA` NOT IN ("'.join('","',@dbs).'")
';
}

if ($OPTIONS{'ignoretbl'}) {
    my @tbls = split(',', $OPTIONS{'ignoretbl'});
    foreach (@tbls) {
        my @a = split(/\./, $_); 
        $query .= '    AND (`s`.`TABLE_SCHEMA` != "'.$a[0].'" AND `s`.`TABLE_NAME` != "'.$a[1].'")
';
    } 
}

if ($OPTIONS{'ignoreidx'}) {
    my @idxs = split(',', $OPTIONS{'ignoreidx'});
    foreach (@idxs) {
        my @a = split(/\./, $_);
        $query .= '    AND (`s`.`TABLE_SCHEMA` != "'.$a[0].'" AND `s`.`TABLE_NAME` != "'.$a[1].'" AND `s`.`INDEX_NAME` != "'.$a[2].'")
';
    }
}

if ($OPTIONS{'tables'}) {
    my @tbls = split(/\,/, $OPTIONS{'tables'});
    foreach (@tbls) {
        my @a = split(/\./, $_);
        $query .= '    AND (`s`.`TABLE_SCHEMA` = "'.$a[0].'" AND `s`.`TABLE_NAME` = "'.$a[1].'")
';
    }
}

if ($OPTIONS{'ignorepk'}) {
    $query .= '    AND `s`.`INDEX_NAME` != "PRIMARY"
';
}

if ($OPTIONS{'ignoreuniq'}) {
    $query .= '    AND `s`.`NON_UNIQUE` = 1
';
}

#if ($OPTIONS{'ignoreunusedtbl'}) {
#    $query .= '    AND `t`.`ROWS_READ` > 0 AND `t`.`ROWS_READ` IS NOT NULL
#';
#}


print $query."\n" if ($OPTIONS{'verbose'} gt 1);

$sth = $dbh->prepare($query);
$sth->execute();

my $n_indexes = 0;
my $n_tbls = 0;
my $ignored_tbls = {};
my %alters;

## loop through all returned rows
while (my $row = $sth->fetchrow_hashref()) {
    my $tbl = '`'.$row->{'TABLE_SCHEMA'}.'`.`'.$row->{'TABLE_NAME'}.'`';

    ## if this table was never read from
    if (!defined($row->{'TBL_READ'}) or $row->{'TBL_READ'} eq 0) {
        ## skip if we already printed this table
        next if ($ignored_tbls->{$row->{'TABLE_NAME'}});

        $ignored_tbls->{$row->{'TABLE_NAME'}} = 1;
        $n_tbls++;

        print "# Table $tbl not used.\n"  if ($OPTIONS{'printunusedtbl'} gt 0);
        
        ## dont bother doing check for unused indexes if table was never read
        next;
    }

    ## build the ALTER command
    $n_indexes++;
    if ($OPTIONS{'createalter'}) {
        if (!defined($alters{$tbl})) {
            $alters{$tbl} = 'ALTER TABLE '.$tbl.' DROP INDEX `'.$row->{'INDEX_NAME'}.'`';
        } else {
            $alters{$tbl} .= ",\n    DROP INDEX `".$row->{'INDEX_NAME'}.'`';
        }
    }
    print "# Index $tbl (".$row->{'INDEX_NAME'}.") not used.\n";
}

if ($OPTIONS{'createalter'}) {
    foreach (sort keys %alters)  {
       print $alters{$_}.";\n";
    }
}

if ($OPTIONS{'summary'} gt 0) {
    $sth = $dbh->prepare('SHOW GLOBAL STATUS LIKE "Uptime"');
    $sth->execute();
    my $ua = $sth->fetchrow_hashref();

    print '
################################################################################
# Unused Indexes: '.$n_indexes,"\n";

    print '# Unused Tables:  '.$n_tbls."\n" if $OPTIONS{'printunusedtbl'};
    print '# Uptime: '.$ua->{'Value'}.' seconds
################################################################################
';
}

=pod

=head1 NAME

check-unused-keys - Perl Script to check unused indexes using Percona userstat

=head1 SYNOPSIS

 check-unused-keys [OPTIONS]

 Options:
   -d, --databases=<dbname>  Comma-separated list of databases to check
   -h, --help                Display this message and exit
   -H, --hostname=<hostname> The target MySQL server host
   --[no]create-alter        Print ALTER statements for each table
   --ignore-databases        Comma-separated list of databases to ignore
   --ignore-indexes          Comma-separated list of indexes to ignore
                                 db_name.tbl_name.index_name
   --ignore-tables           Comma-separated list of tables to ignore
                                 db_name.tbl_name
   --[no]ignore-primary      Whether or not to ignore PRIMARY KEY
   --[no]ignore-unique       Whether or not to ignore UNIQUE indexes
   --options-file            The options file to use
   --[no]print-unused-tables 
                             Whether or not to print a list of unused tables
                                 (indexes from unused tables are never shown)
   -p, --password=<password> The password of the MySQL user
   -i, --port=<portnum>      The port MySQL is listening on
   -s, --socket=<sockfile>   Use the specified mysql unix socket to connect
   -t, --tables=<tables>     Comma-separated list of tables to evaluate
                                 db_name.tbl_name
   --[no]summary             Display summary information
   -u, --username=<username> The MySQL user used to connect
   -v, --verbose             Increase verbosity level
   -V, --version             Display version information and exit

 Defaults are:

 ATTRIBUTE                  VALUE
 -------------------------- ------------------
 databases                  ALL databases 
 help                       FALSE
 hostname                   localhost
 create-alter               FALSE
 ignore-databases           No default value
 ignore-indexes             No default value
 ignore-primary             TRUE
 ignore-tables              No default value
 ignore-unique              TRUE
 options-file               ~/.my.cnf
 password                   No default value
 print-unused-tables        FALSE
 port                       3306
 socket                     No default value
 summary                    TRUE
 tables                     No Default Value
 username                   No default value
 verbose                    0 (out of 2)
 version                    FALSE

=head1 SYSTEM REQUIREMENTS

check-unused-keys requires the following Perl modules:

  Pod::Usage
  Getopt::Long
  DBI
  DBD::mysql

=head1 BUGS

Please report all bugs and feature requests to
http://code.google.com/p/check-unused-keys

=head1 LICENSE

THIS PROGRAM IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.

This program is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
Foundation, version 2; OR the Perl Artistic License.  On UNIX and similar
systems, you can issue `man perlgpl' or `man perlartistic' to read these
licenses.

You should have received a copy of the GNU General Public License along with
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
Place, Suite 330, Boston, MA  02111-1307 USA.

=head1 AUTHOR

Ryan Lowe (ryan.a.lowe@percona.com)

=head1 VERSION

This manual page documents 0.0.1 of check-unused-keys

=cut

数据库设置

mysql> SHOW GLOBAL VARIABLES LIKE "userstat";
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| userstat      | ON    |
+---------------+-------+
1 row in set (0.00 sec)

语法

./check-unused-keys --help

使用

./check-unused-keys --host=192.168.1.15 --username=root --password=zhujie1986 --port=3308 --create-alter 

输出

# Index `medicine`.`inn` (idx_name) not used.
# Index `hrs_ebs`.`qa_theme_item` (idx_hospUuid_parentId) not used.
# Index `hrs_ebs`.`qa_theme_item` (idx_std_hosp_uuid) not used.
# Index `expert`.`doctor_course` (idx_state) not used.
# Index `settle`.`transfer_order` (idx_transfer_no) not used.
# Index `syreserve`.`channel_detail` (idx_module_type_module_business_id) not used.
…………
# Index `cca`.`ip_addr_pool` (idx_ipaddr_id) not used.
# Index `cca`.`ip_addr_pool` (idx_roleid_ipaddr) not used.
ALTER TABLE `area_family_doctor`.`t_agreement` DROP INDEX `idx_patient_idcard_no`;
ALTER TABLE `area_family_doctor`.`t_ops_org_hosp` DROP INDEX `idx_org_id`;
ALTER TABLE `area_family_doctor`.`t_ops_org` DROP INDEX `idx_name`;
ALTER TABLE `area_family_doctor`.`t_ops_sign_area_service` DROP INDEX `idx_org_id`;
ALTER TABLE `area_family_doctor`.`t_pay_record` DROP INDEX `idx_agreement_apply_id`;
…………
ALTER TABLE `working`.`user_profile_bak` DROP INDEX `up_mobile`,
    DROP INDEX `up_call_phone`,
    DROP INDEX `idx_user_name`,
    DROP INDEX `idx_cert_no`,
    DROP INDEX `idx_user_profile_nickname`;
ALTER TABLE `wxgops`.`wx_redpacket` DROP INDEX `idx_red_packet_starttime`,
    DROP INDEX `idx_red_packet_endtime`;

################################################################################
# Unused Indexes: 830
# Uptime: 5037588 seconds
################################################################################

https://www.percona.com/blog/2009/06/26/check-unused-keys-a-tool-to-interact-with-index_statistics/

目录
相关文章
|
10月前
|
机器学习/深度学习 数据可视化 数据挖掘
使用Python实现基于矩阵分解的长期事件(MFLEs)时间序列分析
在现代数据分析中,高维时间序列数据的处理和预测极具挑战性。基于矩阵分解的长期事件(MFLEs)分析技术应运而生,通过降维和时间序列特性结合,有效应对大规模数据。MFLE利用矩阵分解提取潜在特征,降低计算复杂度,过滤噪声,并发现主要模式。相比传统方法如ARIMA和深度学习模型如LSTM,MFLE在多变量处理、计算效率和可解释性上更具优势。通过合理应用MFLE,可在物联网、金融等领域获得良好分析效果。
344 0
使用Python实现基于矩阵分解的长期事件(MFLEs)时间序列分析
|
Java API 数据库
如何使用Spring Boot构建RESTful API,以在线图书管理系统为例
【10月更文挑战第9天】本文介绍了如何使用Spring Boot构建RESTful API,以在线图书管理系统为例,从项目搭建、实体类定义、数据访问层创建、业务逻辑处理到RESTful API的实现,详细展示了每个步骤。通过Spring Boot的简洁配置和强大功能,开发者可以高效地开发出功能完备、易于维护的Web应用。
323 3
|
人工智能 程序员
让通义灵码成为你的1对1老师
本文介绍了作者如何利用通义灵码个人版提升编程学习效率。通过使用通义的代码补全、代码测试、生成Markdown等功能,作者不仅提高了学习速度,还能像有1对1老师辅导一样深入理解课程内容。特别是对于非科班出身的程序员,通义能够有效纠正基础问题,避免走入开发规范的误区。此外,通义还具备强大的workspace功能,帮助用户管理代码库,快速定位弱点,成为学习和工作的全能助手。
194 3
让通义灵码成为你的1对1老师
|
负载均衡 网络协议 算法
OSPF 中的负载均衡:优化网络流量分布
OSPF 中的负载均衡:优化网络流量分布
624 0
|
数据可视化 Python
【Python可视化】绘制学生成绩的雷达图
【Python可视化】绘制学生成绩的雷达图 雷达图,又叫蜘蛛网图、极坐标图。 雷达图相当于平行坐标图,其中轴径向排列。
931 0
【Python可视化】绘制学生成绩的雷达图
|
运维 算法 网络协议
谈谈Linux内核的学习路线,具体要怎么学?
谈谈Linux内核的学习路线,具体要怎么学?
|
数据采集 传感器 数据挖掘
数据资产运营
数据资产运营
|
传感器 算法 API
HarmonyOS学习路之开发篇—设备管理(传感器开发)
HarmonyOS传感器是应用访问底层硬件传感器的一种设备抽象概念。开发者根据传感器提供的Sensor API,可以查询设备上的传感器,订阅传感器的数据,并根据传感器数据定制相应的算法,开发各类应用,比如指南针、运动健康、游戏等。
|
Java 编译器 开发工具
2023 Android 模块化完整方案实现  
本套模块化方案实现来源于公司的业务需求,因为公司业务太多,代码越来越臃肿,越来越难维护,为了提升开发效率,减低代码的维护成本,所以采取了模块化开发方案。 既然是模块化开发,必然要考虑到各个module的开发,调试,迭代拓展及维护,module之间不仅需要做到业务代码隔离,还需要方便的跳转(路由引导模块),方便的传递数据(包括大容量的数据),能够独立编译调。最后,各个module,完成之后,一起打包到主APP即可。
756 0
|
小程序 开发者
微信小程序的发布及上线
微信小程序的发布及上线
456 0