luisedware / Archives

My Archives
45 stars 11 forks source link

LNMP 性能优化之 MySQL 性能优化 #8

Open luisedware opened 7 years ago

luisedware commented 7 years ago

目录

数据准备

打开链接下载数据

http://downloads.mysql.com/docs/sakila-db.zip

打开终端,执行以下命令

# 登录 MySQL Cli 模式
mysql -uroot -p

# 创建数据库
SOURCE /Users/LuisEdware/Downloads/sakila-db/sakila-schema.sql

# 填充数据到数据库
SOURCE /Users/LuisEdware/Downloads/sakila-db/sakila-data.sql

# 使用 sakila 数据库
USE sakila;

数据库结构优化

选择合适的数据类型

数据库表的范式化优化与反范式化优化

为了避免冗余数据,可以根据数据库表设计第三范式对数据的储存进行优化设计;也可以为了提高性能,采用数据库表设计反范式化设计去冗余数据,使得数据库的查询更加的快速。

数据库表垂直拆分

垂直拆分,就是把原来一个有很多列的表拆分成多个表,这解决了表的宽度问题。通常垂直拆分就可以按以下原则进行:

数据库表垂直拆分实践

TODO

数据库表水平拆分

当单表的数据量过大,导致增删查改等操作过慢,这时候需要对表进行水平拆分。水平拆分的表,每一张表的结构都是完全一致的。

常用的水平拆分方法为:

  1. 对 customer_id 进行 hash 运算,如果要拆分成 5 个表则使用 mod(customer_id,5) 取出 0-4 个值
  2. 针对不同的 hashID 把数据存到不同的表中

挑战:

数据库表水平拆分实践

TODO

MySQL 慢查询日志

慢查询日志配置

如何发现有问题的 SQL?答案是使用 MySQL 慢查询日志对有效率问题的 SQL 进行监控,执行命令如下:

# 查看是否开启慢查询日志
show variables like "slow_query_log";

# 查看是否设置了把没有索引的记录到慢查询日志
show variables like "log_queries_not_using_indexes";

# 查看是否设置慢查询的 SQL 执行时间
show variables like "long_query_time";

# 查看慢查询日志记录位置
show variables like "slow_query_log_file";

# 开启慢查询日志
set global slow_query_log=on

# 设置没有索引的记录到慢查询日志
set global log_queries_not_using_indexes=on

# 设置到慢查询日志的 SQL 执行时间(1 代表 1 秒)
set global long_query_time=1

# 查看慢查询日志(在 Linux 终端下执行)
tail -50 /usr/local/var/mysql/luyiyuandeMacBook-Pro-slow.log;

慢查询日志所存内容

慢查询日志分析工具

慢查询日志判断问题 SQL 的依据

EXPLAIN 分析 SQL 的执行计划

使用 EXPLAIN 分析 SQL

EXPLAIN SELECT * FROM staff;

使用 EXPLAIN 分析 SQL 后各个参数含义如下:

执行 EXPLAIN 分析 SQL 返回结果如下:

mysql> EXPLAIN select * from z_order left join z_league on z_order.league_id = z_league.id limit 10000\G;
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: z_order
   partitions: NULL
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 740339
     filtered: 100.00
        Extra: NULL
*************************** 2. row ***************************
           id: 1
  select_type: SIMPLE
        table: z_league
   partitions: NULL
         type: eq_ref
possible_keys: PRIMARY
          key: PRIMARY
      key_len: 4
          ref: bingoshuiguo.z_order.league_id
         rows: 1
     filtered: 100.00
        Extra: Using where
2 rows in set, 1 warning (0.00 sec)

SQL 优化

MAX() 优化

分析 SQL 语句:使用 MAX() 方法查询最后一笔交易的时间

EXPLAIN SELECT MAX(payment_date) FROM payment_date

执行结果如下:

*************************** 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)

如果数据表的数据非常大,查询频率又非常高,那么服务器的 IO 消耗也会非常高,所以这条 SQL 语句需要优化。可以通过建立索引进行优化。执行代码如下:

CREATE INDEX idx_paydate ON payment(payment_date);

然后再分析 SQL 语句,执行结果如下:

*************************** 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.01 sec)

经过优化之后,由于索引是按顺序排列的,MySQL 不需要查询表中的数据,而是通过查询索引最后的一个数据,就可以得知执行结果了。而且这个时候,不管表的数据量多大,查询 MAX() 所需要的时间都是基本固定的,这样就尽可能地减少了 IO 操作。

COUNT() 优化

分析 SQL 语句:使用 COUNT() 函数在一条 SQL 中同时查出 2006 年和 2007 年电影的数量

SELECT
  count(release_year = '2006' OR NULL) AS '2006 年电影数量' ,
  count(release_year = '2007' OR NULL) AS '2007 年电影数量'
FROM
  film;

count(*) 包含空值,count(id) 不包含空值。上述语句就是优化 Count() 函数取值

子查询优化

分析 SQL 语句:查询 sandra 出演的所有影片

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 优化

group by 可能会出现临时表、文件排序等,影响效率。可以通过关联的子查询,来避免产生临时表和文件排序,可以节省 IO。

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;

执行结果如下:

*************************** 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.01 sec)

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);

执行结果如下:

*************************** 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)

LIMIT 优化

LIMIT 常用于分页处理,时常会伴随 ORDER BY 从句使用,因此大多时候会使用 Filesorts ,这样会造成大量的 IO 问题

优化步骤1:使用有索引的列或主键进行 Order By 操作 优化步骤2:记录上次返回的主键,在下次查询时使用主键过滤(保证主键是自增且有索引)

索引优化

为合适的列建立索引

例如:

SELECT * FROM payment WHERE staff_id = 2 AND customer_id = 584;

上述 SQL 语句,是 index(staff_id,customer_id) 合理,还是 index(customer_id,staff_id) 合理。执行语句如下:

SELECT count(DISTINCT customer_id) , count(DISTINCT staff_id) FROM payment;

count(DISTINCT customer_id):599
count(DISTINCT staff_id):2

由于 customer_id 的离散度更大,所以应该使用 index(customer_id,staff_id)

找到重复和冗余的索引

之所以要找到重复和冗余的索引,是因为过多的索引不但影响写入,而且影响查询,索引越多,分析越慢。那么为何重复索引、冗余索引?概念如下:

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

UNIQUE(ID)PRIMARY KEY 重复了。 冗余索引是指多个索引的前缀列相同,或是在联合索引中包含了主键的索引,例子如下:

CREATE TABLE test(
    id INT NOT NULL PRIMARY KEY ,
    NAME VARCHAR(10) NOT NULL ,
    title VARCHAR(50) NOT NULL ,
    KEY(NAME , id)
) ENGINE = INNODB;

查找重复及冗余索引的 SQL 语句如下:

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

也可以使用工具 pt-duplicate-key-checker 检查重复索引和冗余索引,使用例如:

pt-duplicate-key-checker -uroot -p '123456' -h 127.0.0.1 -d sakila

执行结果如下:

# ########################################################################
# Summary of indexes
# ########################################################################

# Size Duplicate Indexes   118425374
# Total Duplicate Indexes  24
# Total Indexes            1439

删除不用的索引

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

pt-index-usage -uroot -p '123456' /usr/local/var/mysql/luyiyuandeMacBook-Pro-slow.log;

系统配置优化

数据库系统配置优化

数据库是基于操作系统的,目前大多数 MySQL 都是安装在Linux 系统之上,所以对于操作系统的一些参数配置也会影响到 MySQL 的性能,下面列举一些常用到的系统配置。

网络方面的配置,要修改文件 /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
net.ipv4.tcp_fin_timeout = 10

打开文件数的限制,可以使用 ulimit -a 查看目录的各项限制,可以修改文件 /etc/security/limits.conf ,增加以下内容以修改打开文件数量的限制

soft nofile 65535
hard nofile 65535

除此之外最好在 MySQL 服务器上关闭 iptables,selinux 等防火墙软件。

MySQL 配置文件

MySQL 可以通过启动时指定配置参数和使用配置文件两种方法进行配置,在一般情况下,配置文件位于 /etc/my.cnf 或是 /etc/mysql/my.cnf,MySQL 查询配置文件的顺序是可以通过以下方法过的

常用参数说明

存储引擎及版本的选择

请选择 MySQL InnoDB 存储引擎及 MySQL 5.5+ 的版本,原因如下:

第三方配置工具使用

percona:https://tools.percona.com/

服务器硬件优化

MySQL 服务器集群

TODO