你须要掌握的 Mysql 优化的一些要点

本文是学习《高性能 Mysql》中关于 Mysql 中查询优化须要注意的一些要点的总结:mysql

Schema 和数据类型优化

  • 尽可能避免使用 NULL 值,尤为存在索引时,由于若是 NULL 列是索引,索引统计以及值的比较更加复杂
  • 尽可能选择小的简单的数据类型,由于它们占用更少的磁盘,内存和 CPU 缓存
  • 尽可能使用 TIMESTAMP 代替 DATETIME,由于 TIMESTAMP 只是 DATETIME 一半大小存储空间,还会跟时区变化,可是 TIMESTAMP 容许的时间范围比较小(1970年~2038年)
  • 对于字符串列的最大长度比平均长度大不少的状况建议使用 VARCHAR 类型
  • 对于很是短且比较均衡的列建议使用 CHAR 类型,不容易产生太多的碎片
  • Mysql 对于 BLOB 和 TEXT 类型的排序和其它类型规则不一样,只会对每一个列的前 max_sort_length 字节的字符串进行排序,这样必然会使用临时表,因此尽可能确保 max_sort_length 的值下不要超过 max_heap_table_size 或者 max_table_size,以保证排序时使用内存临时表
  • 不一样类型字段进行关联查询时每每成本比较高,建议若是须要关联查询尽可能改成相同类型
  • 在查询时尽可能不要使用太多的关联,虽然 Mysql 限制了每一个关联操做最多只能有 61 张表,可是为了让查询执行的速度快且并发性好,单个查询不要超过 12 张表关联
  • 除非枚举值是一些固定不变的值,例如“性别”,建议不要过分使用枚举,由于在修改枚举值时须要 ALTER TABLE 成本很是高,并且枚举值的排序是按照枚举顺序来排序,并非字面值

索引优化

  • 若是查询中某个列是范围查询,那么其右边的全部列将没法使用索引优化,因此尽可能将范围条件放在右边或者使用多个等值条件来代替范围查询
  • ORDER BY 中的排序的列若是建了索引,则有可能使用索引进行排序,进行优化性能
  • 只有当索引的列和 ORDER BY 子句的顺序彻底一致且全部列的排序方向一致时才能使用索引作排序
  • 哈希索引对于等值查询的性能提高很是高,可是哈希索引没法用来排序,也不支持部分索引列匹配查找
  • 在使用索引时对应的索引列必须独立,不能是表达式的一部分也不能是函数的参数,不然不能使用索引:
-- 虽然 id 上创建了索引,可是没法使用索引优化
select id from user where id + 1 =5;
复制代码
  • 当服务器出现多个列作 AND 操做查询时,一般须要建了一个多列索引,而不是多个独立的单列索引
  • 当不须要考虑排序和分组时,将选择项最高的列放在前面一般是最好的,由于能够很快的过滤出须要的行
  • 若是索引包含了须要查询的全部字段值,那么就是可使用覆盖索引查询,只须要读取索引,极大地减小了数据访问量,在 EXPLAIN 分析的 Extra 字段中能够看到 “Using index” 信息
  • 查询时尽可能不要返回多余的列,第一能够减小网络流量,第二增长使用覆盖索引的可能性
  • 若是关联多张表时,只有当 ORDER BY 子句引用的字段所有是第一张表时才能使用索引排序
  • 默认类型转换不只增长开销,还会使索引失效,好比 col 是 VCHAR 类型,那么 where col = '10' 会使用索引,而 where col = 10 不会使用索引
  • 不要建立冗余的索引,Mysql 不只须要单独维护索引列,而且在优化器查询时也须要逐个索引进行过滤,会影响性能

下面是建立冗余索引的几个例子:算法

- 建立了索引(A,B)再建立索引(A),那后者即是冗余索引
- 将一根索引扩展为(A,ID),其中 ID 是主键,对于 InnoDB 来讲主键已经包含在二级索引中了,因此这也是冗余的
复制代码
  • 有一些索引可能服务器永远都不会用到,建议考虑删除,在 percona 版本或 marida 中能够经过 information_schea.index_statistics 查看获得索引的使用状况,在官方版本中可使用 performance_schema.table_io_waits_summary_by_index_usage 查看索引使用状况:
- 能够查到使用最多或者使用最少的表和索引
- 能够查到从未使用过的索引,考虑删除之
- 能够查到线程的使用状况等等
复制代码

事务优化

  • 尽可能不要在事务中混合使用存储引擎,若是有些表支持事务,有些表不支持事务,回滚时会致使数据不一致问题
  • 在应用层应该检查在事务中是否存在 RPC 调用、HTTP 调用、消息队列、缓存、循环查询等耗时的操做,这个操做应该尽可能移到事务以外,由于这些操做会增长事务的处理时间,使 sql 查询不稳定,理想的状况是事务内只处理数据库操做;

其它查询优化

  • 一个大的 DELETE 或者 UPDATE 查询极可能会一次性锁住不少数据,占满整个事务日志,阻塞其它小的重要的查询,若是有可能能够把大的查询拆分红多个小的查询。
  • 关联查询分解:
- 让单表查询的缓存效率更高
- 拆分后用 IN() 代替关联查询,可让 Mysql 按照 ID 顺序去查找
- 能够将数据分布到不一样的 Mysql 服务器上
复制代码
  • 使用 IN 加子查询性能一般都会很低,因此建议使用 EXISTS 等效的查询来获取更好的效率
  • UNION 操做会比 UNION ALL 操做耗时,由于 UNION 操做在合并之后,还要做去重排序操做,除非必须使用 UNION 查询,不然就使用 UNION ALL 查询
  • 能写在 WHERE 条件中判断不要写在 HAVING 子句中,由于 GROUP BY 会对数据进行排序,若是事先排除掉一些数据,会减小排序量,还有就是聚合后的视图可能索引条件已经丢失
  • IS NULL 或者 IS NOT NULL 查询会使索引失效
  • 当觉得当前查询只有一行数据时使用可使用 LIMIT 1,这样检索到一条数据后,就中止搜索了
  • HAVING 子句和 GROUP BY 子句一块儿使用时比先 GROUP BY 成中间表再执行 WHERE 要快
  • GROUP BY 子句会自动对分组的列进行排序,若是不但愿进行排序可使用 ORDER BY NULL
  • 尽量将 GROUP BY WITH ROLLUP 放到应用程序去完成,由于 Mysql 作超级聚合每每性能不佳
  • 优化策略在 UNION 查询中无法很好的使用,通常须要将 WHERE,ORDER BY,LIMIT 子句下推到各个子查询中
  • 优化 COUNT() 查询:
- 若是是统计结果集的大小,请使用 COUNT(*),使用 COUNT(cloumn) 有可能某个列存在 NULL 致使统计不许确,排除 NULL 计算也是要成本的
- 对于 MyIsam 存储引擎,若是不带任何 WHERE 条件的状况下, COUNT(*) 不须要计算,直接经过存储引擎特性得到
复制代码
  • LIMIT 分页优化
-- 分页时对于偏移量特别大的状况下,查询全部列分页将很是耗时,可使用“延迟关联”的方式,其中一个查询中尽量的使用索引覆盖扫描方式 LIMIT 查询出主键 ID,而后再和原表作一次关联返回须要的列:
-- 优化前
select * from user order by id limit 1000, 5;
-- 优化后
select user.* from user join (select id from user order by id limit 1000, 5) new_user on new_user.id = user.id;

-- 若是在一个位置上预先计算出了边界,能够将 limit 查询转换为已知位置的查询进行优化
select * from user where id between 1000 and 1005 order by id 
复制代码

使用查询提示进行优化

若是对优化器的执行计划不满意可使用优化器的几个提示来控制最终的执行计划:sql

HIGH_PRIORITY 和 LOW_PRIORITY

HIGH_PRIORITY 和 LOW_PRIORITY 对于使用表锁的存储引擎有效,HIGH_PRIORITY 会将当前查询插入到全部处于表锁等待的 SQL 队列前面,而 LOW_PRIORITY 会将当前查询放在全部等待表锁的 SQL 队列队尾,只要队列中还有须要访问同一张表的 SQL, 它就被处于等待状态。数据库

DELAYED

该提示对 INSERT 和 REPLACE 有效,使用该提示后会当即返回给客户端,而后将插入的行放入缓存区,等待表空闲时批量写入数据。缓存

该操做致使 LAST_INSERT_ID() 函数没法正常工做。安全

对于一些数据记录,即便插入失败也不影响服务正常运行,可使用该操做,及时响应客户端,加快响应速度。bash

STRAIGHT_JOIN

让全部查询中的的表按照语句中出现的顺序进行关联,不须要 Mysql 优化器去从新选择关联顺序,若是能确保本身写的关联顺序性能比较好的状况下能够选择该提示,减小 Mysql 优化器自己选择分析的时间。服务器

SQL_SMALL_RESULT 和 SQL_BIG_RESULT

这两个提示针对 select 操做,告诉优化器对 group by 或 distinct 如何使用临时表及排序,若是 SQL_SMALL_RESULT 表示结果集很小,使用内存排序,若是是 SQL_BIG_RESULT 表示结果集很大,使用磁盘临时表排序。网络

SQL_CACHE 和 SQL_NO_CACHE

这个提示告诉 Mysql 结果集是否要缓存在查询缓存中数据结构

SQL_CALC_FOUND_ROWS

FOUND_ROWS 这个函数通常状况下只会返回上一次查询的数据集大小,可是若是加了 SQL_CALC_FOUND_ROWS 提示,那么将返回不带 limit 状况下整个数据集大小,这个参数对于分页有必定的用处,不须要屡次查询。

FOR_UPDATE 和 LOCK IN SHARE MODE

该提示只对支持行级锁的存储引擎生效,该提示会对查询中符合条件的数据加锁

这两个提示会让 InnoDB 覆盖索引优化失效,由于 InnoDB 须要访问主键中的版本信息。

USE INDEX 和 IGNORE INDEX 及 FORCE INDEX

告诉优化器是否使用某个索引

合理使用分区表

  • 分区表数据更容易维护,想删除大量数据能够直接使用清除某个分区的方式,而且能够独立备份和恢复某个分区
  • 分区表的数据能够分布到多个物理设备上,有效的利用硬件设备
  • 若是分区列有 NULL 值,可能使分区过滤无效,由于 NULL 值会被存储在第一个分区中
  • 避免创建与分区列不匹配的索引,由于这样根据索引查询会使分区没法区分
  • 在查找访问分区时,Mysql 须要打开并锁住全部的底层表,对于简单的查询来讲这个消耗仍是有点高,可使用批量操做减小开销次数
  • 全部分区都必须使用相同的存储引擎,分区中可使用的函数和表达式也有必定的限制
  • Mysql 只能使用分区函数列自己查询时才可使用分区过滤,不能将分区列放入表达式,此时没法找到对应分区进行过滤

合理使用视图/外键/触发器

  • 建立视图有两种算法:临时表算法和合并算法,若是可能尽可能使用合并算法,使用合并算法时 Mysql 会将视图与基于视图的查询语句进行合并而后优化器基于此进行优化
  • 经过 explain 解析字段 select_type 判断视图使用临时表算法仍是合并算法,在建立查询时能够指定具体使用什么算法,
  • 若是只是使用外键作约束,那么一般在应用程序里实现会更好,外键会带来很大的额外开销
  • 触发器容易掩盖背后的工做,并且问题比较难以排查,可能致使死锁,尽可能不要使用触发器

合理使用绑定变量

  • 使用绑定变量,Mysql 服务器只须要解析一次 SQL 语句,而且会缓存一部分执行计划
  • 使用绑定变量每次仅仅发送的参数,而不是整个查询语句,减小网络开销
  • 绑定变量也相对安全,不须要处理转义,大大减小 SQL 注入和攻击的风险
  • 绑定变量是会话级别的,不一样链接之间不能共用

合理使用查询缓存

  • 若是表发生变化,对应的查询缓存则会失效
  • 查询缓存是否命中与自己查询 SQL,查询的数据库,客户端协议的版本有关系
  • 查询中包含自定义函数,存储函数,用户变量,临时表,Mysql 库中的系统表都不会设置缓存,也不会命中缓存
  • 只有整个事务提交后,相关的查询结果才会被缓存
  • 查询缓存对于复杂计算,耗时比较长的查询有很大优化效果,
  • 对于简单的查询,由于查询缓存的预判检查也自己比较耗时,再加上数据变化比较快时,相反会下降性能
  • 建议查询时使用 SQL_CACHE 和 SQL_NO_CACHE 来进行选择性的使用查询缓存
  • 对于 InnoDB 若是表上有任何锁,那么任何查询都没法从缓存中读取与这个表相关的缓存结果
  • 如何优化查询缓存:
- 用多个小表代替一个大表,可让缓存失效在一个更细的粒度上进行
- 批量写入时只作一次缓存失效,因此比单条写入更好
- 若是没法在数据库或者表级别控制查询缓存,则可使用 SQL_CACHE 和 SQL_NO_CACHE 来控制单个 select 语句是否进行缓存,而且能够修改会话级别的 query_cache_type 来控制查询缓存
- 对于写密集型的应用来讲,关闭查询缓存对性能会更好
复制代码

合理使用 Mysql 服务器配置

  • mysql 的配置文件通常在 /etc/my.cnf 或者 /etc/mysql/my.cnf
  • 任何打算长期保存的配置都应该经过配置文件保存,不该该在命令行里生效,以防下次启动失效
  • DEFAULT 是一个特殊值能够经过 SET 设置给变量:这个值会把会话级变量设置为全局变量,会把全局变量设置为编译器内置的默认值
  • mysql 主要的几个环境变量配置说明:
datadir=    /var/lib/mysql                  # 数据的存储位置

user=       mysql                           # 执行 mysql 用户运行 mysql 实例,要保证该用户存在

port=       3306                            # mysql 实例的端口号

socket:    =/var/lib/mysql/mysql.sock      # socket 文件存储位置,用于 TCP/IP 套接字链接数据库

pid_file    = /var/lib/mysql/mysql.pid      # mysql 进程 id

default_storage_engine        = InnoDB      # 默认的存储引擎

innodb                        = FORCE       # 只有在 Innodb 存储引擎正常启动时,服务器才能正常启动,通常建议设置为 FORCE,保证能够正确使用 InnoDB 存储引擎

innodb_buffer_pool_size       = <value>     # InnoDB 存储引擎可使用的缓存大小

innodb_log_file_size          = <value>     # 设置重作日志大小,过小写入日志须要频繁的刷新磁盘,使写入变慢,太大奔溃恢复时间变慢,要合理设置

innodb_thread_concurrency     = 0           # 它能够限制一次性有多少线程进入内核,0 表示不限制。通常建议设置为:CPU 数量 * 磁盘数量 * 2

innodb_thread_sleep_delay     = 10000       # 为了减小由于操做系统调度引发的上下文切换,线程第一次没法进入内核会休眠 innodb_thread_sleep_delay 秒之后再尝试
                                            # 若是再次没法进入内核,则放入线程等待队列,让操做系统来处理
                                            
innodb_file_per_table         = 1           # 是否让每一张表使用一个独立文件存储,使得删除表变的简单,而且容易分散到不一样的磁盘上,可是会致使空间的浪费

innodb_flush_method           = 0_DIRECT    # 控制 InnoDB 如何和文件系统相互做用,控制将数据刷新到磁盘的方式,要不要使用磁盘缓存等

key_buffer_size               = <value>     # MyISAM 存储引擎分配的键缓存大小,该值对使用 MyISAM 存储引擎的数据库很是重要
                                            # 即便是使用 InnoDB 存储引擎也应该分配必定空间(32M),由于 Mysql 中一些系统表会使用 MyISAM 存储引擎
                                            # Group by 建立临时表时也可能使用 MyISAM 存储引擎
                                            
sort_buffer_size              = <value>     # 该参数会在查询使用内存排序时分配内存,一旦须要排序就会指定这么大的内存,无论是否须要这么大的内存 
                                            # 通常建议把 sort_buffer_size 修改的小一点,若是某个查询确实须要很大内存排序,能够在会话级临时调大该值
                                            
log_error=/var/lib/mysql/mysql-error.log    # 错误日志存放位置

slow_query_log=/var/lib/mysql/mysql-show.log# 慢查询日志存放位置

tmp_table_size/max_heap_table_size = 32M    # 这两个变量用于控制使用内存临时表(Memory存储引擎)的大小,若是超过这个值,将使用磁盘临时表(MyISAM存储引擎)
                                            # 经过 show status 观察 Created_tmp_disk_tables 和 create_tmp_tables 的变化来调整这两个参数
                                            
query_cache_type              = 0           # 控制查询缓存功能的开启和关闭,0 表示关闭,1 表示开启,2 表示只有 select 中明确指定 SQL_CACHE 才缓存

query_cache_size              = 0           # 设置查询缓存的大小

max_connections               = <value>     # 最大链接数,默认是 100,每每过小,若是过小会报太多链接被拒绝的错误,观察 Max_used_connections 状态变量来设置该参数

thread_cache_size             = <value>     # 指定 Mysql 能够保存在缓存中的线程数,通常经过观察 Thread_connected 变量的大小来调整该值的大小

table_cache_size              = 1000        # 设置表缓存大小,设置足够的大小以免老是须要从新打开并从新解析表定义

open_files_limit              = 65535       # 这个参数能够尽可能设置大,由于打开句柄的开销很小,不然会出现“too many open files”

expire_logs_days              = 10          # 服务器在指定的天数后清理二进制日志

max_connect_errors            = 100         # 允许某个应用最大错误次数,若是超过该值,将被加入黑名单,除非刷新主机缓存
复制代码
  • 几个 timeout 相关参数说明:
- connect_timeout

在获取链接阶段(authenticate)起做用,获取 MySQL 链接是屡次握手的结果,除了用户名和密码的匹配校验外,还有 IP->HOST->DNS->IP 验证,任何一步均可能由于网络问题致使线程阻塞。
为了防止线程浪费在没必要要的校验等待上,超过 connect_timeout 的链接请求将会被拒绝,默认值 10 秒。

- interactive_timeout 和 wait_timeout

在链接空闲阶段(sleep)起做用,即便没有网络问题,也不能容许客户端一直占用链接。
对于保持 sleep 状态超过了 wait_timeout(或 interactive_timeout,取决于 client_interactive 标志)的客户端,MySQL 会主动断开链接,默认值是 8 小时。

- net_read_timeout 和 net_write_timeout

则是在链接繁忙阶段(query)起做用,即便链接没有处于 sleep 状态,即客户端忙于计算或者存储数据,MySQL 也选择了有条件的等待。
在数据包的分发过程当中,客户端可能来不及响应(发送、接收、或者处理数据包太慢)。
为了保证链接不被浪费在无尽的等待中,MySQL 也会选择有条件(net_read_timeout和net_write_timeout)地主动断开链接。默认是 30 秒。

- innodb_lock_wait_timeout

innodb 使用这个参数可以有效避免在资源有限的状况下产生太多的锁等待,指的是事务等待获取资源时等待的最长时间,超过这个时间还未分配到资源则会返回应用失败。
参数的时间单位是秒,最小可设置为1s(通常不会设置得这么小),最大可设置1073741824秒(34年),默认安装时这个值是 50 s。
超过这个时间会报 ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction。

- innodb_rollback_on_timeout

默认状况下 innodb_lock_wait_timeout 超时后只是超时的 sql 执行失败,整个事务并不回滚,也不作提交。
如须要事务在超时的时候回滚,则须要设置 innodb_rollback_on_timeout=ON,该参数默认为 OFF。

- lock_wait_timeout

和 innodb_lock_wait_timeout 的区别是前者是 Innodb 的 DML 操做的行级锁的等待时间,后面是数据结构 DDL 操做的锁的等待时间。

- innodb_flush_log_at_timeout

参数 innodb_flush_log_at_trx_commit = 1 时,此超时参数不起做用。当 innodb_flush_log_at_trx_commit=0/2 时才起做用。
表示每 innodb_flush_log_at_timeout 秒进行一次的频率刷新 redo log(在 5.6.6 版本以前是固定每秒一次刷新 redo log,5.6.6 版本以后刷新频率能够经过这个参数设置,固然,这个参数自己默认值也是 1S)
复制代码
相关文章
相关标签/搜索