查询缓存及索引:MySQL系列之九
一、MySQL的架构
- 连接器
- 连接池,安全认证、线程池、连接限制、检查内存、缓存
- SQL接口 DML、DDL
- SQL解析器,对SQL语句的权限检查、解析为二进制程序
- 优化器,优化访问路径
- 缓存cache,buffer
- 存储引擎 innodb
- 文件系统
- 日志
二、查询缓存(Query Cache)
- SQL语句
- 查询缓存
- 解析器
- 解析树
- 预处理
- 查找最好的查询路径
- 查询优化SQL语句
- 执行计划
- API调用存储引擎
-
调用数据,返回结果
缓存SELECT操作或预处理查询的结果集和SQL语句,当有新的SELECT语句或预处理查询语句请求,先去查询缓存,判断是否存在可用的记录集,判断标准:与缓存的SQL语句,是否完全一样,区分大小写。
不需要对SQL语句做任何解析和执行,当然语法解析必须通过在先,直接从Query Cache中获得查询结果,提高查询性能
查询缓存的判断规则,不够智能,也即提高了查询缓存的使用门槛,降低其效率;查询缓存的使用,会增加检查和清理Query Cache中记录集的开销
哪些查询可能不会被缓存:
- 查询语句中加了SQL_NO_CACHE参数;
- 查询语句中含有获得值的函数,包含自定义函数,如:NOW()、CURDATE()、GET_LOCK()、RAND()、CONVERT_TZ()等;
- 对系统数据库的查询:mysql、information_schema 查询语句中使用SESSION级别变量或存储过程中的局部变量;
- 查询语句中使用了LOCK IN SHARE MODE、FOR UPDATE的语句,查询语句中类似SELECT …INTO 导出数据的语句;
- 对临时表的查询操作;存在警告信息的查询语句;不涉及任何表或视图的查询语句;某用户只有列级别权限的查询语句;
- 事务隔离级别为Serializable时,所有查询语句都不能缓存。
查询缓存相关的服务器变量:
- query_cache_min_res_unit: 查询缓存中内存块的最小分配单位,默认4k,较小值会减少浪费,但会导致更频繁的内存分配操作,较大值会带来浪费,会导致碎片过多,内存不足;
- query_cache_limit:单个查询结果能缓存的最大值,默认为1M,对于查询结果过大而无法缓存的语句,建议使用SQL_NO_CACHE;
- query_cache_size:查询缓存总共可用的内存空间;单位字节,必须是1024的整数倍,最小值40KB,低于此值有警报;
- query_cache_wlock_invalidate:如果某表被其它的会话锁定,是否仍然可以从查询缓存中返回结果,默认值为OFF,表示可以在表被其它会话锁定的场景中继续从缓存返回数据;ON则表示不允许;
- query_cache_type: 是否开启缓存功能,取值为ON, OFF, DEMAND,默认值为ON
- 值为OFF或0时,查询缓存功能关闭;
- 值为ON或1时,查询缓存功能打开,SELECT的结果符合缓存条件即会缓存,否则,不予缓存,显式指定SQL_NO_CACHE,不予缓存;
- 值为DEMAND或2时,查询缓存功能按需进行,显式指定SQL_CACHE的SELECT语句才会缓存;其它均不予缓存。
MariaDB [(none)]> SHOW VARIABLES LIKE 'query_cache%';
+------------------------------+----------+
| Variable_name | Value |
+------------------------------+----------+
| query_cache_limit | 1048576 |
| query_cache_min_res_unit | 4096 |
| query_cache_size | 33554432 |
| query_cache_strip_comments | OFF |
| query_cache_type | ON |
| query_cache_wlock_invalidate | OFF |
+------------------------------+----------+
优化查询缓存:
查询缓存相关的状态变量:
- Qcache_free_blocks:处于空闲状态 Query Cache中内存 Block 数;
- Qcache_free_memory:处于空闲状态的 Query Cache 内存总量;
- Qcache_hits:Query Cache 命中次数;
- Qcache_inserts:向 Query Cache 中插入新的 Query Cache 的次数,即没有命中的次数;
- Qcache_lowmem_prunes:当 Query Cache 内存容量不够,需要删除老的Query Cache 以给新的 Cache 对象使用的次数;
- Qcache_not_cached:没有被 Cache 的 SQL 数,包括无法被 Cache 的 SQL 以及由于 query_cache_type 设置的不会被 Cache 的 SQL语句;
- Qcache_queries_in_cache:在 Query Cache 中的 SQL 数量;
- Qcache_total_blocks:Query Cache 中总的 Block。
MariaDB [(none)]> SHOW GLOBAL STATUS LIKE 'Qcache%';
+-------------------------+----------+
| Variable_name | Value |
+-------------------------+----------+
| Qcache_free_blocks | 1 |
| Qcache_free_memory | 33536824 |
| Qcache_hits | 0 |
| Qcache_inserts | 0 |
| Qcache_lowmem_prunes | 0 |
| Qcache_not_cached | 4 |
| Qcache_queries_in_cache | 0 |
| Qcache_total_blocks | 1 |
+-------------------------+----------+
命中率和内存使用率估算:
- 查询缓存中内存块的最小分配单位query_cache_min_res_unit :(query_cache_size – Qcache_free_memory) / Qcache_queries_in_cache
- 查询缓存命中率 :Qcache_hits / ( Qcache_hits + Qcache_inserts ) * 100%
- 查询缓存内存使用率:(query_cache_size – qcache_free_memory) / query_cache_size * 100%
三、索引
索引是特殊数据结构:定义在查找时作为查找条件的字段,索引实现在存储引擎。
- 索引可以降低服务需要扫描的数据量,减少了IO次数
- 索引可以帮助服务器避免排序和使用临时表
- 索引可以帮助将随机I/O转为顺序I/O
- 但是占用额外空间,影响插入速度
1、索引类型:
- B + Tree 索引:顺序存储,每一个叶子节点到根的距离都是相同的,左前缀索引,适合查询范围类的数据;
- 适合使用B-Tree索引的查询类型
- 全值匹配
- 匹配最左前缀
- 匹配范围值
- 精确匹配某一列并范围匹配另一列(复合索引)
- 只访问索引的查询
- 不适合使用B-tree索引的查询类型
- 不从最左列开始
- 不能跳过索引中的列
- 如果查询中某个列是为范围查询那么右侧的列无法再使用索引优化查询
-
Hash索引:基于哈希表,构建出键值对的索引,特别适用于精确匹配索引中的索引列,只支持等值比较查询(IN,=,<>);不适合于顺序查询,不支持模糊匹配;只有Memory存储引擎支持显式Hash索引
- 空间索引(R – Tree):只有MyISAM支持空间索引
-
全文索引(FULL TEXT):在文本中查找关键词
2、高性能索引策略:
- 独立使用列,尽量避免其参与运算
- 使用左前缀索引:索引构建于字段的左侧的多少字符要通过索引选择性来评估;索引选择性:不重复的索引值和数据表的记录总数的比值
- 多列索引:AND操作时更适合使用多列索引,而非为每个列创建单独的索引
- 选择合适的索引列次序:无排序和分组时,将选择性最高放左侧
3、索引的优化建议
- 只要列中含有NULL值,就最好不要在此例设置索引,复合索引如果有NULL值,此列在使用时也不会使用索引
- 尽量使用短索引,如果可以,应该制定一个前缀长度
- 对于经常在where子句使用的列,最好设置索引
- 对于有多个列where或者order by子句,应该建立复合索引
- 对于like语句,以%或者‘-’开头的不会使用索引,以%结尾会使用索引
- 尽量不要在列上进行运算(函数操作和表达式操作)
- 尽量不要使用not in和<>操作
- 多表连接时,尽量小表驱动大表,即小表 join 大表
- 在千万级分页时使用limit
- 对于经常使用的查询,可以开启缓存
- 大部分情况连接效率远大于子查询
4、索引的创建与删除
- 创建索引
CREATE INDEX index_name ON tbl_name (index_col_name,…);
MariaDB [hellodb]> CREATE INDEX index_name ON students(name); #创建简单索引
MariaDB [hellodb]> CREATE INDEX index_name_age ON students(name,age); #创建复合索引
- 查看索引
SHOW INDEXES FROM [db_name.]tbl_name;
MariaDB [hellodb]> SHOW INDEX FROM students\G
- 删除索引
DROP INDEX index_name ON tbl_name;
MariaDB [hellodb]> DROP INDEX index_name ON students;
- 优化表空间
MariaDB [hellodb]> OPTIMIZE TABLE students;
- 查看索引使用的情况
启用记录索引使用情况:SET GLOBAL userstat=1;
查看索引使用情况:SHOW INDEX_STATISTICS;
我们可以统计不经常使用的索引从而进行优化
四、EXPLAIN命令
通过EXPLAIN来分析索引的有效性:EXPLAIN SELECT clause
,获取查询执行计划信息,用来查看查询优化器如何执行查询
MariaDB [hellodb]> EXPLAIN SELECT name FROM students WHERE name = 'Lin Daiyu'\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: students
type: ref
possible_keys: index_name_age
key: index_name_age
key_len: 152
ref: const
rows: 1
Extra: Using where; Using index
- id:当前查询语句中,每个SELECT语句的编号;复杂类型的查询有三种:简单子查询、用于FROM子句中的子查询、联合查询(UNION,注意:UNION查询的分析结果会出现一个额外匿名临时表)
- select_type:
- SIMPLE :简单查询
- SUBQUERY: 简单子查询
- PRIMARY:最外面的SELECT
- DERIVED: 用于FROM中的子查询
- UNION:UNION语句的第一个之后的SELECT语句
- UNION RESULT: 匿名临时表
- table:SELECT语句关联到的表
- type:关联类型或访问类型,即MySQL决定的如何去查询表中的行的方式,以下顺序,性能从低到高
- ALL: 全表扫描
- index:根据索引的次序进行全表扫描;如果在Extra列出现“Using index”表示了使用覆盖索引,而非全表扫描
- range:有范围限制的根据索引实现范围扫描;扫描位置始于索引中的某一点,结束于另一点
- ref: 根据索引返回表中匹配某单个值的所有行
- eq_ref:仅返回一个行,但与需要额外与某个参考值做比较
- const, system: 直接返回单个行
- possible_keys:查询可能会用到的索引
- key: 查询中使用到的索引
- key_len: 在索引使用的字节数
- ref: 在利用key字段所表示的索引完成查询时所用的列或某常量值
- rows:MySQL估计为找所有的目标行而需要读取的行数
- Extra:额外信息
- Using index:MySQL将会使用覆盖索引,以避免访问表
- Using where:MySQL服务器将在存储引擎检索后,再进行一次过滤
- Using temporary:MySQL对结果排序时会使用临时表
- Using filesort:对结果使用一个外部索引排序
五、SQL语句性能优化
- 查询时,能不要*就不用*,尽量写全字段名
- 大部分情况连接效率远大于子查询
- 多表连接时,尽量小表驱动大表,即小表 join 大表
- 在千万级分页时使用limit
- 对于经常使用的查询,可以开启缓存
- 多使用explain和profile分析查询语句
- 查看慢查询日志,找出执行时间长的sql语句优化