MySQL数据库优化

时间:2021-09-24 07:10:28

ref: immoc学习笔记

[toc]

第1章、MySQL数据库优化简介

1、数据库优化的目的

  • a) 避免出现访问错误的
    • 1、 由于数据库连接timeout导致出现5XX错误
    • 2、由于慢查询导致页面无法正常加载
    • 3、由于阻塞造成数据无法提交
  • b) 增加数据库的稳定性
    很多数据库都是因为低效的查询引起的。
  • c) 优化用户体验
    • 1、流畅的页面访问速度
    • 2、良好的网站功能体验

2、可以考虑优化的方面

效果    ------------------------->
硬件、系统配置、数据库表结构、索引
<------------------------- 成本

第2章、SQL语句优化

如何发现有问题的SQL语句

* 1、慢查询
```
set global slow_query_log = on
show variables like 'slow_query_log'
```
* 2、设置全局慢查询日志文件
`set global slow_query_log_file = "/data/mysql/vincent-slow.log";`
* 3、
```
SET global log_queries_not_using_indexes=on;
SET global long_query_time=1;
```

慢查询日志分析工具

mysqldumpslow [官方]

mysqldumpslow -t 3 /data/mysql/vincent-slow.log 

Reading mysql slow query log from /data/mysql/vincent-slow.log
Count: 2 Time=0.02s (0s) Lock=0.00s (0s) Rows=64.0 (128), root[root]@localhost
select TABLE_NAME, COLUMN_NAME from information_schema.columns
where table_schema = 'S'
order by table_name,ordinal_position

Count: 1 Time=0.00s (0s) Lock=0.00s (0s) Rows=1.0 (1), root[root]@localhost
select count(*) from film

Count: 2 Time=0.00s (0s) Lock=0.00s (0s) Rows=0.0 (0), root[root]@localhost
SELECT ROUTINE_NAME FROM INFORMATION_SCHEMA.ROUTINES
WHERE ROUTINE_TYPE="S" AND ROUTINE_SCHEMA = "S"

pt-query-digest [非官方]

http://blog.csdn.net/seteor/article/details/24017913

 如何通过慢查询日志发现有问题的SQL?

  • 1、查询次数多且每次查询占用很长时间的SQL

通常为pt-query-digest分析的前几个查询

  • 2、IO大的SQL

注意pt-query-digest分析中的Rows examine项

  • 3、未命中索引的SQL

注意pt-query-digest分析中的Rows examine和Rows Send的对比

如何分析sql查询

  • 使用explain查询SQL的执行计划

explain返回各列的含义

  • table: 显示这一行的数据是关于那张表的

  • type: 这是非常重要的一列,显示连接使用了那种类型。从最好到最差的连接类型为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()函数的优化

explain select * from mysql.user;
+------+---------------+---------+--------+-----------------+--------+-----------+--------+--------+---------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
|------+---------------+---------+--------+-----------------+--------+-----------+--------+--------+---------|
| 1 | SIMPLE | user | ALL | <null> | <null> | <null> | <null> | 7 | |
+------+---------------+---------+--------+-----------------+--------+-----------+--------+--------+---------+
1 row in set
Time: 0.002s
mysql root@localhost:sakila> explain select max(payment_date) from payment \G
***************************[ 1. row ]***************************
id | 1
select_type | SIMPLE
table | payment
type | ALL
possible_keys | None
key | None
key_len | None
ref | None
rows | 13795
Extra |

1 row in set
Time: 0.002s

- 建立索引之后的结果
mysql root@localhost:sakila> CREATE INDEX idx_paydate ON payment(payment_date);
Query OK, 0 rows affected
Time: 0.238s
mysql root@localhost:sakila> explain select max(payment_date) from payment \G
***************************[ 1. row ]***************************
id | 1
select_type | SIMPLE
table | None
type | None
possible_keys | None
key | None
key_len | None
ref | None
rows | None
Extra | Select tables optimized away

1 row in set
Time: 0.001s
  • 关于count(*)和count(field)的区别
mysql root@localhost:sakila> CREATE TABLE t(id int);
Query OK, 0 rows affected
Time: 0.051s
mysql root@localhost:sakila> INSERT INTO t VALUES (1), (2), (NULL);
Query OK, 3 rows affected
Time: 0.009s
mysql root@localhost:sakila> SELECT * FROM t;
+--------+
| id |
|--------|
| 1 |
| 2 |
| <null> |
+--------+
3 rows in set
Time: 0.001s
mysql root@localhost:sakila> SELECT COUNT(*), COUNT(id) FROM t;
+------------+-------------+
| COUNT(*) | COUNT(id) |
|------------+-------------|
| 3 | 2 |
+------------+-------------+
1 row in set
Time: 0.001s

mysql root@localhost:sakila> SELECT COUNT(id=1 or id=NULL), COUNT(id=2 or id=NULL) FROM t;
+--------------------------+--------------------------+
| COUNT(id=1 or id=NULL) | COUNT(id=2 or id=NULL) |
|--------------------------+--------------------------|
| 1 | 1 |
+--------------------------+--------------------------+
1 row in set
Time: 0.002s

从上面可以得出结论,count()函数只会记录匹配到的行,而匹配到NULL则不会计数

子查询优化

通常情况下,需要把子查询优化为join查询,但是在优化时需要注意关键键是否有一对多的关系,需要注意重复数据

(查看sandra出演的所有影片)

mysql root@localhost:sakila> 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')) \G

***************************[ 1. row ]***************************
id | 1
select_type | PRIMARY
table | film
type | ALL
possible_keys | None
key | None
key_len | None
ref | None
rows | 1
Extra | Using where
***************************[ 2. row ]***************************
id | 2
select_type | DEPENDENT SUBQUERY
table | film_actor
type | index_subquery
possible_keys | idx_fk_film_id
key | idx_fk_film_id
key_len | 2
ref | func
rows | 2
Extra | Using index; Using where
***************************[ 3. row ]***************************
id | 3
select_type | DEPENDENT SUBQUERY
table | actor
type | unique_subquery
possible_keys | PRIMARY
key | PRIMARY
key_len | 2
ref | func
rows | 1
Extra | Using where

3 rows in set
Time: 0.002s

group by优化

通过下面的查询可以看到引用了临时表

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
type | ALL
possible_keys | PRIMARY
key | None
key_len | None
ref | None
rows | 200
Extra | Using temporary; Using filesort
***************************[ 2. row ]***************************
id | 1
select_type | SIMPLE
table | film_actor
type | ref
possible_keys | PRIMARY
key | PRIMARY
key_len | 2
ref | sakila.actor.actor_id
rows | 13
Extra | Using index

2 rows in set
Time: 0.042s

优化之后的查询语句

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 | <derived2>
type | ALL
possible_keys | None
key | None
key_len | None
ref | None
rows | 200
Extra |
***************************[ 2. row ]***************************
id | 1
select_type | PRIMARY
table | actor
type | eq_ref
possible_keys | PRIMARY
key | PRIMARY
key_len | 2
ref | c.actor_id
rows | 1
Extra |
***************************[ 3. row ]***************************
id | 2
select_type | DERIVED
table | film_actor
type | index
possible_keys | None
key | PRIMARY
key_len | 4
ref | None
rows | 4748
Extra | Using index

3 rows in set
Time: 0.004s

limit查询的优化

limit通常用于分布处理,时常会伴随order by从句使用,因此大多时候会使用filesort,这样会造成大量的IO问题

explain SELECT film_id, description FROM sakila.film ORDER BY title LIMIT 50, 5\G
***************************[ 1. row ]***************************
id | 1
select_type | SIMPLE
table | film
type | ALL
possible_keys | None
key | None
key_len | None
ref | None
rows | 1
Extra | Using filesort

1 row in set
Time: 0.001s

> SELECT film_id, description FROM sakila.film ORDER BY title LIMIT 50, 5;
+-----------+---------------+
| film_id | description |
|-----------+---------------|
+-----------+---------------+
0 rows in set
Time: 0.039s

改写后的SQL

explain SELECT film_id, description FROM sakila.film ORDER BY film_id LIMIT 50\G
***************************[ 1. row ]***************************
id | 1
select_type | SIMPLE
table | film
type | index
possible_keys | None
key | PRIMARY
key_len | 2
ref | None
rows | 1
Extra |

1 row in set

第3章 索引优化

3.1、如何选择合适的列建立索引

  • 1、在where从句,group by从句,on 从句中出现的列
  • 2、索引字段越小越好
  • 3、离散度大的列放到联合索引的前面
select * from payment where staff_id = 2 and customer_id = 584;
  • 4、是index(staff_id, customer_id)好还是index(customer_id, staff_id)好?
    • 由于customer_id离散度更大,所以使用后者

3.2、索引优化SQL的方法

  • 重复及冗余的索引

重复索引是指相同的列以相同顺序建立的同类型的索引,如下表中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;

推荐使用工具:pt-duplicate-key-checker

# 使用方法
pt-duplicate-key-checker -uroot -p 'passwd' -p3306 -hlocalhost

3.3、索引维护的方法

  • 删除不用的索引

目前MySQL官方还没有记录索引使用情况的功能,但是在PerconMySQL和MariaDB中可以通过INDEX_STATISTICS表查看那些索引没有被使用,但是在MySQL中目前只能通过慢查询日志配合pt-index-usage工具来进行索引使用情况的分析

pt-index-usage -uroot -p'passwd' mysql-slow.log

第4章 数据库结构优化

4.1、选择合适的数据类型

数据类型的选择重点在于合适二字,如何确定选择的数据类型是否合适?

  • 1、使用可以存下你的数据的最小的数据类型
  • 2、使用简单的数据类型。int要比varchar类型处理更加简单
  • 3、尽可能的使用not null定义字段
  • 4、尽量少使用text类型,非用不可时最好考虑分表

示例,使用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('2018006-26 13:14:25'));

SELECT FROM_UNIXTIME(timestr) FROM test;

4.2、数据库表的范式优化

  • 表的范式化
    • 范式化是指数据库设计的规范,目前说到范式化一般就是指第三设计模式,要求数据表中不存在非关键字段对任意候选关键字段的传递函数依赖规则符合第三范式
    商品名称 价格 重量 有效期 分类 分类描述
    可乐 3.00 250ml 2014.6 饮料 碳酸饮料
    北冰洋 3.00 250ml 2014.6 饮料 碳酸饮料
    • 存在以下传递函数依赖关系:
      • 商品名称 -> 分类 -> 分类描述
      • 也就是说存在非关键字段“分类描述”对关键字段“商品名称”的传递函数依赖

4.3、数据库表的反范式优化

反范式化是指为了查询效率的考虑把原本符合第三范式的表适当的增加冗余,以达到优化查询效率的目的,反范式化是一种以空间换时间的操作

4.4、数据库表的垂直拆分

  • 所谓垂直拆分,就是把原来一个有很多列的表拆分成多个表,这解决了表的宽度问题。

  • 通常垂直拆分可以按以下几个原则进行:
    • 1、把不常用的字段单独放到一个表中
    • 2、把大字段独立存入到一个表中
    • 3、把经常一起使用的字段放到一起

4.5、数据库表的水平拆分

第5章 系统配置优化

  • 表的水平拆分是为了解决数据量过大的问题,水平拆分的表每一个表的结构都是完全一致的,下面以payment表为例
Create Table CREATE TABLE `payment` (
`payment_id` smallint(5) unsigned NOT NULL AUTO_INCREMENT,
`customer_id` smallint(5) unsigned NOT NULL,
`staff_id` tinyint(3) unsigned NOT NULL,
`rental_id` int(11) DEFAULT NULL,
`amount` decimal(5,2) NOT NULL,
`payment_date` datetime NOT NULL,
`last_update` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`payment_id`),
KEY `idx_fk_staff_id` (`staff_id`),
KEY `idx_fk_customer_id` (`customer_id`),
KEY `fk_payment_rental` (`rental_id`),
KEY `idx_paydate` (`payment_date`),
CONSTRAINT `fk_payment_customer` FOREIGN KEY (`customer_id`) REFERENCES `customer` (`customer_id`) ON UPDATE CASCADE,
CONSTRAINT `fk_payment_rental` FOREIGN KEY (`rental_id`) REFERENCES `rental` (`rental_id`) ON DELETE SET NULL ON UPDATE CASCADE,
CONSTRAINT `fk_payment_staff` FOREIGN KEY (`staff_id`) REFERENCES `staff` (`staff_id`) ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=16050 DEFAULT CHARSET=utf8
  • 常用的水平拆分方法
    • 1、对customer_id进行hash运算,如果要拆分成5个表则使用mod( customer_id, 5 )取0-4个值
    • 2、针对不同的hashID把数据存到不同的表中
    • 挑战
      • 1、跨分区表进行数据查询
      • 2、统计及后台报表操作

5.1、数据库系统配置优化

操作系统优化

数据库是基于操作系统的,目前大多数MySQL都是安装在linux上的,所以操作系统的配置势必会影响到MySQL的性能的,下面是一些常用的系统配置

  • 网络方面

修改/etc/sysctl.conf文件

# 增加tcp支持的队列数
net.ipv4.tcp_max_syn_backlog = 65535

# 减少断开连接时,资源回收
net.ipv4.tcp_max_tw_backets = 8000
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_tw_recycle = 1
net.ipv4.tcp_fin_timeout = 10
  • 打开文件数限制

编辑/etc/security/limits.conf,可以使用ulimit -a查看,增加以下内容

* soft nofile 65535
* hard nofile 65535

如果条件允许,则可以关闭selinux和iptables

5.2、MySQL配置文件优化

MySQL可以通过启动时自定义参数和配置文件两种方式,在linux中配置文件位于/etc/my.cnf或者是/etc/mysql/my.cnf

可以通过下面的命令查找配置文件的加载顺序

注意:如果多个位置存在 配置文件,则后面的会覆盖前面的

/opt/mysql/bin/mysqld --verbose --help | grep -A 1 'Default options'

Default options are read from the following files in the given order:
/etc/my.cnf /etc/mysql/my.cnf /usr/local/mysql/etc/my.cnf ~/.my.cnf

MySQL配置文件--常用参数说明

  • innodb_buffer_pool_size

非常重要的一个参数,用于配置Innodb的缓冲池,如果数据库中只有Innodb表,则推荐配置为总内存的75%

下面的结果是在我的虚拟机得到的,虚拟机总内存1G,下面得到的结果单位也是Gb

mysql root@localhost:sakila> 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;
+----------+------------+
| ENGINE | Total MB |
|----------+------------|
| <null> | <null> |
| CSV | 0.0 |
| InnoDB | 6.4 |
| MyISAM | 0.7 |
+----------+------------+
4 rows in set
Time: 0.011s

innodb_buffer_pool_size >= Total MB
  • innode_log_buffer_size

innodb log缓冲大小,由于日志最长每秒钟就会刷新,所以一般不需要太大

  • innodb_flush_log_at_trx_commit

关键参数,对innodb的IO效率影响较大,默认值为1,可以取0,1,2这3个参数,一般建议设置为2,但如果对数据的安全性要求比较高,则可以使用默认的1

  • innodb_file_per_table

关键参数,控制innodb每一个表使用独立的表空间,默认为off,也就是默认所以表都会建立在共享的表空间中

  • innodb_stats_on_metadata

决定了MySQL会在什么情况下刷新innodb表的统计信息,不建议打开

5.3、第三方配置工具使用

在想着网址提交信息后,可给出建议的配置

https://tools.percona.com/wizard

第6章 服务器硬件优化

6.1、服务器硬件优化

如何选择CPU

思考: 是选择单核更快的CPU还是选择多核的CPU

  • 1、mysql有一些工作只能使用到单核CPU
  • 2、MySQL对CPU多核的支持并不是核数越多就越快

MySQL5.5使用的服务器核数不要超过32核

Disk IO优化

  • 常用RAID级别简介
    • RAID0: 也称为条带,就是把多个磁盘链接为一个磁盘使用,这个级别IO最好
    • RAID1: 也称为镜像,要求至少两个硬盘,第个磁盘的数据都是一样的
    • RAID5: 也是把多个磁盘当作一个磁盘使用,至少3块硬盘,数据读写时会建立奇偶校验信息,并且奇偶检验信息和相对应的数据分别存储于不同的磁盘上,当RAID5的一个磁盘数据发生损坏时,利用剩下的数据和相应的奇偶检验信息去恢复被损坏的数据是完全没有问题的
    • 推荐使用RAID1+0:就是RAID1和RAID0的结合,同时具备两个级别的优缺点。一般建议数据库使用这个级别