Python-100-Days/Day91-100/93.MySQL性能优化.md

12 KiB
Raw Blame History

MySQL性能优化

使用索引

在前面《关系型数据库MySQL》一文中,我们已经讲到过索引的相关知识,这里我们做一个简单的回顾。

索引的设计原则

  1. 创建索引的列并不一定是select操作中要查询的列,最适合做索引的列是出现在where子句中经常用作筛选条件或连表子句中作为表连接条件的列。
  2. 具有唯一性的列,索引效果好;重复值较多的列,索引效果差。
  3. 如果为字符串类型创建索引最好指定一个前缀长度创建短索引。短索引可以减少磁盘I/O而且在做比较时性能也更好更重要的是MySQL底层的高速索引缓存能够缓存更多的键值。
  4. 创建一个包含N列的复合索引多列索引相当于是创建了N个索引此时应该利用最左前缀进行匹配。
  5. 不要过度使用索引。索引并不是越多越好索引需要占用额外的存储空间而且会影响写操作的性能插入、删除、更新数据时索引也需要更新。MySQL在生成执行计划时要考虑各个索引的使用这个也是需要耗费时间的。
  6. 要注意可能使索引失效的场景,例如:模糊查询使用了前置通配符、使用负向条件进行查询等。

使用过程

过程通常也称之为存储过程它是事先编译好存储在数据库中的一组SQL的集合。调用存储过程可以简化应用程序开发人员的工作减少与数据库服务器之间的通信对于提升数据操作的性能是有帮助的这些我们在之前的《关系型数据库MySQL》一文中已经提到过。

数据分区

MySQL支持做数据分区通过分区可以存储更多的数据、优化查询获得更大的吞吐量并快速删除过期的数据。关于这个知识点建议大家看看MySQL的官方文档。数据分区有以下几种类型:

  1. RANGE分区基于连续区间范围把数据分配到不同的分区。

    CREATE TABLE tb_emp (
        eno INT NOT NULL,
        ename VARCHAR(20) NOT NULL,
        job VARCHAR(10) NOT NULL,
        hiredate DATE NOT NULL,
        dno INT NOT NULL
    )
    PARTITION BY RANGE( YEAR(hiredate) ) (
        PARTITION p0 VALUES LESS THAN (1960),
        PARTITION p1 VALUES LESS THAN (1970),
        PARTITION p2 VALUES LESS THAN (1980),
        PARTITION p3 VALUES LESS THAN (1990),
        PARTITION p4 VALUES LESS THAN MAXVALUE
    );
    
  2. LIST分区基于枚举值的范围把数据分配到不同的分区。

  3. HASH分区 / KEY分区基于分区个数把数据分配到不同的分区。

    CREATE TABLE tb_emp (
        eno INT NOT NULL,
        ename VARCHAR(20) NOT NULL,
        job VARCHAR(10) NOT NULL,
        hiredate DATE NOT NULL,
        dno INT NOT NULL
    )
    PARTITION BY HASH(dno)
    PARTITIONS 4;
    

SQL优化

  1. 定位低效率的SQL语句 - 慢查询日志。

    • 查看慢查询日志相关配置

      mysql> show variables like 'slow_query%';
      +---------------------------+----------------------------------+
      | Variable_name             | Value                            |
      +---------------------------+----------------------------------+
      | slow_query_log            | OFF                              |
      | slow_query_log_file       | /mysql/data/localhost-slow.log   |
      +---------------------------+----------------------------------+
      
      mysql> show variables like 'long_query_time';
      +-----------------+-----------+
      | Variable_name   | Value     |
      +-----------------+-----------+
      | long_query_time | 10.000000 |
      +-----------------+-----------+
      
    • 修改全局慢查询日志配置。

      mysql> set global slow_query_log='ON'; 
      mysql> set global long_query_time=1;
      

      或者直接修改MySQL配置文件启用慢查询日志。

      [mysqld]
      slow_query_log=ON
      slow_query_log_file=/usr/local/mysql/data/slow.log
      long_query_time=1
      
  2. 通过explain了解SQL的执行计划。例如

    explain select ename, job, sal from tb_emp where dno=20\G
    *************************** 1. row ***************************
               id: 1
      select_type: SIMPLE
            table: tb_emp
             type: ref
    possible_keys: fk_emp_dno
              key: fk_emp_dno
          key_len: 5
              ref: const
             rows: 7
            Extra: NULL
    1 row in set (0.00 sec)
    
    • select_type查询类型SIMPLE - 简单查询、PRIMARY - 主查询、UNION - 并集、SUBQUERY - 子查询)。
    • table:输出结果集的表。
    • type访问类型ALL - 全表查询性能最差、index、range、ref、eq_ref、const、NULL
    • possible_keys:查询时可能用到的索引。
    • key:实际使用的索引。
    • key_len:索引字段的长度。
    • rows:扫描的行数,行数越少肯定性能越好。
    • extra:额外信息。
  3. 通过show profilesshow profile for query分析SQL。

    MySQL从5.0.37开始支持剖面系统来帮助用户了解SQL执行性能的细节可以通过下面的方式来查看MySQL是否支持和开启了剖面系统。

    select @@have_profiling;
    select @@profiling;
    

    如果没有开启剖面系统可以通过下面的SQL来打开它。

    set profiling=1;
    

    接下来就可以通过剖面系统来了解SQL的执行性能例如

    mysql> select count(*) from tb_emp;
    +----------+
    | count(*) |
    +----------+
    |       14 |
    +----------+
    1 row in set (0.00 sec)
    
    mysql> show profiles;
    +----------+------------+-----------------------------+
    | Query_ID | Duration   | Query                       |
    +----------+------------+-----------------------------+
    |        1 | 0.00029600 | select count(*) from tb_emp |
    +----------+------------+-----------------------------+
    1 row in set, 1 warning (0.00 sec)
    
    mysql> show profile for query 1;
    +----------------------+----------+
    | Status               | Duration |
    +----------------------+----------+
    | starting             | 0.000076 |
    | checking permissions | 0.000007 |
    | Opening tables       | 0.000016 |
    | init                 | 0.000013 |
    | System lock          | 0.000007 |
    | optimizing           | 0.000005 |
    | statistics           | 0.000012 |
    | preparing            | 0.000010 |
    | executing            | 0.000003 |
    | Sending data         | 0.000070 |
    | end                  | 0.000012 |
    | query end            | 0.000008 |
    | closing tables       | 0.000012 |
    | freeing items        | 0.000032 |
    | cleaning up          | 0.000013 |
    +----------------------+----------+
    15 rows in set, 1 warning (0.00 sec)
    
  4. 优化CRUD操作。

    • 优化insert语句

      • insert语句后面跟上多组值进行插入在性能上优于分开insert
      • 如果有多个连接向同一个表插入数据,使用insert delayed可以获得更好的性能。
      • 如果要从一个文本文件装载数据到表时,使用load data infileinsert性能好得多。
    • 优化order by语句

      • 如果where子句的条件和order by子句的条件相同,而且排序的顺序与索引的顺序相同,如果还同时满足排序字段都是升序或者降序,那么只靠索引就能完成排序。
    • 优化group by语句

      • 在使用group by子句分组时,如果希望避免排序带来的开销,可以用order by null禁用排序。
    • 优化嵌套查询

      • MySQL从4.1开始支持嵌套查询子查询这使得可以将一个查询的结果当做另一个查询的一部分来使用。在某些情况下子查询可以被更有效率的连接查询取代因为在连接查询时MySQL不需要在内存中创建临时表来完成这个逻辑上需要多个步骤才能完成的查询。
    • 优化or条件

      • 如果条件之间是or关系,则只有在所有条件都用到索引的情况下索引才会生效。
    • 优化分页查询

      • 分页查询时,一个比较头疼的事情是如同limit 1000, 20此时MySQL已经排序出前1020条记录但是仅仅返回第1001到1020条记录前1000条实际都用不上查询和排序的代价非常高。一种常见的优化思路是在索引上完成排序和分页的操作然后根据返回的结果做表连接操作来得到最终的结果这样可以避免出现全表查询也避免了外部排序。

        select * from tb_emp order by ename limit 1000, 20;
        select * from tb_emp t1 inner join (select eno from tb_emp order by ename limit 1000, 20) t2 on t1.eno=t2.eno;
        

        上面的代码中第2行SQL是优于第1行SQL的当然我们的前提是已经在ename字段上创建了索引。

    • 使用SQL提示

      • USE INDEX建议MySQL使用指定的索引。
      • IGNORE INDEX建议MySQL忽略掉指定的索引。
      • FORCE INDEX强制MySQL使用指定的索引。

配置优化

可以使用下面的命令来查看MySQL服务器配置参数的默认值。

show variables;
show variables like 'key_%';
show variables like '%cache%';
show variables like 'innodb_buffer_pool_size';

通过下面的命令可以了解MySQL服务器运行状态值。

show status;
show status like 'com_%';
show status like 'innodb_%';
show status like 'connections';
show status like 'slow_queries';
  1. 调整max_connectionsMySQL最大连接数量默认151。在Linux系统上如果内存足够且不考虑用户等待响应时间这些问题MySQL理论上可以支持到万级连接但是通常情况下这个值建议控制在1000以内。
  2. 调整back_logTCP连接的积压请求队列大小通常是max_connections的五分之一最大不能超过900。
  3. 调整table_open_cache这个值应该设置为max_connections的N倍其中N代表每个连接在查询时打开的表的最大个数。
  4. 调整innodb_lock_wait_timeout该参数可以控制InnoDB事务等待行锁的时间默认值是50ms对于反馈响应要求较高的应用可以将这个值调小避免事务长时间挂起对于后台任务可以将这个值调大来避免发生大的回滚操作。
  5. 调整innodb_buffer_pool_sizeInnoDB数据和索引的内存缓冲区大小以字节为单位这个值设置得越高访问表数据需要进行的磁盘I/O操作就越少如果可能甚至可以将该值设置为物理内存大小的80%。

架构优化

  1. 通过拆分提高表的访问效率。

    • 垂直拆分
    • 水平拆分
  2. 逆范式理论。数据表设计的规范程度称之为范式Normal Form要提升表的规范程度通常需要将大表拆分为更小的表范式级别越高数据冗余越小而且在插入、删除、更新数据时出问题的可能性会大幅度降低但是节省了空间就意味着查询数据时可能花费更多的时间原来的单表查询可能会变成连表查询。为此项目实践中我们通常会进行逆范式操作故意降低范式级别增加冗余来减少查询的时间开销。

    • 1NF列不能再拆分
    • 2NF所有的属性都依赖于主键
    • 3NF所有的属性都直接依赖于主键消除传递依赖
    • BCNF消除非平凡多值依赖
  3. 使用中间表提高统计查询速度。

    使用insert into 中间表 select ... where ...这样的语句先将需要的数据筛选出来放到中间表中,然后再对中间表进行统计,避免不必要的运算和处理。

  4. 主从复制和读写分离,具体内容请参考《项目部署上线和性能调优》

  5. 配置MySQL集群。

说明本章内容参考了网易出品的《深入浅出MySQL》一书该书和《高性能MySQL》一样都对MySQL进行了深入细致的讲解虽然总体感觉后者更加高屋建瓴但是前者也算得上是提升MySQL技能的佳作作者的文字功底稍显粗糙深度也不及后者建议有兴趣的读者可以阅读这两本书。