第一章:为什么需要需要进行数据库优化
Mysql的架构介绍 Mysql简介
1.是一种关系型数据库
2.是一种关联数据库管理系统,将数据存储在不同的表中,而不是把所有数据放在一个大仓库里面,增加了速度并提高了灵活性
3.开源的,免费的 公司版本是收费的
4.支持大型数据库,可以处理拥有千万条记录的大型数据库
5.使用标准的SQL数据语言形式
6.可以允许多个系统上,并且支持多语言.包括C,C++,Python,Java,Perl,PHP,Eiffel,Ruby和Tcl等
7.对PHP有很好的支持,但是我是Java忠实的粉丝
8.支持大型数据库,支持5000万条记录的数据仓库,32位操作系统表文件最大支持4GB,64位操作系统支持最大的表文件位 8TB
9.是可以定制的,采用了 GPL 协议,可以修改源码来开发自己的Mysql系统
高级Mysql
1.Mysql内核
2.SQL优化攻城狮
3.Mysql服务器优化
4.各种参数常量设定
5.查询语句优化
6.主从复制
7.软硬件升级
8.容灾备份
9.SQL编程
为什么需要进行数据库优化
避免网站界面出现访问错误
- 由于数据库连接 timeout 产生页面 5xx 错误
- 由于慢查询造成页面无法加载
- 由于阻塞造成数据无法提交
增加数据库的稳定性
很多数据库问题都是因低效的查询引起的
优化用户体验 - 流畅页面的返回速度
- 良好的网站功能体验
第二章:Mysql数据库优化
可以从那些部分进行数据库的优化
注:从上图可以看出,在此 " 金字塔 " 中,优化的成本从下而上逐渐增高,而优化的效果会逐渐降低
SQL以及索引优化
根据需求写出良好的SQL,并创建有效的索引,实现某一种需求可以有多种写法,这时候我们就要去选择一种效率高的写法.
数据库表结构优化
根据数据库的范式,设计表结构,表结构设计的好直接关系到写SQL语句
系统配置优化
大多数运行在Linux机器上,如 tcp 链接数的限制,打开文件数的限制,安全性的限制,因此我们要对这些配置进行相应的优化.
硬件配置优化
选择适合数据库服务的cpu,更快的IO,更高的内存;cpu并不是越多越好,某些数据库版本有最大的限制,IO操作并不是减少阻塞.
SQL以及索引优化
查看Mysql的版本
mysql> select @@version;
+------------+
| @@version |
+------------+
| 5.7.29-log |
+------------+
准备数据
到此网址下载数据 https://dev.mysql.com/doc/index-other.html
步骤
- 链接数据库
mysql -u用户名 -p密码
2.创建表以及语句执行
把文件上传到服务器
然后获取路径 并且执行
mysql> source /sakila-db/sakila-schema.sql
- 此时数据库就已经创建好了
mysql> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| icanci_main |
| mysql |
| performance_schema |
| sakila |
| sys |
+--------------------+
6 rows in set (0.00 sec)
加载数据
mysql> source /sakila-db/sakila-data.sql
- 加载数据之后查询
mysql> use sakila;
Database changed
mysql> show tables;
+----------------------------+
| Tables_in_sakila |
+----------------------------+
| actor |
| actor_info |
| address |
| category |
| city |
| country |
| customer |
| customer_list |
| film |
| film_actor |
| film_category |
| film_list |
| film_text |
| inventory |
| language |
| nicer_but_slower_film_list |
| payment |
| rental |
| sales_by_film_category |
| sales_by_store |
| staff |
| staff_list |
| store |
+----------------------------+
23 rows in set (0.01 sec)
- 检查数据是否已经被加载进去
mysql> select count(*) from film;
+----------+
| count(*) |
+----------+
| 1000 |
+----------+
1 row in set (0.00 sec)
mysql> select count(*) from film_text;
+----------+
| count(*) |
+----------+
| 1000 |
+----------+
1 row in set (0.00 sec)
mysql> select count(*) from payment;
+----------+
| count(*) |
+----------+
| 16049 |
+----------+
1 row in set (0.01 sec)
-
生成表关系 (PowerDisgner工具生成)
如何发现有问题的SQL
Mysql 慢查询日志的开启方式和存储格式
- 检查慢查询日志是否开启
mysql> show variables like 'slow_query_log';
+----------------+-------+
| Variable_name | Value |
+----------------+-------+
| slow_query_log | ON |
+----------------+-------+
1 row in set (0.00 sec)
此时,我的慢查询日志是已经开启的,如果是 OFF 则就是关闭的
查看慢查询的日志存储位置
mysql> show variables like '%log%';
+--------------------------------------------+-------------------------------------------------+
| Variable_name | Value |
+--------------------------------------------+-------------------------------------------------+
| back_log | 150 |
| binlog_cache_size | 32768 |
| binlog_checksum | CRC32 |
| binlog_direct_non_transactional_updates | OFF |
| binlog_error_action | ABORT_SERVER |
| binlog_format | MIXED |
| binlog_group_commit_sync_delay | 0 |
| binlog_group_commit_sync_no_delay_count | 0 |
| binlog_gtid_simple_recovery | ON |
| binlog_max_flush_queue_time | 0 |
| binlog_order_commits | ON |
| binlog_row_image | FULL |
| binlog_rows_query_log_events | OFF |
| binlog_stmt_cache_size | 32768 |
| binlog_transaction_dependency_history_size | 25000 |
| binlog_transaction_dependency_tracking | COMMIT_ORDER |
| expire_logs_days | 10 |
| general_log | OFF |
| general_log_file | /www/server/data/VM_0_14_centos.log |
| innodb_api_enable_binlog | OFF |
| innodb_flush_log_at_timeout | 1 |
| innodb_flush_log_at_trx_commit | 1 |
| innodb_locks_unsafe_for_binlog | OFF |
| innodb_log_buffer_size | 16777216 |
| innodb_log_checksums | ON |
| innodb_log_compressed_pages | ON |
| innodb_log_file_size | 67108864 |
| innodb_log_files_in_group | 2 |
| innodb_log_group_home_dir | /www/server/data |
| innodb_log_write_ahead_size | 8192 |
| innodb_max_undo_log_size | 1073741824 |
| innodb_online_alter_log_max_size | 134217728 |
| innodb_undo_log_truncate | OFF |
| innodb_undo_logs | 128 |
| log_bin | ON |
| log_bin_basename | /www/server/data/mysql-bin |
| log_bin_index | /www/server/data/mysql-bin.index |
| log_bin_trust_function_creators | OFF |
| log_bin_use_v1_row_events | OFF |
| log_builtin_as_identified_by_password | OFF |
| log_error | ./VM_0_14_centos.err |
| log_error_verbosity | 3 |
| log_output | FILE |
| log_queries_not_using_indexes | OFF |
| log_slave_updates | OFF |
| log_slow_admin_statements | OFF |
| log_slow_slave_statements | OFF |
| log_statements_unsafe_for_binlog | ON |
| log_syslog | OFF |
| log_syslog_facility | daemon |
| log_syslog_include_pid | ON |
| log_syslog_tag | |
| log_throttle_queries_not_using_indexes | 0 |
| log_timestamps | UTC |
| log_warnings | 2 |
| max_binlog_cache_size | 18446744073709547520 |
| max_binlog_size | 1073741824 |
| max_binlog_stmt_cache_size | 18446744073709547520 |
| max_relay_log_size | 0 |
| relay_log | |
| relay_log_basename | /www/server/data/VM_0_14_centos-relay-bin |
| relay_log_index | /www/server/data/VM_0_14_centos-relay-bin.index |
| relay_log_info_file | relay-log.info |
| relay_log_info_repository | FILE |
| relay_log_purge | ON |
| relay_log_recovery | OFF |
| relay_log_space_limit | 0 |
| slow_query_log | ON |
| slow_query_log_file | /www/server/data/mysql-slow.log |
| sql_log_bin | ON |
| sql_log_off | OFF |
| sync_binlog | 1 |
| sync_relay_log | 10000 |
| sync_relay_log_info | 10000 |
+--------------------------------------------+-------------------------------------------------+
74 rows in set (0.00 sec)
我的mysql慢查询日志位置就在这里
/www/server/data/mysql-slow.log
设置慢查询的位置 此处我不更改
set global slow_query_log_file='/www/server/data/mysql-slow.log';
//慢查询日志的位置 此处我不设置其他的位置
设置慢查询开启
set global log_queries_not_using_indexes=on;
//开启慢查询日志
设置慢查询记录
set global long_query_time=1;
//大于1秒钟的数据记录到慢日志中,如果设置为默认0,则会有大量的信息存储在磁盘中,磁盘很容易满掉
开启 log_queries_not_using_indexes
mysql> set global log_queries_not_using_indexes=on;
Query OK, 0 rows affected (0.00 sec)
查询日志信息
more /www/server/data/mysql-slow.log
# Time: 2020-02-18T03:22:15.686360Z
# User@Host: root[root] @ localhost [] Id: 116
# Query_time: 0.000346 Lock_time: 0.000193 Rows_sent: 2 Rows_examined: 2
use sakila;
SET timestamp=1581996135;
select * from store;
这三个设置都需要设置
Mysql慢查询日志的存储格式
查询的执行时间
# Time: 2020-02-18T03:22:15.686360Z
执行查询的主机信息
# User@Host: root[root] @ localhost [] Id: 116
SQL的执行信息
Query_time:SQL的执行时间
Lock_time:锁定时间
Rows_sent:发送的行数
Rows_examined:扫描的行数
# Query_time: 0.000346 Lock_time: 0.000193 Rows_sent: 2 Rows_examined: 2
操作的是那一个数据库
use sakila;
SQL的执行时间
SET timestamp=1581996135;
执行的SQL语句
select * from store;
慢查询日志分析工具 mysqldumpslow
介绍:如何进行查看慢查询,如果开启了慢查询日志,就会产生很多数据,然后我们就可以通过对日志进行分析 ,生成日志分析报表,然后根据报表进行优化
用法:
注意:在mysql数据库所在的服务器上,而不是在mysql>命令行中
该工具如何使用:mysqldumpslow -h
这个工具是最常用的工具,通过安装mysql进行附带安装,但是该工具统计的结果比较少,对我们的优化锁表现的数据还是比较少 但是我的数据库没有附带安装 此处不再说明,可自行百度,不是很难
慢查日志分析工具 pt-query-digest
介绍及作用
作为一名优秀的mysql dba也需要有掌握几个好用的mysql管理工具,所以我也一直在整理和查找一些能够便于管理mysql的利器。以后的一段时间内,将会花一大部分的精力去搜索这些工具。
性能的管理一直都是摆在第一位的,dba的很多工作管理层都看不到也没有办法衡量价值,但是如果一个系统慢的跟蜗牛一样,dba通过监控调优把系统从崩溃边 缘重新拉回到高铁时代。这种价值和触动应该是巨大的。(很多企业的领导认为系统跑不动了就需要换更快的CPU、更大的内存、更快的存储,而且这还不是少 数,所以DBA的价值也一直体现不出来,薪水自然也就不会很高)
mysql 的日志是跟踪mysql性能瓶颈的最快和最直接的方式了,系统性能出现瓶颈的时候,首先要打开慢查询日志,进行跟踪;这段时间关于慢查询日志的管理和查看 已经整理过两篇文章了,不经意间又发现了一个查看慢查询日志的工具:mk-query-digest,这个工具网上号称mysql dba必须掌握的十大工具之首。
快速安装(注:必须先要安装wget)
命令
安装 wget
yum install wget
如果已经安装了会出现以下信息
wget https://www.percona.com/downloads/percona-toolkit/2.2.16/RPM/percona-toolkit-2.2.16-1.noarch.rpm && yum localinstall -y percona-toolkit-2.2.16-1.noarch.rpm
检查是否安装完成:
命令行中输入:
pt-summary
显示如下图所示:说明安装成功
或者输入
pt-query-digest --help
显示如下图所示:说明安装成功
pt-query-digest analyzes MySQL queries from slow, general, and binary log files.
It can also analyze queries from C<SHOW PROCESSLIST> and MySQL protocol data
from tcpdump. By default, queries are grouped by fingerprint and reported in
descending order of query time (i.e. the slowest queries first). If no C<FILES>
are given, the tool reads C<STDIN>. The optional C<DSN> is used for certain
options like L<"--since"> and L<"--until">. For more details, please use the
--help option, or try 'perldoc /usr/bin/pt-query-digest' for complete
documentation.
Usage: pt-query-digest [OPTIONS] [FILES] [DSN]
Options:
--ask-pass Prompt for a password when connecting to MySQL
--attribute-aliases=a List of attribute|alias,etc (default db|Schema)
--attribute-value-limit=i A sanity limit for attribute values (default
4294967296)
--charset=s -A Default character set
--config=A Read this comma-separated list of config files;
if specified, this must be the first option on
the command line
--[no]continue-on-error Continue parsing even if there is an error (
default yes)
--[no]create-history-table Create the --history table if it does not exist (
default yes)
--[no]create-review-table Create the --review table if it does not exist (
default yes)
--daemonize Fork to the background and detach from the shell
--database=s -D Connect to this database
--defaults-file=s -F Only read mysql options from the given file
--embedded-attributes=a Two Perl regex patterns to capture pseudo-
attributes embedded in queries
--expected-range=a Explain items when there are more or fewer than
expected (default 5,10)
--explain=d Run EXPLAIN for the sample query with this DSN
and print results
--filter=s Discard events for which this Perl code doesn't
return true
--group-by=A Which attribute of the events to group by (
default fingerprint)
--help Show help and exit
--history=d Save metrics for each query class in the given
table. pt-query-digest saves query metrics (query
time, lock time, etc.) to this table so you can
see how query classes change over time
--host=s -h Connect to host
--ignore-attributes=a Do not aggregate these attributes (default arg,
cmd, insert_id, ip, port, Thread_id, timestamp,
exptime, flags, key, res, val, server_id, offset,
end_log_pos, Xid)
--inherit-attributes=a If missing, inherit these attributes from the
last event that had them (default db,ts)
--interval=f How frequently to poll the processlist, in
seconds (default .1)
--iterations=i How many times to iterate through the collect-and-
report cycle (default 1)
--limit=A Limit output to the given percentage or count (
default 95%:20)
--log=s Print all output to this file when daemonized
--order-by=A Sort events by this attribute and aggregate
function (default Query_time:sum)
--outliers=a Report outliers by attribute:percentile:count (
default Query_time:1:10)
--output=s How to format and print the query analysis
results (default report)
--password=s -p Password to use when connecting
--pid=s Create the given PID file
--port=i -P Port number to use for connection
--processlist=d Poll this DSN's processlist for queries, with --
interval sleep between
--progress=a Print progress reports to STDERR (default time,30)
--read-timeout=m Wait this long for an event from the input; 0 to
wait forever (default 0). Optional suffix s=
seconds, m=minutes, h=hours, d=days; if no
suffix, s is used.
--[no]report Print query analysis reports for each --group-by
attribute (default yes)
--report-all Report all queries, even ones that have been
reviewed
--report-format=A Print these sections of the query analysis
report (default rusage,date,hostname,files,header,
profile,query_report,prepared)
--report-histogram=s Chart the distribution of this attribute's
values (default Query_time)
--resume=s If specified, the tool writes the last file
offset, if there is one, to the given filename
--review=d Save query classes for later review, and don't
report already reviewed classes
--run-time=m How long to run for each --iterations. Optional
suffix s=seconds, m=minutes, h=hours, d=days; if
no suffix, s is used.
--run-time-mode=s Set what the value of --run-time operates on (
default clock)
--sample=i Filter out all but the first N occurrences of
each query
--set-vars=A Set the MySQL variables in this comma-separated
list of variable=value pairs
--show-all=H Show all values for these attributes
--since=s Parse only queries newer than this value (parse
queries since this date)
--socket=s -S Socket file to use for connection
--timeline Show a timeline of events
--type=A The type of input to parse (default slowlog)
--until=s Parse only queries older than this value (parse
queries until this date)
--user=s -u User for login if not current user
--variations=A Report the number of variations in these
attributes' values
--version Show version and exit
--[no]version-check Check for the latest version of Percona Toolkit,
MySQL, and other programs (default yes)
--watch-server=s This option tells pt-query-digest which server IP
address and port (like "10.0.0.1:3306") to watch
when parsing tcpdump (for --type tcpdump); all
other servers are ignored
Option types: s=string, i=integer, f=float, h/H/a/A=comma-separated list, d=DSN, z=size, m=time
Rules:
This tool accepts additional command-line arguments. Refer to the SYNOPSIS and usage information for details.
DSN syntax is key=value[,key=value...] Allowable DSN keys:
KEY COPY MEANING
=== ==== =============================================
A yes Default character set
D yes Default database to use when connecting to MySQL
F yes Only read default options from the given file
P yes Port number to use for connection
S yes Socket file to use for connection
h yes Connect to host
p yes Password to use when connecting
t no The --review or --history table
u yes User for login if not current user
If the DSN is a bareword, the word is treated as the 'h' key.
Options and values after processing arguments:
--ask-pass FALSE
--attribute-aliases db|Schema
--attribute-value-limit 4294967296
--charset (No value)
--config /etc/percona-toolkit/percona-toolkit.conf,/etc/percona-toolkit/pt-query-digest.conf,/root/.percona-toolkit.conf,/root/.pt-query-digest.conf
--continue-on-error TRUE
--create-history-table TRUE
--create-review-table TRUE
--daemonize FALSE
--database (No value)
--defaults-file (No value)
--embedded-attributes (No value)
--expected-range 5,10
--explain (No value)
--filter (No value)
--group-by fingerprint
--help TRUE
--history (No value)
--host (No value)
--ignore-attributes arg,cmd,insert_id,ip,port,Thread_id,timestamp,exptime,flags,key,res,val,server_id,offset,end_log_pos,Xid
--inherit-attributes db,ts
--interval .1
--iterations 1
--limit 95%:20
--log (No value)
--order-by Query_time:sum
--outliers Query_time:1:10
--output report
--password (No value)
--pid (No value)
--port (No value)
--processlist (No value)
--progress time,30
--read-timeout 0
--report TRUE
--report-all FALSE
--report-format rusage,date,hostname,files,header,profile,query_report,prepared
--report-histogram Query_time
--resume (No value)
--review (No value)
--run-time (No value)
--run-time-mode clock
--sample (No value)
--set-vars
--show-all
--since (No value)
--socket (No value)
--timeline FALSE
--type slowlog
--until (No value)
--user (No value)
--variations
--version FALSE
--version-check TRUE
--watch-server (No value)
工具使用简介:
检查是否安装了 上面已经检查
pt-summary --help
如果需要所有的信息 执行以下命令
wget http://percona.com/get/pt-summary
查看服务器信息
命令:pt-summary
# Percona Toolkit System Summary Report ######################
Date | 2020-02-18 03:44:40 UTC (local TZ: CST +0800)
Hostname | VM_0_14_centos
Uptime | 21:30, 2 users, load average: 0.14, 0.06, 0.06
System | Bochs; Bochs; vNot Specified (Other)
Service Tag | a5863a57-d6c0-4578-b72a-46c6e6f1cebf
Platform | Linux
Release | CentOS Linux release 7.6.1810 (Core)
Kernel | 3.10.0-1062.9.1.el7.x86_64
Architecture | CPU = 64-bit, OS = 64-bit
Threading | NPTL 2.17
Compiler | GNU CC version 4.8.5 20150623 (Red Hat 4.8.5-39).
SELinux | Disabled
Virtualized | No virtualization detected
# Processor ##################################################
Processors | physical = 1, cores = 1, virtual = 1, hyperthreading = no
Speeds | 1x2394.454
Models | 1xIntel(R) Xeon(R) CPU E5-26xx v4
Caches | 1x4096 KB
# Memory #####################################################
Total | 1.8G
Free | 159.0M
Used | physical = 890.2M, swap allocated = 0.0, swap used = 0.0, virtual = 890.2M
Buffers | 788.9M
Caches | 782.4M
Dirty | 524 kB
UsedRSS | 1007.7M
Swappiness | 30
DirtyPolicy | 30, 10
DirtyStatus | 0, 0
Locator Size Speed Form Factor Type Type Detail
========= ======== ================= ============= ============= ===========
DIMM 0 2048 MB DIMM RAM None
# Mounted Filesystems ########################################
Filesystem Size Used Type Opts Mountpoint
devtmpfs 909M 0% devtmpfs rw,nosuid,size=930176k,nr_inodes=232544,mode=755 /dev
/dev/vda1 50G 16% ext4 rw,relatime,data=ordered /
tmpfs 184M 0% tmpfs rw,nosuid,nodev /run/user/0
tmpfs 184M 0% tmpfs rw,nosuid,nodev,mode=755 /run/user/0
tmpfs 184M 0% tmpfs rw,nosuid,nodev,relatime,size=188220k,mode=700 /run/user/0
tmpfs 184M 0% tmpfs ro,nosuid,nodev,noexec,mode=755 /run/user/0
tmpfs 920M 0% tmpfs rw,nosuid,nodev /sys/fs/cgroup
tmpfs 920M 0% tmpfs rw,nosuid,nodev,mode=755 /sys/fs/cgroup
tmpfs 920M 0% tmpfs rw,nosuid,nodev,relatime,size=188220k,mode=700 /sys/fs/cgroup
tmpfs 920M 0% tmpfs ro,nosuid,nodev,noexec,mode=755 /sys/fs/cgroup
tmpfs 920M 1% tmpfs rw,nosuid,nodev /dev/shm
tmpfs 920M 1% tmpfs rw,nosuid,nodev,mode=755 /dev/shm
tmpfs 920M 1% tmpfs rw,nosuid,nodev,relatime,size=188220k,mode=700 /dev/shm
tmpfs 920M 1% tmpfs ro,nosuid,nodev,noexec,mode=755 /dev/shm
tmpfs 920M 1% tmpfs rw,nosuid,nodev /run
tmpfs 920M 1% tmpfs rw,nosuid,nodev,mode=755 /run
tmpfs 920M 1% tmpfs rw,nosuid,nodev,relatime,size=188220k,mode=700 /run
tmpfs 920M 1% tmpfs ro,nosuid,nodev,noexec,mode=755 /run
# Disk Schedulers And Queue Size #############################
sr0 | [deadline] 128
vda | [mq-deadline] 256
# Disk Partioning ############################################
Device Type Start End Size
============ ==== ========== ========== ==================
/dev/sr0 Disk 42989568
/dev/vda Disk 53687091200
/dev/vda1 Part 2048 104857566 53686025216
# Kernel Inode State #########################################
dentry-state | 25259 14095 45 0 1581 0
file-nr | 2464 0 183854
inode-nr | 43468 17206
# LVM Volumes ################################################
Unable to collect information
# LVM Volume Groups ##########################################
Unable to collect information
# RAID Controller ############################################
Controller | No RAID controller detected
# Network Config #############################################
Controller | Red Hat, Inc. Virtio network device
FIN Timeout | 60
Port Range | 60999
# Interface Statistics #######################################
interface rx_bytes rx_packets rx_errors tx_bytes tx_packets tx_errors
========= ========= ========== ========== ========== ========== ==========
lo 1250000 15000 0 1250000 15000 0
eth0 900000000 1000000 0 100000000 600000 0
# Network Devices ############################################
Device Speed Duplex
========= ========= =========
eth0
# Network Connections ########################################
Connections from remote IP addresses
74.121.199.234 1
127.0.0.1 1
169.254.0.3 2
169.254.0.55 1
183.208.228.87 4
Connections to local IP addresses
127.0.0.1 1
172.17.0.14 8
Connections to top 10 local ports
22 4
37920 1
48458 1
48460 1
52220 1
8989 1
States of connections
ESTABLISHED 5
LISTEN 15
TIME_WAIT 4
# Top Processes ##############################################
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
29176 www 20 0 2524248 83796 6576 S 6.2 4.5 0:59.68 jsvc
30873 www 20 0 1322176 63356 1956 S 6.2 3.4 0:48.60 jsvc
1 root 20 0 191004 3124 1676 S 0.0 0.2 0:14.64 systemd
2 root 20 0 0 0 0 S 0.0 0.0 0:00.00 kthreadd
4 root 0 -20 0 0 0 S 0.0 0.0 0:00.00 kworker/0:0H
6 root 20 0 0 0 0 S 0.0 0.0 0:03.38 ksoftirqd/0
7 root rt 0 0 0 0 S 0.0 0.0 0:00.00 migration/0
8 root 20 0 0 0 0 S 0.0 0.0 0:00.00 rcu_bh
9 root 20 0 0 0 0 S 0.0 0.0 0:11.70 rcu_sched
# Notable Processes ##########################################
PID OOM COMMAND
1422 -17 sshd
# Simplified and fuzzy rounded vmstat (wait please) ##########
procs ---swap-- -----io---- ---system---- --------cpu--------
r b si so bi bo ir cs us sy il wa st
2 0 0 0 125 300 300 500 6 2 92 0 0
0 0 0 0 80 0 450 1250 7 13 80 0 0
0 0 0 0 0 0 250 500 2 1 97 0 0
0 0 0 0 0 0 175 400 0 0 100 0 0
0 0 0 0 0 0 175 350 0 1 99 0 0
# The End ####################################################
查看磁盘开销使用信息
命令:pt-diskstats
使用 ctrl + c 退出
查看mysql数据库信息
命令:pt-mysql-summary --user=root --password=123456
分析慢查询日志
命令:pt-query-digest /www/server/data/mysql-slow.log
查找mysql的从库和同步状态
命令:pt-slave-find --host=localhost --user=root --password=123456
查看mysql的死锁信息
命令:pt-deadlock-logger --user=root --password=123456 localhost
从慢查询日志中分析索引使用情况
命令:pt-index-usage --user=root --password=123456 localhost /www/server/data/mysql-slow.log
查找所有的使用的命令
history
查找数据库表中重复的索引
命令: pt-duplicate-key-checker --host=localhost --user=root --password=123456
[root@VM_0_14_centos ~]# pt-duplicate-key-checker --host=localhost --user=root --password=123456
# ########################################################################
# Summary of indexes
# ########################################################################
# Total Indexes 96
查看mysql表和文件的当前活动IO开销
命令:pt-ioprofile
[root@VM_0_14_centos ~]# pt-ioprofile
Tue Feb 18 12:13:15 CST 2020
Tracing process ID 22825
total filename
查看不同mysql配置文件的差异
命令:pt-config-diff /etc/my.cnf /etc/my_master.cnf
pt-find查找mysql表和执行命令,示例如下
查找数据库里大于2G的表:
命令:pt-find --user=root --password=123456 --tablesize +2G
查找10天前创建,MyISAM引擎的表:
命令:pt-find --user=root --password=123456 --ctime +10 --engine MyISAM
查看表和索引大小并排序
命令:pt-find --user=root --password=123456 --printf "%T\t%D.%N\n" | sort -rn
命令:pt-kill 杀掉符合标准的mysql进程
显示查询时间大于60秒的查询
命令:pt-kill --user=root --password=123456 --busy-time 60 --print
kill掉大于60秒的查询
命令: pt-kill --user=root --password=123456 --busy-time 60 --kill
查看mysql授权
命令:pt-show-grants --user=root --password=123456
命令:pt-show-grants --user=root --password=123456 --separate –revoke
验证数据库复制的完整性
命令: pt-table-checksum --user=root --password=123456
附录:
如何通过慢查日志发现有问题的SQL
查询次数多且每次查询占用时间长的 SQL
通常为pt-query-digest分析的前几个查询;该工具可以很清楚的看出每个SQL执行的次数及百分比等信息,执行的次数多,占比比较大的SQLIO大的 SQL
注意pt-query-digest分析中的Rows examine项。扫描的行数越多,IO越大。未命中的索引的SQL
注意pt-query-digest分析中的Rows examine 和Rows Send的对比。说明该SQL的索引命中率不高,对于这种SQL,我们要重点进行关注。
通过explain查询分析SQL的执行计划
使用explain查询SQL的执行计划
SQL的执行计划侧面反映出了SQL的执行效率,具体执行方式如下所示:
在执行的SQL前面加上explain关键词即可;
mysql> explain select * from staff;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
| 1 | SIMPLE | staff | NULL | ALL | NULL | NULL | NULL | NULL | 2 | 100.00 | NULL |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)
每个字段的说明:
1)、id列数字越大越先执行,如果说数字一样大,那么就从上往下依次执行,id列为null的就表是这是一个结果集,不需要使用它来进行查询。2)、select_type列常见的有:
A:simple:表示不需要union操作或者不包含子查询的简单select查询。有连接查询时,外层的查询为simple,且只有一个
B:primary:一个需要union操作或者含有子查询的select,位于最外层的单位查询的select_type即为primary。且只有一个
C:union:union连接的两个select查询,第一个查询是dervied派生表,除了第一个表外,第二个以后的表select_type都是union
D:dependent union:与union一样,出现在union 或union all语句中,但是这个查询要受到外部查询的影响
E:union result:包含union的结果集,在union和union all语句中,因为它不需要参与查询,所以id字段为null
F:subquery:除了from子句中包含的子查询外,其他地方出现的子查询都可能是subquery
G:dependent subquery:与dependent union类似,表示这个subquery的查询要受到外部表查询的影响
H:derived:from字句中出现的子查询,也叫做派生表,其他数据库中可能叫做内联视图或嵌套select
3)、table
显示的查询表名,如果查询使用了别名,那么这里显示的是别名,如果不涉及对数据表的操作,那么这显示为null,如果显示为尖括号括起来的<derived N>就表示这个是临时表,后边的N就是执行计划中的id,表示结果来自于这个查询产生。如果是尖括号括起来的<union M,N>,与<derived N>类似,也是一个临时表,表示这个结果来自于union查询的id为M,N的结果集。4)、type
依次从好到差:system,const,eq_ref,ref,fulltext,ref_or_null,unique_subquery,index_subquery,range,index_merge,index,ALL,除了all之外,其他的type都可以使用到索引,除了index_merge之外,其他的type只可以用到一个索引A:system:表中只有一行数据或者是空表,且只能用于myisam和memory表。如果是Innodb引擎表,type列在这个情况通常都是all或者index
B:const:使用唯一索引或者主键,返回记录一定是1行记录的等值where条件时,通常type是const。其他数据库也叫做唯一索引扫描
C:eq_ref:出现在要连接过个表的查询计划中,驱动表只返回一行数据,且这行数据是第二个表的主键或者唯一索引,且必须为not null,唯一索引和主键是多列时,只有所有的列都用作比较时才会出现eq_ref
D:ref:不像eq_ref那样要求连接顺序,也没有主键和唯一索引的要求,只要使用相等条件检索时就可能出现,常见与辅助索引的等值查找。或者多列主键、唯一索引中,使用第一个列之外的列作为等值查找也会出现,总之,返回数据不唯一的等值查找就可能出现。
E:fulltext:全文索引检索,要注意,全文索引的优先级很高,若全文索引和普通索引同时存在时,mysql不管代价,优先选择使用全文索引
F:ref_or_null:与ref方法类似,只是增加了null值的比较。实际用的不多。
G:unique_subquery:用于where中的in形式子查询,子查询返回不重复值唯一值
H:index_subquery:用于in形式子查询使用到了辅助索引或者in常数列表,子查询可能返回重复值,可以使用索引将子查询去重。
I:range:索引范围扫描,常见于使用>,<,is null,between ,in ,like等运算符的查询中。
J:index_merge:表示查询使用了两个以上的索引,最后取交集或者并集,常见and ,or的条件使用了不同的索引,官方排序这个在ref_or_null之后,但是实际上由于要读取所个索引,性能可能大部分时间都不如range
K:index:索引全表扫描,把索引从头到尾扫一遍,常见于使用索引列就可以处理不需要读取数据文件的查询、可以使用索引排序或者分组的查询。
L:all:这个就是全表扫描数据文件,然后再在server层进行过滤返回符合要求的记录。
5)、possible_keys
查询可能使用到的索引都会在这里列出来6)、key
查询真正使用到的索引,select_type为index_merge时,这里可能出现两个以上的索引,其他的select_type这里只会出现一个。7)、key_len
用于处理查询的索引长度,如果是单列索引,那就整个索引长度算进去,如果是多列索引,那么查询不一定都能使用到所有的列,具体使用到了多少个列的索引,这里就会计算进去,没有使用到的列,这里不会计算进去。留意下这个列的值,算一下你的多列索引总长度就知道有没有使用到所有的列了。要注意,mysql的ICP特性使用到的索引不会计入其中。另外,key_len只计算where条件用到的索引长度,而排序和分组就算用到了索引,也不会计算到key_len中。8)、ref
如果是使用的常数等值查询,这里会显示const,如果是连接查询,被驱动表的执行计划这里会显示驱动表的关联字段,如果是条件使用了表达式或者函数,或者条件列发生了内部隐式转换,这里可能显示为func9)、rows
这里是执行计划中估算的扫描行数,不是精确值10)、extra
这个列可以显示的信息非常多,有几十种,常用的有A:distinct:在select部分使用了distinc关键字
B:no tables used:不带from字句的查询或者From dual查询
C:使用not in()形式子查询或not exists运算符的连接查询,这种叫做反连接。即,一般连接查询是先查询内表,再查询外表,反连接就是先查询外表,再查询内表。
D:using filesort:排序时无法使用到索引时,就会出现这个。常见于order by和group by语句中
E:using index:查询时不需要回表查询,直接通过索引就可以获取查询的数据。
F:using join buffer(block nested loop),using join buffer(batched key accss):5.6.x之后的版本优化关联查询的BNL,BKA特性。主要是减少内表的循环数量以及比较顺序地扫描查询。
G:using sort_union,using_union,using intersect,using sort_intersection:
using intersect:表示使用and的各个索引的条件时,该信息表示是从处理结果获取交集
using union:表示使用or连接各个使用索引的条件时,该信息表示从处理结果获取并集
using sort_union和using sort_intersection:与前面两个对应的类似,只是他们是出现在用and和or查询信息量大时,先查询主键,然后进行排序合并后,才能读取记录并返回。H:using temporary:表示使用了临时表存储中间结果。临时表可以是内存临时表和磁盘临时表,执行计划中看不出来,需要查看status变量,used_tmp_table,used_tmp_disk_table才能看出来。
I:using where:表示存储引擎返回的记录并不是所有的都满足查询条件,需要在server层进行过滤。查询条件中分为限制条件和检查条件,5.6之前,存储引擎只能根据限制条件扫描数据并返回,然后server层根据检查条件进行过滤再返回真正符合查询的数据。5.6.x之后支持ICP特性,可以把检查条件也下推到存储引擎层,不符合检查条件和限制条件的数据,直接不读取,这样就大大减少了存储引擎扫描的记录数量。extra列显示using index condition
J:firstmatch(tb_name):5.6.x开始引入的优化子查询的新特性之一,常见于where字句含有in()类型的子查询。如果内表的数据量比较大,就可能出现这个
K:loosescan(m..n):5.6.x之后引入的优化子查询的新特性之一,在in()类型的子查询中,子查询返回的可能有重复记录时,就可能出现这个
除了这些之外,还有很多查询数据字典库,执行计划过程中就发现不可能存在结果的一些提示信息
11)、filtered
使用explain extended时会出现这个列,5.7之后的版本默认就有这个字段,不需要使用explain extended了。这个字段表示存储引擎返回的数据在server层过滤后,剩下多少满足查询的记录数量的比例,注意是百分比,不是具体记录数。
其他
具体慢查询的优化案例
函数Max()的优化
用途:查询最后支付时间-优化max()函数
语句:
select max(payment_date) from payment;
mysql> select max(payment_date) from payment;
+---------------------+
| max(payment_date) |
+---------------------+
| 2006-02-14 15:16:03 |
+---------------------+
1 row in set (0.00 sec)
执行计划:
explain select max(payment_date) from payment;
mysql> explain select max(payment_date) from payment;
+----+-------------+---------+------------+------+---------------+------+---------+------+-------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+---------+------------+------+---------------+------+---------+------+-------+----------+-------+
| 1 | SIMPLE | payment | NULL | ALL | NULL | NULL | NULL | NULL | 16086 | 100.00 | NULL |
+----+-------------+---------+------------+------+---------------+------+---------+------+-------+----------+-------+
1 row in set, 1 warning (0.00 sec)
执行计划
explain select max(payment_date) from payment\G
mysql> explain select max(payment_date) from payment\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: payment
partitions: NULL
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 16086
filtered: 100.00
Extra: NULL
1 row in set, 1 warning (0.00 sec)
可以看到显示的执行计划,并不是很高效,可以拖慢服务器的效率,如何优化了?
创建索引
mysql> create index inx_paydate on payment(payment_date);
Query OK, 0 rows affected (0.11 sec)
Records: 0 Duplicates: 0 Warnings: 0
创建索引之后
mysql> explain select max(payment_date) from payment\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: NULL
partitions: NULL
type: NULL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: NULL
filtered: NULL
Extra: Select tables optimized away
1 row in set, 1 warning (0.00 sec)
索引是顺序操作的,不需要扫描表,执行效率就会比较恒定,
函数Count()的优化
需求:在一条SQL中同时查处2006年和2007年电影的数量
错误的方式:
语句:
select count(release_year='2006' or release_year='2007') from film;
mysql> select count(release_year='2006' or release_year='2007') from film;
+---------------------------------------------------+
| count(release_year='2006' or release_year='2007') |
+---------------------------------------------------+
| 1000 |
+---------------------------------------------------+
1 row in set (0.00 sec)
mysql> explain select count(release_year='2006' or release_year='2007') from film;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
| 1 | SIMPLE | film | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | 100.00 | NULL |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)
2006和2007年分别是多少,判断不出来
select count(*) from film where release_year='2006' or release_year='2007';
mysql> select count(*) from film where release_year='2006' or release_year='2007';
+----------+
| count(*) |
+----------+
| 1000 |
+----------+
1 row in set (0.00 sec)
正确的编写方式:
select count(release_year='2006' or null) as '06films',count(release_year='2007' or null) as '07films' from film;
mysql> select count(release_year='2006' or null) as '06films',count(release_year='2007' or null) as '07films' from film;
+---------+---------+
| 06films | 07films |
+---------+---------+
| 1000 | 0 |
+---------+---------+
1 row in set (0.00 sec)
区别:count(*)和count(id)
创建表并插入语句
mysql> create table t(id int);
Query OK, 0 rows affected (0.06 sec)
mysql>
mysql> insert into t values(1),(2),(null);
Query OK, 3 rows affected (0.01 sec)
Records: 3 Duplicates: 0 Warnings: 0
查询
mysql> select * from t;
+------+
| id |
+------+
| 1 |
| 2 |
| NULL |
+------+
3 rows in set (0.00 sec)
mysql> select count(*) from t;
+----------+
| count(*) |
+----------+
| 3 |
+----------+
1 row in set (0.00 sec)
mysql> select count(id) from t;
+-----------+
| count(id) |
+-----------+
| 2 |
+-----------+
1 row in set (0.00 sec)
说明:
Count(id)是不包含null的值
Count(*)是包含null的值
子查询的优化
子查询是我们在开发过程中经常使用的一种方式,在通常情况下,需要把子查询优化为join查询但在优化是需要注意关联键是否有一对多的关系,要注意重复数据。
查看我们所创建的t表
mysql> show create table t;
+-------+----------------------------------------------------------------------------------------+
| Table | Create Table |
+-------+----------------------------------------------------------------------------------------+
| t | CREATE TABLE `t` (
`id` int(11) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 |
+-------+----------------------------------------------------------------------------------------+
1 row in set (0.00 sec)
接下来创建一个t1表 并插入一条数据
create table t1(tid int);
insert into t1 values (1);
进行一个子查询,需求:查询t表中id在t1表中tid的所有数据;
mysql> select * from t where t.id in (select t1.tid from t1);
+------+
| id |
+------+
| 1 |
+------+
1 row in set (0.00 sec)
接下来用join的操作来进行操作
mysql> select id from t join t1 on t.id =t1.tid;
+------+
| id |
+------+
| 1 |
+------+
1 row in set (0.00 sec)
分别查看执行计划
mysql> explain select * from t where t.id in (select t1.tid from t1);
+----+--------------+-------------+------------+------+---------------+------+---------+------+------+----------+----------------------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+--------------+-------------+------------+------+---------------+------+---------+------+------+----------+----------------------------------------------------+
| 1 | SIMPLE | <subquery2> | NULL | ALL | NULL | NULL | NULL | NULL | NULL | 100.00 | NULL |
| 1 | SIMPLE | t | NULL | ALL | NULL | NULL | NULL | NULL | 3 | 33.33 | Using where; Using join buffer (Block Nested Loop) |
| 2 | MATERIALIZED | t1 | NULL | ALL | NULL | NULL | NULL | NULL | 1 | 100.00 | NULL |
+----+--------------+-------------+------------+------+---------------+------+---------+------+------+----------+----------------------------------------------------+
3 rows in set, 1 warning (0.00 sec)
mysql> explain select id from t join t1 on t.id =t1.tid;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------------------------------------------+
| 1 | SIMPLE | t1 | NULL | ALL | NULL | NULL | NULL | NULL | 1 | 100.00 | NULL |
| 1 | SIMPLE | t | NULL | ALL | NULL | NULL | NULL | NULL | 3 | 33.33 | Using where; Using join buffer (Block Nested Loop) |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------------------------------------------+
2 rows in set, 1 warning (0.00 sec)
执行计划
mysql> explain select * from t where t.id in (select t1.tid from t1)\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: <subquery2>
partitions: NULL
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: NULL
filtered: 100.00
Extra: NULL
*************************** 2. row ***************************
id: 1
select_type: SIMPLE
table: t
partitions: NULL
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 3
filtered: 33.33
Extra: Using where; Using join buffer (Block Nested Loop)
*************************** 3. row ***************************
id: 2
select_type: MATERIALIZED
table: t1
partitions: NULL
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 1
filtered: 100.00
Extra: NULL
3 rows in set, 1 warning (0.00 sec)
mysql> explain select id from t join t1 on t.id =t1.tid\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: t1
partitions: NULL
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 1
filtered: 100.00
Extra: NULL
*************************** 2. row ***************************
id: 1
select_type: SIMPLE
table: t
partitions: NULL
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 3
filtered: 33.33
Extra: Using where; Using join buffer (Block Nested Loop)
2 rows in set, 1 warning (0.00 sec)
通过上面结果来看,查询的结果是一致的,我们就将子查询的方式优化为join操作。
接下来,在t1表中再插入一条数据
insert into t1 values (1);
在这种情况下,如果我们使用子查询方式进行查询,返回的结果就是如下所示:
mysql> select * from t where t.id in (select t1.tid from t1);
+------+
| id |
+------+
| 1 |
+------+
1 row in set (0.00 sec)
如果使用join方式进行查找,如下所示:
mysql> select id from t join t1 on t.id =t1.tid;
+------+
| id |
+------+
| 1 |
| 1 |
+------+
2 rows in set (0.00 sec)
在这种情况下出现了一对多的关系,会出现数据的重复,我们为了方式数据重复,不得不使用distinct关键词进行去重操作
mysql> select distinct id from t join t1 on t.id =t1.tid;
+------+
| id |
+------+
| 1 |
+------+
1 row in set (0.00 sec)
注意:这个一对多的关系是开发过程中遇到的一个坑,出现数据重复,需要大家注意一下。
例子:查询sandra出演的所有影片:
explain select title,release_year,length
from film
where film_id in (
select film_id from film_actor where actor_id in (
select actor_id from actor where first_name='sandra'));
mysql> explain select title,release_year,length
-> from film
-> where film_id in (
-> select film_id from film_actor where actor_id in (
-> select actor_id from actor where first_name='sandra'));
+----+--------------+-------------+------------+--------+------------------------+---------+---------+-----------------------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+--------------+-------------+------------+--------+------------------------+---------+---------+-----------------------+------+----------+-------------+
| 1 | SIMPLE | <subquery2> | NULL | ALL | NULL | NULL | NULL | NULL | NULL | 100.00 | NULL |
| 1 | SIMPLE | film | NULL | eq_ref | PRIMARY | PRIMARY | 2 | <subquery2>.film_id | 1 | 100.00 | NULL |
| 2 | MATERIALIZED | actor | NULL | ALL | PRIMARY | NULL | NULL | NULL | 200 | 10.00 | Using where |
| 2 | MATERIALIZED | film_actor | NULL | ref | PRIMARY,idx_fk_film_id | PRIMARY | 2 | sakila.actor.actor_id | 27 | 100.00 | Using index |
+----+--------------+-------------+------------+--------+------------------------+---------+---------+-----------------------+------+----------+-------------+
4 rows in set, 1 warning (0.00 sec)
group by的优化
最好使用同一表中的列,
需求:每个演员所参演影片的数量-(影片表和演员表)
explain select actor.first_name,actor.last_name,count(*)
from sakila.film_actor
inner join sakila.actor using(actor_id)
group by film_actor.actor_id;
mysql> explain select actor.first_name,actor.last_name,count(*)
-> from sakila.film_actor
-> inner join sakila.actor using(actor_id)
-> group by film_actor.actor_id;
+----+-------------+------------+------------+------+------------------------+---------+---------+-----------------------+------+----------+---------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+------------+------------+------+------------------------+---------+---------+-----------------------+------+----------+---------------------------------+
| 1 | SIMPLE | actor | NULL | ALL | PRIMARY | NULL | NULL | NULL | 200 | 100.00 | Using temporary; Using filesort |
| 1 | SIMPLE | film_actor | NULL | ref | PRIMARY,idx_fk_film_id | PRIMARY | 2 | sakila.actor.actor_id | 27 | 100.00 | Using index |
+----+-------------+------------+------------+------+------------------------+---------+---------+-----------------------+------+----------+---------------------------------+
2 rows in set, 1 warning (0.00 sec)
mysql> ^C
mysql> explain select actor.first_name,actor.last_name,count(*) from sakila.film_actor inner join sakila.actor using(actor_id) group by film_actor.actor_id\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: actor
partitions: NULL
type: ALL
possible_keys: PRIMARY
key: NULL
key_len: NULL
ref: NULL
rows: 200
filtered: 100.00
Extra: Using temporary; Using filesort
*************************** 2. row ***************************
id: 1
select_type: SIMPLE
table: film_actor
partitions: NULL
type: ref
possible_keys: PRIMARY,idx_fk_film_id
key: PRIMARY
key_len: 2
ref: sakila.actor.actor_id
rows: 27
filtered: 100.00
Extra: Using index
2 rows in set, 1 warning (0.00 sec)
优化之后的SQL
explain select actor.first_name,actor.last_name,c.cnt
from sakila.actor inner join (
select actor_id,count(*) as cnt from sakila.film_actor group by actor_id
)as c using(actor_id);
mysql> explain select actor.first_name,actor.last_name,c.cnt from sakila.actor inner join ( select actor_id,count(*) as cnt from sakila.film_actor group by actor_id )as c using(actor_id)\G
*************************** 1. row ***************************
id: 1
select_type: PRIMARY
table: actor
partitions: NULL
type: ALL
possible_keys: PRIMARY
key: NULL
key_len: NULL
ref: NULL
rows: 200
filtered: 100.00
Extra: NULL
*************************** 2. row ***************************
id: 1
select_type: PRIMARY
table: <derived2>
partitions: NULL
type: ref
possible_keys: <auto_key0>
key: <auto_key0>
key_len: 2
ref: sakila.actor.actor_id
rows: 27
filtered: 100.00
Extra: NULL
*************************** 3. row ***************************
id: 2
select_type: DERIVED
table: film_actor
partitions: NULL
type: index
possible_keys: PRIMARY,idx_fk_film_id
key: PRIMARY
key_len: 4
ref: NULL
rows: 5462
filtered: 100.00
Extra: Using index
3 rows in set, 1 warning (0.00 sec)
说明:从上面的执行计划来看,这种优化后的方式没有使用临时文件和文件排序的方式了,取而代之的是使用了索引。查询效率很高了。
这个时候我们表中的数据比较大,会大量的占用IO操作,优化了sql执行的效率,节省了服务器的资源,因此我们就需要优化。
注意:
1、mysql 中using关键词的作用:也就是说要使用using,那么表a和表b必须要有相同的列。
2、在用Join进行多表联合查询时,我们通常使用On来建立两个表的关系。其实还有一个更方便的关键字,那就是Using。
3、如果两个表的关联字段名是一样的,就可以使用Using来建立关系,简洁明了。
Limit查询的优化
Limit常用于分页处理,时长会伴随order by从句使用,因此大多时候回使用Filesorts这样会造成大量的IO问题。
例子:
需求:查询影片id和描述信息,并根据主题进行排序,取出从序号50条开始的5条数据。
mysql> select film_id,description from sakila.film order by title limit 50,5;
+---------+---------------------------------------------------------------------------------------------------------------------------------+
| film_id | description |
+---------+---------------------------------------------------------------------------------------------------------------------------------+
| 51 | A Insightful Panorama of a Forensic Psychologist And a Mad Cow who must Build a Mad Scientist in The First Manned Space Station |
| 52 | A Thrilling Documentary of a Composer And a Monkey who must Find a Feminist in California |
| 53 | A Epic Drama of a Madman And a Cat who must Face a A Shark in An Abandoned Amusement Park |
| 54 | A Awe-Inspiring Drama of a Car And a Pastry Chef who must Chase a Crocodile in The First Manned Space Station |
| 55 | A Awe-Inspiring Story of a Feminist And a Cat who must Conquer a Dog in A Monastery |
+---------+---------------------------------------------------------------------------------------------------------------------------------+
5 rows in set (0.00 sec)
在查看一下它的执行计划:
mysql> explain select film_id,description from sakila.film order by title limit 50,5;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------+
| 1 | SIMPLE | film | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | 100.00 | Using filesort |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------+
1 row in set, 1 warning (0.00 sec)
mysql> explain select film_id,description from sakila.film order by title limit 50,5\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: film
partitions: NULL
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 1000
filtered: 100.00
Extra: Using filesort
1 row in set, 1 warning (0.00 sec)
对于这种操作,我们该用什么样的优化方式了?
优化步骤1:
使用有索引的列或主键进行order by操作,因为大家知道,innodb是按照主键的逻辑顺序进行排序的。可以避免很多的IO操作。
mysql> select film_id,description from sakila.film order by film_id limit 50,5;
+---------+---------------------------------------------------------------------------------------------------------------------------------+
| film_id | description |
+---------+---------------------------------------------------------------------------------------------------------------------------------+
| 51 | A Insightful Panorama of a Forensic Psychologist And a Mad Cow who must Build a Mad Scientist in The First Manned Space Station |
| 52 | A Thrilling Documentary of a Composer And a Monkey who must Find a Feminist in California |
| 53 | A Epic Drama of a Madman And a Cat who must Face a A Shark in An Abandoned Amusement Park |
| 54 | A Awe-Inspiring Drama of a Car And a Pastry Chef who must Chase a Crocodile in The First Manned Space Station |
| 55 | A Awe-Inspiring Story of a Feminist And a Cat who must Conquer a Dog in A Monastery |
+---------+---------------------------------------------------------------------------------------------------------------------------------+
5 rows in set (0.00 sec)
mysql> explain select film_id,description from sakila.film order by film_id limit 50,5;
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------+
| 1 | SIMPLE | film | NULL | index | NULL | PRIMARY | 2 | NULL | 55 | 100.00 | NULL |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)
mysql> explain select film_id,description from sakila.film order by film_id limit 50,5\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: film
partitions: NULL
type: index
possible_keys: NULL
key: PRIMARY
key_len: 2
ref: NULL
rows: 55
filtered: 100.00
Extra: NULL
1 row in set, 1 warning (0.00 sec)
那如果我们获取从500行开始的5条记录,执行计划又是什么样的了?
mysql> explain select film_id,description from sakila.film order by film_id limit 500,5\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: film
partitions: NULL
type: index
possible_keys: NULL
key: PRIMARY
key_len: 2
ref: NULL
rows: 505
filtered: 100.00
Extra: NULL
1 row in set, 1 warning (0.00 sec)
随着我们翻页越往后,IO操作会越来越大的,如果一个表有几千万行数据,翻页越后面,会越来越慢,因此我们要进一步的来优化。
优化步骤2、记录上次返回的主键, 在下次查询时使用主键过滤。(说明:避免了数据量大时扫描过多的记录)
上次limit是50,5的操作,因此我们在这次优化过程需要使用上次的索引记录值,
mysql> explain select film_id,description from sakila.film where film_id >55 and film_id<=60 order by film_id limit 1,5;
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
| 1 | SIMPLE | film | NULL | range | PRIMARY | PRIMARY | 2 | NULL | 5 | 100.00 | Using where |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)
mysql> ^C
mysql> explain select film_id,description from sakila.film where film_id >55 and film_id<=60 order by film_id limit 1,5\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: film
partitions: NULL
type: range
possible_keys: PRIMARY
key: PRIMARY
key_len: 2
ref: NULL
rows: 5
filtered: 100.00
Extra: Using where
1 row in set, 1 warning (0.01 sec)
结论:扫描行数不变,执行计划是很固定,效率也是很固定的
注意事项:
主键要顺序排序并连续的,如果主键中间空缺了某一列,或者某几列,会出现列出数据不足5行的数据;如果不连续的情况,建立一个附加的列index_id列,保证这一列数据要自增的,并添加索引即可。
索引的优化
1、什么是索引?
索引的作用相当于图书的目录,可以根据目录中的页码快速找到所需的内容。
数据库使用索引以找到特定值,然后顺指针找到包含该值的行。在表中建立索引,然后在索引中找到符合查询条件的索引值,最后通过保存在索引中的ROWID(相当于页码)快速找到表中对应的记录。索引的建立是表中比较有指向性的字段,相当于目录,比如说行政区域代码,同一个地域的行政区域代码都是相同的,那么给这一列加上索引,避免让它重复扫描,从而达到优化的目的!
2、如何创建索引
在执行CREATE TABLE语句时可以创建索引,也可以单独用CREATE INDEX或ALTER TABLE来为表增加索引。
1、ALTER TABLE
ALTER TABLE用来创建普通索引、UNIQUE索引或PRIMARY KEY索引。
ALTER TABLE table_name ADD INDEX index_name (column_list)
ALTER TABLE table_name ADD UNIQUE (column_list)
ALTER TABLE table_name ADD PRIMARY KEY (column_list)
说明:其中table_name是要增加索引的表名,column_list指出对哪些列进行索引,多列时各列之间用逗号分隔。索引名index_name可选,缺省时,MySQL将根据第一个索引列赋一个名称。另外,ALTER TABLE允许在单个语句中更改多个表,因此可以在同时创建多个索引。
2、CREATE INDEX
CREATE INDEX可对表增加普通索引或UNIQUE索引。
CREATE INDEX index_name ON table_name (column_list)
CREATE UNIQUE INDEX index_name ON table_name (column_list)
说明:table_name、index_name和column_list具有与ALTER TABLE语句中相同的含义,索引名不可选。另外,不能用CREATE INDEX语句创建PRIMARY KEY索引。
3、索引类型
在创建索引时,可以规定索引能否包含重复值。如果不包含,则索引应该创建为PRIMARY KEY或UNIQUE索引。对于单列惟一性索引,这保证单列不包含重复的值。对于多列惟一性索引,保证多个值的组合不重复。
PRIMARY KEY索引和UNIQUE索引非常类似。
事实上,PRIMARY KEY索引仅是一个具有名称PRIMARY的UNIQUE索引。这表示一个表只能包含一个PRIMARY KEY,因为一个表中不可能具有两个同名的索引。
下面的SQL语句对students表在sid上添加PRIMARY KEY索引。
ALTER TABLE students ADD PRIMARY KEY (sid)
4、删除索引
可利用ALTER TABLE或DROP INDEX语句来删除索引。类似于CREATE INDEX语句,DROP INDEX可以在ALTER TABLE内部作为一条语句处理,语法如下。
DROP INDEX index_name ON talbe_name
ALTER TABLE table_name DROP INDEX index_name
ALTER TABLE table_name DROP PRIMARY KEY
其中,前两条语句是等价的,删除掉table_name中的索引index_name。
第3条语句只在删除PRIMARY KEY索引时使用,因为一个表只可能有一个PRIMARY KEY索引,因此不需要指定索引名。如果没有创建PRIMARY KEY索引,但表具有一个或多个UNIQUE索引,则MySQL将删除第一个UNIQUE索引。
如果从表中删除了某列,则索引会受到影响。对于多列组合的索引,如果删除其中的某列,则该列也会从索引中删除。如果删除组成索引的所有列,则整个索引将被删除。
5、查看索引
mysql> show index from film;
+-------+------------+-----------------------------+--------------+----------------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+-------+------------+-----------------------------+--------------+----------------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| film | 0 | PRIMARY | 1 | film_id | A | 1000 | NULL | NULL | | BTREE | | |
| film | 1 | idx_title | 1 | title | A | 1000 | NULL | NULL | | BTREE | | |
| film | 1 | idx_fk_language_id | 1 | language_id | A | 1 | NULL | NULL | | BTREE | | |
| film | 1 | idx_fk_original_language_id | 1 | original_language_id | A | 1 | NULL | NULL | YES | BTREE | | |
+-------+------------+-----------------------------+--------------+----------------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
4 rows in set (0.00 sec)
6、什么情况下,使用索引了?
1、表的主关键字
2、自动建立唯一索引
3、表的字段唯一约束
4、直接条件查询的字段(在SQL中用于条件约束的字段)
5、查询中与其它表关联的字段
6、查询中排序的字段(排序的字段如果通过索引去访问那将大大提高排序速度)
7、查询中统计或分组统计的字段
8、表记录太少(如果一个表只有5条记录,采用索引去访问记录的话,那首先需访问索引表,再通过索引表访问数据表,一般索引表与数据表不在同一个数据块)
9、经常插入、删除、修改的表(对一些经常处理的业务表应在查询允许的情况下尽量减少索引)
10、数据重复且分布平均的表字段(假如一个表有10万行记录,有一个字段A只有T和F两种值,且每个值的分布概率大约为50%,那么对这种表A字段建索引一般不会提高数据库的查询速度。)
11、经常和主字段一块查询但主字段索引值比较多的表字段
12、对千万级MySQL数据库建立索引的事项及提高性能的手段
如何选择合适的列建立索引
1、在where从句,group by从句,order by从句,on从句中的列添加索引
2、索引字段越小越好(因为数据库数据存储单位是以“页”为单位的,数据存储的越多,IO也会越大)
3、离散度大的列放到联合索引的前面
例子:
select * from payment where staff_id =2 and customer_id =584;
注意:
是index(staff_id,customer_id)好,还是index(customer_id,staff_id)好?
那我们怎么进行验证离散度好了?
A、我们先查看一下表结构
mysql> desc payment;
+--------------+----------------------+------+-----+-------------------+-----------------------------+
| Field | Type | Null | Key | Default | Extra |
+--------------+----------------------+------+-----+-------------------+-----------------------------+
| payment_id | smallint(5) unsigned | NO | PRI | NULL | auto_increment |
| customer_id | smallint(5) unsigned | NO | MUL | NULL | |
| staff_id | tinyint(3) unsigned | NO | MUL | NULL | |
| rental_id | int(11) | YES | MUL | NULL | |
| amount | decimal(5,2) | NO | | NULL | |
| payment_date | datetime | NO | MUL | NULL | |
| last_update | timestamp | YES | | CURRENT_TIMESTAMP | on update CURRENT_TIMESTAMP |
+--------------+----------------------+------+-----+-------------------+-----------------------------+
7 rows in set (0.00 sec)
B、分别查看这两个字段中不同的id的数量,数量越多,则表明离散程度越大:因此可以通过下图看出:customer_id 离散程度大。
结论:由于customer_id 离散程度大,使用index(customer_id,staff_id)好
C、mysql联合索引
①命名规则 :表名_字段名
1、需要加索引的字段,要在where条件中
2、数据量少的字段不需要加索引
3、如果where条件中是OR关系,加索引不起作用
4、符合最左原则
②什么是联合索引
1、 两个或更多个列上的索引被称作联合索引,又被称为是复合索引。
2、 利用索引中的附加列,您可以缩小搜索的范围,但使用一个具有两列的索引 不同于使用两个单独的索引。复合索引的结构与电话簿类似,人名由姓和名构成,电话簿首先按姓氏对进行排序,然后按名字对有相同姓氏的人进行排序。如果您知 道姓,电话簿将非常有用;如果您知道姓和名,电话簿则更为有用,但如果您只知道名不姓,电话簿将没有用处。所以说创建复合索引时,应该仔细考虑列的顺序。对索引中的所有列执行搜索或仅对前几列执行搜索时,复合索引非常有用;仅对后面的任意列执行搜索时,复合索引则没有用处。
索引优化SQL的方法
1、索引的维护及优化(重复及冗余索引)
增加索引会有利于查询效率,但会降低insert,update,delete的效率,但实际上往往不是这样的,过多的索引会不但会影响使用效率,同时会影响查询效率,这是由于数据库进行查询分析时,首先要选择使用哪一个索引进行查询,如果索引过多,分析过程就会越慢,这样同样的减少查询的效率,因此我们要知道如何增加,有时候要知道维护和删除不需要的索引
2、如何找到重复和冗余的索引
重复索引:
重复索引是指相同的列以相同的顺序建立的同类型的索引,如下表中的 primary key和ID列上的索引就是重复索引
create table test(
id int not null primary key,
name varchar(10) not null,
title varchar(50) not null,
unique(id)
)engine=innodb;
冗余索引:
冗余索引是指多个索引的前缀列相同,或是在联合索引中包含了主键的索引,下面这个例子中key(name,id)就是一个冗余索引。
create table test(
id int not null primary key,
name varchar(10) not null,
title varchar(50) not null,
key(name,id)
)engine=innodb;
说明:对于innodb来说,每一个索引后面,实际上都会包含主键,这时候我们建立的联合索引,又人为的把主键包含进去,那么这个时候就是一个冗余索引。
3、如何查找重复索引
工具:使用pt-duplicate-key-checker工具检查重复及冗余索引
pt-duplicate-key-checker -uroot -padmin -h 127.0.0.1
4、索引维护的方法
由于业务变更,某些索引是后续不需要使用的,就要进行删除。
在mysql中,目前只能通过慢查询日志配合pt-index-usage工具来进行索引使用情况的分析;
pt-index-usage -uroot -padmin /var/lib/mysql/mysql-host-slow.log
附:https://www.percona.com/downloads/
注意事项
设计好MySql的索引可以让你的数据库飞起来,大大的提高数据库效率。设计MySql索引的时候有一下几点注意:
1,创建索引
对于查询占主要的应用来说,索引显得尤为重要。很多时候性能问题很简单的就是因为我们忘了添加索引而造成的,或者说没有添加更为有效的索引导致。如果不加索引的话,那么查找任何哪怕只是一条特定的数据都会进行一次全表扫描,如果一张表的数据量很大而符合条件的结果又很少,那么不加索引会引起致命的性能下降。
但是也不是什么情况都非得建索引不可,比如性别可能就只有两个值,建索引不仅没什么优势,还会影响到更新速度,这被称为过度索引。
2,复合索引
比如有一条语句是这样的:select * from users where area=’beijing’ and age=22;
如果我们是在area和age上分别创建单个索引的话,由于mysql查询每次只能使用一个索引,所以虽然这样已经相对不做索引时全表扫描提高了很多效率,但是如果在area、age两列上创建复合索引的话将带来更高的效率。如果我们创建了(area, age,salary)的复合索引,那么其实相当于创建了(area,age,salary)、(area,age)、(area)三个索引,这被称为最佳左前缀特性。
因此我们在创建复合索引时应该将最常用作限制条件的列放在最左边,依次递减。
3,索引不会包含有NULL值的列
只要列中包含有NULL值都将不会被包含在索引中,复合索引中只要有一列含有NULL值,那么这一列对于此复合索引就是无效的。所以我们在数据库设计时不要让字段的默认值为NULL。
4,使用短索引
对字符串列进行索引,如果可能应该指定一个前缀长度。例如,如果有一个CHAR(255)的 列,如果在前10 个或20 个字符内,多数值是惟一的,那么就不要对整个列进行索引。短索引不仅可以提高查询速度而且可以节省磁盘空间和I/O操作。
5,排序的索引问题
mysql查询只使用一个索引,因此如果where子句中已经使用了索引的话,那么order by中的列是不会使用索引的。因此数据库默认排序可以符合要求的情况下不要使用排序操作;尽量不要包含多个列的排序,如果需要最好给这些列创建复合索引。
6,like语句操作
一般情况下不鼓励使用like操作,如果非使用不可,如何使用也是一个问题。like "%aaa%"不会使用索引而like "aaa%" 可以使用索引。
7,不要在列上进行运算
select * from users where YEAR(adddate)
8,不使用NOT IN操作
NOT IN操作都不会使用索引将进行全表扫描。NOT IN可以NOT EXISTS代替