数据库优化的目的
- 避免出现页面访问错误
- 由于数据库连接timeout产生页面5xx错误
- 由于慢查询造成页面无法加载
- 由于阻塞造成数据无法提交
- 增加数据库的稳定性
- 很多数据库问题都是由于低效的查询引起的
- 优化用户体验
- 流畅页面的访问速度
- 良好的网站功能
- SQL及索引优化,占比最大且位于基础,表明对于优化来说SQL及索引优化是最重要的;根据需求写出结构良好的SQL,根据这个SQL在表中建立有效的索引,其中索引要适量、有效。
- 根据数据库范式来进行数据库的设计,设计出简洁明了的数据库表结构,尽量的减少坠余,对我们的查询进行有益的帮助;
- 系统本身的限制:TCP/IP连接数的限制、打开文件数的限制、安全性限制,所以需要对系统配置进行优化;
- 选择更适合数据库的CPU、更快的IO、更多的内存,数据库数据装到内存中,所以内存越大对性能越好。CPU越多并不见得会对我们的数据库造成更好的影响,MySQL会对CPU的核数有限制,它并不会拥有太多核数,有些查询也只能用到单核;IO设备对数据库性能有良好影响,它不能减少数据库的锁机制,锁是数据库内部保证数据完整性的机制。
数据准备
演示数据说明:
- 使用MySQL提供的sakila数据库,可以通过以下URL获取这个演示数据库
https://dev.mysql.com/doc/index-other.html - sakila数据库的表结构信息可以通过以下网站查看
https://dev.mysql.com/doc/sakila/en/sakila-installation.html
SELECT @@version; #查看版本号
@@version
5.6.39
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
如何发现有问题的SQL
- 使用MySQL慢查询日志对有效率问题的SQL进行监控
- show variables like 'slow_query_log' ; // mysql是否开启慢查询日志
- set global show_query_log_file = '/home/mysql/sql_log/mysql-show.log' ; // 指定慢查询日志存储的位置
- set global log_queries_not_using_indexes=on ; // 指定是否要把没有使用索引的SQL记录到慢查询日志中
- set global long_query_time = 1 ; // 查过多少秒之后的查询记录到慢查询日志中,这里是指把大于1秒的记录
show variables like 'slow_query_log' ;
Variable_name | Value |
---|---|
slow_query_log | OFF |
show variables like '%log%' ; # 查看慢查询日志变量的设置
Variable_name | Value |
---|---|
back_log | 80 |
binlog_cache_size | 32768 |
binlog_checksum | CRC32 |
binlog_direct_non_transactional_updates | OFF |
binlog_error_action | IGNORE_ERROR |
binlog_format | STATEMENT |
binlog_gtid_simple_recovery | OFF |
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 |
binlogging_impossible_mode | IGNORE_ERROR |
expire_logs_days | 0 |
general_log | OFF |
general_log_file | D:\Developement_Tools\mysql-5.6.39-winx64\data\SKY-20180328LTX.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 | 8388608 |
innodb_log_compressed_pages | ON |
innodb_log_file_size | 50331648 |
innodb_log_files_in_group | 2 |
innodb_log_group_home_dir | .\ |
innodb_mirrored_log_groups | 1 |
innodb_online_alter_log_max_size | 134217728 |
innodb_undo_logs | 128 |
log_bin | OFF |
log_bin_basename | |
log_bin_index | |
log_bin_trust_function_creators | OFF |
log_bin_use_v1_row_events | OFF |
log_error | D:\Developement_Tools\mysql-5.6.39-winx64\data\SKY-20180328LTX.err |
log_output | FILE |
log_queries_not_using_indexes | OFF |
log_slave_updates | OFF |
log_slow_admin_statements | OFF |
log_slow_slave_statements | OFF |
log_throttle_queries_not_using_indexes | 0 |
log_warnings | 1 |
max_binlog_cache_size | 18446744073709500000 |
max_binlog_size | 1073741824 |
max_binlog_stmt_cache_size | 18446744073709500000 |
max_relay_log_size | 0 |
relay_log | |
relay_log_basename | |
relay_log_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 |
simplified_binlog_gtid_recovery | OFF |
slow_query_log | OFF |
slow_query_log_file | D:\Developement_Tools\mysql-5.6.39-winx64\data\SKY-20180328LTX-slow.log |
sql_log_bin | ON |
sql_log_off | OFF |
sync_binlog | 0 |
sync_relay_log | 10000 |
sync_relay_log_info | 10000 |
set global log_queries_not_using_indexes=on ;
show variables like 'long_query_time' ; # 查看查询时间上限设置
set global slow_query_log = on ; # 开启慢查日志
show variables like 'slow%' ; # 查看MySQL慢查询日志的位置
慢查询的存储格式
# Time: 180505 8:57:43
# User@Host: root[root] @ localhost [::1] Id: 2
# Query_time: 0.001071 Lock_time: 0.000234 Rows_sent: 15 Rows_examined: 278
SET timestamp=1525481863;
show tables;
慢查日志所包含的内容
- 执行SQL的主机信息
- User@Host: root[root] @ localhost [::1] Id: 2
- SQL的执行信息
- Query_time: 0.001071 Lock_time: 0.000234 Rows_sent: 15 Rows_examined: 278
- SQL执行时间
- SET timestamp=1525481863;
- SQL的内容
- SELECT CONCAT('storage engine:', @@storage_engine) as INFO ;
MySQL慢查日志分析工具
mysqldumpslow工具
- Usage: mysqldumpslow [OPTS...][LOGS...]
Parse and summarize the MySQL slow query log. Options are
--verbose verbose
--debug debug
--help write this text to standard output
mysqldumpslow -h
mysqldumpslow -s 通过什么方式排序慢查日志
mysqldumpslow -t 3 D:\Developement_Tools\mysql-5.6.39-winx64\data\SKY-20180328LTX-slow.log | more 滚动图分析前三条比较慢的日志
pt-query-digest工具
- 输出到文件
- pt-query-digest slow-log > slow_log.report
- 输出到数据库表
- pt-query-digest slow.log -review
h=127.0.0.1,D=test,p=root,P=3306,u=root,t=query_review
--create-reviewtable
--review-history t= hostname_show
- pt-query-digest slow.log -review
pt-query-digest -h
pt-query-digest --limit 限定分析百分之多少的sql
pt-query-digest D:\Developement_Tools\mysql-5.6.39-winx64\data\SKY-20180328LTX-slow.log | more
分析结果分为三部分
-
头部:慢查日志中包含多少SQL、抽象成多少SQL、慢查日志的时间范围
-
pt-query-digest输出
如果通过慢查日志发现有问题的SQL
- 查询次数多且每次查询占用时间长的SQL
- 通常pt-query-digest分析的前几个查询
- IO大的SQL
- 注意pt-query-digest分析中的Rows examine项
- 未命中索引的SQL
- 注意pt-query-digest分析中Rows examine和Rows Send的对比
如何分析SQL查询
- 使用explain查询SQL的执行计划
id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
---|---|---|---|---|---|---|---|---|---|
1 | SIMPLE | customer | ALL | NULL | NULL | NULL | NULL | 599 | NULL |
-
explain返回各列的含义
- table:显示这一行的数据是关于哪张表的
- type:这是重要的列,显示连接使用了何种类型。从最好到最差的连接类型为const、eq_reg、ref、range、index和ALL
- const:说明是长度查找,一般是唯一主键或索引
- eq_reg:范围查找,一般是唯一索引或主键范围查找
- ref:常见于连接的查询中,比如一段表是基于某段索引的查找
- range:基于索引的范围查找
- index:对于索引的扫描
- ALL:表扫描
- possible_keys:显示可能应用在这张表中的索引。如果为空,没有可能的索引。
- key:实际使用的索引。如果为NULL,则没有使用索引。
- key_len:使用的索引长度。在不损失精确性的情况下,长度越短越好
- ref:显示索引的哪一列被使用了,如果可能的话,是一个常数
- rows:MySQL认为必须检查的用来返回请求数据的行数
-
extra列需要注意的返回值
- Using filesort:看到这个的时候,查询就需要优化了。MYSQL需要进行额外的步骤来发现如何对返回的行排序。它根据连接类型以及存储排序键值和匹配条件的全部行的行指针来排序全部行
- Using temporary:看到这个的时候,查询需要优化了。这里,MYSQL徐哟创建一个临时表来存储接口,这通常发生在对不同的列表进行ORDER BY上,而不是GROUP BY上
Count()和Max()的优化方法
- 查询最后支付时间——优化max()函数
EXPLAIN select max(payment_date) from payment
id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
---|---|---|---|---|---|---|---|---|---|
1 | SIMPLE | payment | ALL | NULL | NULL | NULL | NULL | 16086 | NULL |
create index idx_paydate on payment(payment_date) ; # 优化,在payment_data添加索引
id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
---|---|---|---|---|---|---|---|---|---|
1 | SIMPLE | NULL | NULL | NULL | NULL | NULL | NULL | NULL | Select tables optimized away |
-
Select tables optimized away:表示它并不需要实际查询表的数据,只需要通过表的索引就可以完全知道SQL的执行结果。索引是顺序的排列的,通过索引的统计信息非常清楚的知道payment_date的数值是什么样子,并不需要表的一些操作,大大优化这条SQL执行的效率,减少了IO操作。这种索引称为覆盖索引
-
在一条SQL中同时查出2006年和2007年电影的数量——优化count()函数
- 错误的方式:
- SELECT COUNT(release_year='2006' OR release_year='2007') FROM film ;
- 无法分开计算2006和2007年的电影数量
- SELECT COUNT(*) FROM film WHERE release_year='2006' AND release_year='2007' ;
- release_year不可能同时为2006和2007,因此上有逻辑错误
- 正确的方式
- select count(release_year = '2006' or null) as '2006年电影数量', count(release_year='2007' or null) as '2007年电影数量' FROM film ;
- 错误的方式:
select count(*), count(id) from t ; count(id)不包含空值
-
子查询优化
- 通常情况下,需要把子查询优化为join查询,但在优化时要注意关联键是否有一对多的关系,要注意重复数据
- (查询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'))
- 通常情况下,需要把子查询优化为join查询,但在优化时要注意关联键是否有一对多的关系,要注意重复数据
-
优化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
Using temporary 使用临时表 Using filesort 使用文件排序
-
优化group by 查询——优化后
- 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)
-
优化Limit查询
- limit 常用于分页处理,时常会伴随order by从句使用,因此大多时候会使用Filesorts这样会造成大量的IO问题。
- SELECT film_id,description FROM sakila.film ORDER BY title LIMIT 50,5 ;
- 优化步骤1:使用有索引的列或主键进行Order by操作
- SELECT film_id,description FROM sakila.film ORDER BY film_id LIMIT 50,5 ;
- 优化步骤2:记录上次返回的主键,在下次查询时使用主键过滤
- SELECT film_id,description FROM sakila.film ORDER BY film_id > 55 and film_id <= 60 ORDER BY film_id LIMIT 1,5 ;
- 避免了数据量大时扫描过多的记录
如何选择合适的列建立索引?
+ 1.在where,group by,order by,on从句中出现的列
+ 2.索引字段越小越好(因为数据库的存储单位是页,一页中能存下的数据越多越好 )
+ 3.离散度大得列放在联合索引前面(唯一值越多离散度越高)
+ select * from payment where staff_id=2 and customer_id=584;
是index(sftaff_id,customer_id) 好?还是index(customer_id,staff_id)好?
由于customer_id的离散度更大,所以应该使用index(customer_id,staff_id)
索引的维护及优化——重复及冗余索引
-
重复索引是指相同的列以相同的顺序建立的同类型的索引,如下表中primary key和ID列上的索引就是重复索引
- create table test(id int not null primay 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 primay key,name varchar(10) not null,title varchar(50) not null, key(name,id) ) engine=innodb ;
查找重复索引
use information_schema ;
select a.table_schema as '数据名',a.table_name as '表名',a.index_name as '索引1',b.index_name as '索引2' ,a.column_name as '重复列名' from statistics a join statistics b on a.table_schema =b.table_schema and a.table_name = b.table_name and a.seq_in_index = b.seq_in_index and a.column_name = b.column_name where a.seq_in_index = 1 and a.index_name<>b.index_name ;
show create table employees.dept_emp ; 显示表结构
- 使用pt-duplicate-key-checker工具检查重复及冗余索引
用法:
pt-duplicate-key-checker
-uroot
-p '123456'
-h 127.0.0.1
索引的维护及优化——删除不用索引
- 目前MySQL中还没有记录索引的使用情况,但是在PerconMySQL和MariaDB中可以通过INDEX_STATISTICS表来查看那些索引未使用,但在MySQL中目前只能通过慢日志配合pt-index-usage工具来进行索引使用情况的分析。
pt-index-usage
- uroot -p '123456'
mysql-slow.log
选择合适的数据类型
-
数据类型的选择,重点在于合适二字,如何确定选择的数据类型是否合适?
- 1、使用可以存下你的数据的最小的数据类型。
- 2、使用简单的数据类型。int 要比 varchar 类型在MySQL处理上简单,所以 MySQL 的处理效率会更高。
- 3、尽可能使用 not null 定义字段。MySQL 中,null 需要额外的字段存储,增加了 IO 、存储的开销。
- 4、尽量少用 text、blob 类型,非用不可时,最好考虑分表。
-
使用int来存储日期时间,利用FROM_UNIXTIME(),UNIX_TIMESTAMP()两个函数来进行转换
- CREATE TABLE test(id int AUTO_INCREMENT NOT NULL, timestr INT, PRIMARY KEY(id)) ;
- INSERT INTO test(timestr) VALUES(UNIX_TIMESTAMP('2014-06-01 13:12:00')) ;
- SELECT FROM_UNIXTIME(timestr) FROM test ;
-
使用bigint来IP地址,利用INET_ATON(), INET_NTOA()两个函数来进行转换
- CREATE TABLE sessions(id INT AUTO_INCREMENT NOT NULL, ipaddress BIGINT, PRIMARY KEY(id)) ;
- INSERT INTO sessions(ipaddress) VALUES (INET_ATON('192.168.0.1')) ;
- SELECT INET_NIOA(ipaddress) FROM sessions ;
表的范式化和反范式化
- 范式化是指数据库设计的规范,目前说到范式化一般是指第三设计范式,也就是要求数据表中不存在非关键字段对任意候选关键字的传递函数依赖则符合第三范式
商品名称 | 价格 | 重量 | 有效期 | 分类 | 分类描述 |
---|---|---|---|---|---|
可乐 | 3.00 | 250ml | 2014.6 | 饮料 | 碳酸饮料 |
北冰洋 | 3.00 | 250ml | 2014.7 | 饮料 | 碳酸饮料 |
存在以下传递函数依赖关系:
(商品名称)--> (分类)--> (分类描述)
也就是说存在非关键字段"分类描述" 对关键字段"商品名称"的传递函数依赖。
不符合第三范式要求的表存在以下问题:
1.数据冗余:(分类、分类描述)对于每一个商品都会进行记录
2.数据插入异常
3.数据更新异常
4.数据删除异常
- 反范式化:为了查询效率的考虑,把原本符合第三范式的表适当增加冗余,以达到优化查询效率的目的。以空间换取时间的操作。
如何查询订单信息?
SELECT b.用户名,b.电话,b.地址,a.订单ID,SUM(c.商品单价*c.商品数量) as 订单价格 FORM '订单表' a JOIN '用户表' b ON a.用户ID = b.用户ID JOIN '订单商品表' c ON c.订单ID = b.订单ID GRUOP BY b.用户名,b.电话,b.地址,a.订单ID
041.pngSELECT a.用户名,a.电话,a.地址,a.订单ID,a.订单价格 FROM '订单表' a
表的垂直拆分
所谓垂直拆分,就是把原来一个有很多列的表拆分成多个表解决表的宽度问题,通常垂直拆分可以按以下原则进行:
1、把不常用的字段单独存放到一个表中;
2、把大字段独立存放到一个表中;
3、把经常一起使用的字段放到一起
表的水平拆分
表的水平拆分是为了解决单表数据量过大的问题,水平拆分的表每一个表的结构都是完全一致的。
常用的水平拆分方法:
1.对customer_id进行hash运算,如果是要拆分成5个表的话,则使用mod(customer_id,5)
2.针对不同的hashID把数据存到不同的表中
系统配置优化
操作系统配置优化:
- 数据库是基于操作系统的,目前大多数MySQL都是安装在Linux系统之上,所以对于操作系统的一些参数配置也会影响到MySQL的性能,下面就列出一些常用到的系统配置:
- 网络方面,要修改/etc/sysctl.conf文件,增加tcp支持的队列数,减少断开连接时,资源的回收。
- 打开文件数的限制。修改/etc/security/limits.conf文件,增加一下内容以修改打开文件数量的限制。
关闭iptables,selinux等防火墙软件。
- 网络方面,要修改/etc/sysctl.conf文件
- 增加tcp支持的队列数目
net.ipv4.tcp_max_syn_backlog=65535 - 减少断开连接的数目,及时回收资源
net.ipv4.tcp_max_tw_buckets = 8000
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_tw_recycle =1 - 打开文件数的限制,可以使用ulimit -a查看目录的各位限制,可以修改/etc/security/limits.conf文件,增加以下内容以修改打开文件数量的限制。
- soft nofile 65535
- hard nofile 65535
除此之外最好在MySQL服务器上关闭iptables,selinux等防火墙软件。
MySQL配置文件
- MySQL可以通过启动时指定配置参数和使用配置文件两种方法进行配置,在大多数情况下配置文件位于 /etc/my.cnf 或是 /etc/mysql/my.cnf 在windows系统配置文件可以是位于c:/windows/my.ini 文件,MySQL查找配置文件的顺序可以通过以下方法获得
$ /usr/sbin/mysqld --verbose --help | grep -A 1 'Default options'
注意:如果存在多个位置存在配置文件,则后面的会覆盖前面的
- innodb_buffer_pool_size:非常重要的一个参数,用于配置Innodb的缓冲池如果数据库中只有Innodb表,则推荐配置量为总内存的75%。
SELECT ENGINE,ROUND(SUM(data_length + index_length)/1024/1024,1) AS "Total MB", FROM INFORMATION_SCHEMA.TABLES WHERE table_schema not in ("information_schema","performance_schema") GROUP BY ENGINE ;
Innodb_buffer_pool_size >= Total MB
-
innodb_buffer_pool_instances:MySQL5.5中新增加参数,可以控制缓冲池的个数,默认情况下只有一个缓冲池。
-
innodb_log_buffer_size:innodb log 缓冲的大小,由于日志最长每秒钟就会刷新所以一般不用太大。
-
innodb-flush-log-at-trx-commit:关键参数,对innodb的IO效率影响很大。默认值为1,可以取0,1,2三个值,一般建议设为2,但是如果数据安全性要求比较高则使用默认值1.
-
innodb_read_io_threads/innodb_write_io_threads:以上两个参数决定了Innodb读写的IO进程数,默认为4.
-
innodb_file_per_table:关键参数,控制Innodb每一个表使用独立的表空间,默认为OFF,也就是所有表都会建立在共享表空间中。
-
innodb_stats_on_metadata:决定了MySQL在什么情况下会刷新innodb表的统计信息。
第三方配置工具
- Percon Configuration Wizard
https://tools.percona.com/wizard
服务器硬件优化
1、MySQL有一些工作只能使用到单核CPU:Replicate、SQL ......
2、MySQL对CPU核数的支持并不是越多越快。MySQL5.5使用的服务器不要超过32核。
- Disk IO优化
常用RAID级别简介
- RAID0:也称为条带,就是把多个磁盘链接成一个硬盘使用,这个级别IO最好
- RAID1:也称为镜像,要求至少有两个磁盘,每组磁盘存储的数据相同
- RAID5:也是把多个(最少3个)硬盘合并成1个逻辑使用,数据读写时会建立奇偶校验信息,并且奇偶校验信息和相对应的数据分别存储于不同的磁盘上。当RAID5的一个磁盘数据发生损坏后,利用剩下的数据和相应的奇偶校验信息去恢复被损坏的数据
- RAID1+0:就是RAID1和RAID0的结合。同时具备两个级别的优缺点。一般建议数据库使用这个级别。
SNA和NAT是否适合数据库
1、常用于高可用解决方案
2、顺序读写效率很高,但是随机读写不如人意
3、数据库随机读写比率很高
网友评论