只要涉及到MySQL数据库,那么其调优一定是必不可少的。往往大家都给具体化为MySQL查询语句及索引的调优,这样描述是不太准确的,因为MySQL的调优不仅仅是SQL语句,还有硬件升级系统配置优化表结构涉及优化开发规范约定等 。从这些维度上来说,只要能提高MySQL的性能,我们都可以称之为MySQL调优。

特点:

  • 优化成本:硬件升级>系统配置>表结构设计>SQL语句及索引。

  • 优化效果:硬件升级<系统配置<表结构设计<SQL语句及索引。

硬件升级

从研发之前数据库选型之后,就应该需要对数据库的用途、性能需要、数据量级等指标有个大概的评估,在满足当前需要的基础上尽量选择性能更好的硬件,找好硬件成本与性能需求之间的平衡点。

  • 可以使用更好的cpu,更大的内存。
  • 可以使用SSD,最大化磁盘IO。

系统配置(innodb)

给MySQL配置足够的内存

大家知道,MySQL是有缓存的,无论是查询缓存,还是pool_buffer里的缓存,都是使用内存,如果内存不够的情况下,就会采取LRU算法来刷盘到磁盘中,读取时就会变慢。

加大 innodb_buffer_pool_size ,使其尽量降低磁盘IO。

mysql> show global status like 'innodb_buffer_pool_pages_%'; 
+----------------------------------+-------+ 
| Variable_name 									 | Value |
+----------------------------------+-------+ 
| Innodb_buffer_pool_pages_data     |8190  | 
| Innodb_buffer_pool_pages_dirty    |0     | 
| Innodb_buffer_pool_pages_flushed  |12646 | 
| Innodb_buffer_pool_pages_free     | 0    | 0 表示已经被用光 
| Innodb_buffer_pool_pages_misc     | 1    | 
| Innodb_buffer_pool_pages_total    | 8191 | 
+----------------------------------+-------+

innodb_buffer_pool_size 默认为128M,理论上可以扩大到内存的3/4或4/5。

修改 my.cnf,然后重启

innodb_buffer_pool_size = 256M

降低磁盘写入次数

  • 增大redolog,减少落盘次数。

可以将innodb_log_file_size的大小设置为整个pool_buffer的1/4,即 0.25 * innodb_buffer_pool_size。

  • 通用查询日志可以关闭(慢查询和bin-log必须打开)。

  • redolog刷盘策略 innodb_flush_log_at_trx_commit可以适度降低为0或者2(非金融类极度需要安全性的业务场景)。

    0:默认值。事务提交后,将二进制日志从缓冲写入磁盘,但是不进行刷新操作(fsync()),此时只是写入了操作系统缓冲,若操作系统宕机则会丢失部分二进制日志。

    1:事务提交后,将二进制文件写入磁盘并立即执行刷新操作,相当于是同步写入磁盘,不经过操作系统的缓存。

    N:每写N次操作系统缓冲就执行一次刷新操作。

    将这个参数设为1以上的数值会提高数据库的性能,但同时会伴随数据丢失的风险。

数据库初启动时预热

数据库启动完成后,只有当查询到某条数据时才会进行读取到缓存中,那么我们可以提前预热来让业务请求均使用到缓存。

InnoDB数据库,进行数据预热的脚本是:

SELECT 
	DISTINCT CONCAT('SELECT ',ndxcollist,' FROM ',db,'.',tb, ' ORDER BY ',ndxcollist,';') SelectQueryToLoadCache
FROM 
    (
    SELECT
      engine, table_schema db, table_name tb, index_name,
      GROUP_CONCAT(column_name ORDER BY seq_in_index) ndxcollist
    FROM
        (
        SELECT
          B.engine, A.table_schema, A.table_name,
          A.index_name, A.column_name, A.seq_in_index
        FROM 
        	information_schema.statistics A
        	INNER JOIN (
              SELECT engine, table_schema, table_name
              FROM information_schema.tables
              WHERE engine='InnoDB' 
           ) B USING (table_schema,table_name)
         WHERE B.table_schema NOT IN ('information_schema','mysql')
         ORDER BY  table_schema,table_name,index_name,seq_in_index 
       ) A
     GROUP BY  table_schema,table_name,index_name 
   ) AA
ORDER BY  db,tb;
  1. 将脚本保存为 preloadmem.sql
  2. 执行命令:
mysql -uroot -proot -AN < /root/preloadmem.sql > /root/preloadmem.sql

表结构设计优化

非实时数据,设计中间表

对于实时性要求不高的查询数据,比如统计报表,可以每隔一段时间进行统计,写入此表,能节省频繁统计查询导致数据库压力过大。

冗余字段,减少连表查询

需要注意数据一致性问题,多系统之间可以通过MQ等方式来同步。

合理设计字段类型

比如性别可以用tinyint,boolean值可以用bit,尽量设置NOT NULL,大字段单独拆表存储。

主键优化

每张表必须有主键,类型最好为数值型而不是字符型。

SQL语句及索引优化

MySQL的语句优化,一般有以下几个步骤

1.使用explain命令查看索引情况

需要着重关注下typeextra字段

  • type

    表示存储引擎查询数据时采用的方式。比较重要的一个属性,通过它可以判断出查询是全表扫描还是基于索引的部分扫描。常用属性值如下,从上至下效率依次增强。**至少要达到range级别。杜绝出现all级别。 **

    • ALL:表示全表扫描,性能最差。
    • index:表示基于索引的全表扫描,先扫描索引再扫描全表数据。
    • range:表示使用索引范围查询。使用>、>=、<、<=、in等等。
    • ref:表示使用非唯一索引进行单值查询。
    • eq_ref:一般情况下出现在多表join查询,表示前面表的每一个记录,都只能匹配后面表的一行结果。
    • const:表示使用主键或唯一索引做等值查询,常量查询。
    • NULL:表示不用访问表,速度最快。
  • Extra

    Extra表示很多额外的信息,各种操作会在Extra提示相关信息,常见几种如下:

    • Using where 表示查询需要通过索引回表查询数据。(回表查询:通过索引查询主键值,然后再去聚簇索引查询记录信息)
    • Using index 表示查询需要通过索引,索引就可以满足所需数据。(索引覆盖:仅在索引树上就可以查询到所需的值,无需回表查询)
    • Using fifilesort 表示查询出来的结果需要额外排序,数据量小在内存,大的话在磁盘,因此有Using fifilesort ,建议优化
    • Using temprorary 查询使用到了临时表,一般出现于去重、分组等操作。

2.根据explain结果进行调优

调优手段如下:

  • select 查询字段尽量少,且指名。最大可能减少回表查询。

  • 查询字段创建索引或组合索引,避免全表扫描。

  • 组合索引是否满足最左匹配原则。

  • 索引过滤性问题,即创建索引的列的数据重复度越低越好。

  • IN 子句数据值不应太多,该数据会存放到数组中,且需要排序。

  • 巧用limit来停止全表扫描, 比如 limit 1。

  • 大表分页查询优化

    分页使用 limit m,n 尽量让m 小,举例如下:limit 1000000, 10;可以找到1000000那条数据的id,然后where id > xxx limit 10;

  • 排序字段增加索引,利用索引天然的有序性,减少内存排序 filesort。

  • 少用 OR ,否则左右两边任一条件字段无索引,均会导致索引失效。

  • 尽量用 union all 代替 union

    union和union all的差异主要是前者需要将结果集合并后再进行唯一性过滤操作,这就会涉及到排序,

    增加大量的CPU运算,加大资源消耗及延迟。当然,union all的前提条件是两个结果集没有重复数据。

  • 不要使用随机函数ORDER BY RAND() ,它不会走索引。

  • in 和 exists,in适合小表驱动大表,而exists相反。

  • where子句避免对字段进行函数操作,会导致索引时效。 WHERE age+1 = 30 可以修改为 WHERE age = 30 -1 ;

  • 条件查询字段类型要一致,避免隐式转换。比如 WHERE age = ‘30’ 修改 为 WHERE age = 30;

  • 如果inner join能满足查询业务需求,则尽量使用inner join而不是left join,这种情况下MySQL会自动选择小表作为驱动表,减少嵌套循环次数。join的字段需要创建索引。