Skip to content

Commit f7f434d

Browse files
committed
[docs add] MySQL Query Cache(查询缓存) 详解
1 parent 9e476c1 commit f7f434d

File tree

6 files changed

+212
-8
lines changed

6 files changed

+212
-8
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,7 @@ JVM 这部分内容主要参考 [JVM 虚拟机规范-Java8 ](https://docs.oracle
210210
- [MySQL三大日志(binlog、redo log和undo log)详解](./docs/database/mysql/mysql-logs.md)
211211
- [InnoDB 存储引擎对 MVCC 的实现](./docs/database/mysql/innodb-implementation-of-mvcc.md)
212212
- [SQL 语句在 MySQL 中的执行过程](./docs/database/mysql/how-sql-executed-in-mysql.md)
213+
- [MySQL 查询缓存详解](./docs/database/mysql/mysql-query-cache.md)
213214
- [MySQL执行计划分析](./docs/database/mysql/mysql-query-execution-plan.md)
214215
- [MySQL自增主键一定是连续的吗](./docs/database/mysql/mysql-auto-increment-primary-key-continuous.md)
215216
- [MySQL 时间类型数据存储建议](./docs/database/mysql/some-thoughts-on-database-storage-time.md)

docs/.vuepress/config.ts

+2-7
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,8 @@ export default defineUserConfig({
4343
s.parentNode.insertBefore(hm, s);
4444
})();`,
4545
],
46-
[
47-
"link",
48-
{
49-
rel: "stylesheet",
50-
href: "/iconfont/iconfont.css",
51-
},
52-
],
46+
["link", { rel: "stylesheet", href: "/iconfont/iconfont.css" }],
47+
["link", { rel: "icon", href: "/favicon.ico" }],
5348
],
5449
locales: {
5550
"/": {

docs/.vuepress/sidebar.ts

+1
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,7 @@ export const sidebarConfig = sidebar({
264264
"transaction-isolation-level",
265265
"innodb-implementation-of-mvcc",
266266
"how-sql-executed-in-mysql",
267+
"mysql-query-cache",
267268
"mysql-query-execution-plan",
268269
"mysql-auto-increment-primary-key-continuous",
269270
"some-thoughts-on-database-storage-time",
+206
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
---
2+
title: MySQL 查询缓存详解
3+
category: 数据库
4+
tag:
5+
- MySQL
6+
head:
7+
- - meta
8+
- name: keywords
9+
content: MySQL查询缓存,MySQL缓存机制中的内存管理
10+
- - meta
11+
- name: description
12+
content: 为了提高完全相同的查询语句的响应速度,MySQL Server 会对查询语句进行 Hash 计算得到一个 Hash 值。MySQL Server 不会对 SQL 做任何处理,SQL 必须完全一致 Hash 值才会一样。得到 Hash 值之后,通过该 Hash 值到查询缓存中匹配该查询的结果。MySQL 中的查询缓存虽然能够提升数据库的查询性能,但是查询同时也带来了额外的开销,每次查询后都要做一次缓存操作,失效后还要销毁。
13+
---
14+
15+
缓存是一个有效且实用的系统性能优化的手段,不论是操作系统还是各种软件和网站或多或少都用到了缓存。
16+
17+
然而,有经验的 DBA 都建议生产环境中把 MySQL 自带的 Query Cache(查询缓存)给关掉。而且,从 MySQL 5.7.20 开始,就已经默认弃用查询缓存了。在 MySQL 8.0及之后,更是直接删除了查询缓存的功能。
18+
19+
这又是为什么呢?查询缓存真就这么鸡肋么?
20+
21+
带着如下几个问题,我们正式进入本文。
22+
23+
- MySQL 查询缓存是什么?适用范围?
24+
- MySQL 缓存规则是什么?
25+
- MySQL 缓存的优缺点是什么?
26+
- MySQL 缓存对性能有什么影响?
27+
28+
## MySQL 查询缓存介绍
29+
30+
MySQL体系架构如下图所示:
31+
32+
![](https://oss.javaguide.cn/github/javaguide/mysql/mysql-architecture.png)
33+
34+
为了提高完全相同的查询语句的响应速度,MySQL Server 会对查询语句进行 Hash 计算得到一个 Hash 值。MySQL Server 不会对 SQL 做任何处理,SQL 必须完全一致 Hash 值才会一样。得到 Hash 值之后,通过该 Hash 值到查询缓存中匹配该查询的结果。
35+
36+
- 如果匹配(命中),则将查询的结果集直接返回给客户端,不必再解析、执行查询。
37+
- 如果没有匹配(未命中),则将 Hash 值和结果集保存在查询缓存中,以便以后使用。
38+
39+
也就是说,**一个查询语句(select)到了 MySQL Server 之后,会先到查询缓存看看,如果曾经执行过的话,就直接返回结果集给客户端。**
40+
41+
![](https://oss.javaguide.cn/javaguide/13526879-3037b144ed09eb88.png)
42+
43+
## MySQL 查询缓存管理和配置
44+
45+
通过 `show variables like '%query_cache%'`命令可以查看查询缓存相关的信息。
46+
47+
8.0 版本之前的话,打印的信息可能是下面这样的:
48+
49+
```bash
50+
mysql> show variables like '%query_cache%';
51+
+------------------------------+---------+
52+
| Variable_name | Value |
53+
+------------------------------+---------+
54+
| have_query_cache | YES |
55+
| query_cache_limit | 1048576 |
56+
| query_cache_min_res_unit | 4096 |
57+
| query_cache_size | 599040 |
58+
| query_cache_type | ON |
59+
| query_cache_wlock_invalidate | OFF |
60+
+------------------------------+---------+
61+
6 rows in set (0.02 sec)
62+
```
63+
64+
8.0 以及之后版本之后,打印的信息是下面这样的:
65+
66+
```bash
67+
mysql> show variables like '%query_cache%';
68+
+------------------+-------+
69+
| Variable_name | Value |
70+
+------------------+-------+
71+
| have_query_cache | NO |
72+
+------------------+-------+
73+
1 row in set (0.01 sec)
74+
```
75+
76+
我们这里对 8.0 版本之前`show variables like '%query_cache%';`命令打印出来的信息进行解释。
77+
78+
- **`have_query_cache`** 该 MySQL Server 是否支持查询缓存,如果是 YES 表示支持,否则则是不支持。
79+
- **`query_cache_limit`**MySQL 查询缓存的最大查询结果,查询结果大于该值时不会被缓存。
80+
- **`query_cache_min_res_unit`**查询缓存分配的最小块的大小(字节)。当查询进行的时候,MySQL 把查询结果保存在查询缓存中,但如果要保存的结果比较大,超过 `query_cache_min_res_unit` 的值 ,这时候 MySQL 将一边检索结果,一边进行保存结果,也就是说,有可能在一次查询中,MySQL 要进行多次内存分配的操作。适当的调节 `query_cache_min_res_unit` 可以优化内存。
81+
- **`query_cache_size`**为缓存查询结果分配的内存的数量,单位是字节,且数值必须是 1024 的整数倍。默认值是 0,即禁用查询缓存。
82+
- **`query_cache_type`**设置查询缓存类型,默认为 ON。设置 GLOBAL 值可以设置后面的所有客户端连接的类型。客户端可以设置 SESSION 值以影响他们自己对查询缓存的使用。
83+
- **`query_cache_wlock_invalidate`** :如果某个表被锁住,是否返回缓存中的数据,默认关闭,也是建议的。
84+
85+
`query_cache_type` 可能的值(修改 `query_cache_type` 需要重启 MySQL Server):
86+
87+
- 0 或 OFF:关闭查询功能。
88+
- 1 或 ON:开启查询缓存功能,但不缓存 `Select SQL_NO_CACHE` 开头的查询。
89+
- 2 或 DEMAND:开启查询缓存功能,但仅缓存 `Select SQL_CACHE` 开头的查询。
90+
91+
**建议**
92+
93+
- `query_cache_size`不建议设置的过大。过大的空间不但挤占实例其他内存结构的空间,而且会增加在缓存中搜索的开销。建议根据实例规格,初始值设置为10MB到100MB之间的值,而后根据运行使用情况调整。
94+
- 建议通过调整 `query_cache_size` 的值来开启、关闭查询缓存,因为修改`query_cache_type` 参数需要重启 MySQL Server 生效。
95+
96+
8.0 版本之前,`my.cnf` 加入以下配置,重启 MySQL 开启查询缓存
97+
98+
```properties
99+
query_cache_type=1
100+
query_cache_size=600000
101+
```
102+
103+
或者,MySQL 执行以下命令也可以开启查询缓存
104+
105+
```properties
106+
set global query_cache_type=1;
107+
set global query_cache_size=600000;
108+
```
109+
110+
手动清理缓存可以使用下面三个 SQL:
111+
112+
- `flush query cache;` :清理查询缓存内存碎片。
113+
- `reset query cache;`:从查询缓存中移除所有查询。
114+
- `flush tables;` 关闭所有打开的表,同时该操作会清空查询缓存中的内容。
115+
116+
## MySQL 缓存机制
117+
118+
### 缓存规则
119+
120+
- 查询缓存会将查询语句和结果集保存到内存(一般是 key-value 的形式,key 是查询语句,value 是查询的结果集),下次再查直接从内存中取。
121+
- 缓存的结果是通过 sessions 共享的,所以一个 client 查询的缓存结果,另一个 client 也可以使用。
122+
- SQL 必须完全一致才会导致查询缓存命中(大小写、空格、使用的数据库、协议版本、字符集等必须一致)。检查查询缓存时,MySQL Server 不会对 SQL 做任何处理,它精确的使用客户端传来的查询。
123+
- 不缓存查询中的子查询结果集,仅缓存查询最终结果集。
124+
- 不确定的函数将永远不会被缓存, 比如 `now()``curdate()``last_insert_id()``rand()` 等。
125+
- 不缓存产生告警(Warnings)的查询。
126+
- 太大的结果集不会被缓存 (< query_cache_limit)。
127+
- 如果查询中包含任何用户自定义函数、存储函数、用户变量、临时表、MySQL 库中的系统表,其查询结果也不会被缓存。
128+
- 缓存建立之后,MySQL 的查询缓存系统会跟踪查询中涉及的每张表,如果这些表(数据或结构)发生变化,那么和这张表相关的所有缓存数据都将失效。
129+
- MySQL 缓存在分库分表环境下是不起作用的。
130+
- 不缓存使用 `SQL_NO_CACHE` 的查询。
131+
- ......
132+
133+
查询缓存 `SELECT` 选项示例:
134+
135+
```sql
136+
SELECT SQL_CACHE id, name FROM customer;# 会缓存
137+
SELECT SQL_NO_CACHE id, name FROM customer;# 不会缓存
138+
```
139+
140+
### 缓存机制中的内存管理
141+
142+
查询缓存是完全存储在内存中的,所以在配置和使用它之前,我们需要先了解它是如何使用内存的。
143+
144+
MySQL 查询缓存使用内存池技术,自己管理内存释放和分配,而不是通过操作系统。内存池使用的基本单位是变长的 block, 用来存储类型、大小、数据等信息。一个结果集的缓存通过链表把这些 block 串起来。block 最短长度为 `query_cache_min_res_unit`
145+
146+
当服务器启动的时候,会初始化缓存需要的内存,是一个完整的空闲块。当查询结果需要缓存的时候,先从空闲块中申请一个数据块为参数 `query_cache_min_res_unit` 配置的空间,即使缓存数据很小,申请数据块也是这个,因为查询开始返回结果的时候就分配空间,此时无法预知结果多大。
147+
148+
分配内存块需要先锁住空间块,所以操作很慢,MySQL 会尽量避免这个操作,选择尽可能小的内存块,如果不够,继续申请,如果存储完时有空余则释放多余的。
149+
150+
但是如果并发的操作,余下的需要回收的空间很小,小于 `query_cache_min_res_unit`,不能再次被使用,就会产生碎片。
151+
152+
## MySQL 查询缓存的优缺点
153+
154+
**优点:**
155+
156+
- 查询缓存的查询,发生在 MySQL 接收到客户端的查询请求、查询权限验证之后和查询 SQL 解析之前。也就是说,当 MySQL 接收到客户端的查询 SQL 之后,仅仅只需要对其进行相应的权限验证之后,就会通过查询缓存来查找结果,甚至都不需要经过 Optimizer 模块进行执行计划的分析优化,更不需要发生任何存储引擎的交互。
157+
- 由于查询缓存是基于内存的,直接从内存中返回相应的查询结果,因此减少了大量的磁盘 I/O 和 CPU 计算,导致效率非常高。
158+
159+
**缺点:**
160+
161+
- MySQL 会对每条接收到的 SELECT 类型的查询进行 Hash 计算,然后查找这个查询的缓存结果是否存在。虽然 Hash 计算和查找的效率已经足够高了,一条查询语句所带来的开销可以忽略,但一旦涉及到高并发,有成千上万条查询语句时,hash 计算和查找所带来的开销就必须重视了。
162+
- 查询缓存的失效问题。如果表的变更比较频繁,则会造成查询缓存的失效率非常高。表的变更不仅仅指表中的数据发生变化,还包括表结构或者索引的任何变化。
163+
- 查询语句不同,但查询结果相同的查询都会被缓存,这样便会造成内存资源的过度消耗。查询语句的字符大小写、空格或者注释的不同,查询缓存都会认为是不同的查询(因为他们的 Hash 值会不同)。
164+
- 相关系统变量设置不合理会造成大量的内存碎片,这样便会导致查询缓存频繁清理内存。
165+
166+
## MySQL 查询缓存对性能的影响
167+
168+
在 MySQL Server 中打开查询缓存对数据库的读和写都会带来额外的消耗:
169+
170+
- 读查询开始之前必须检查是否命中缓存。
171+
- 如果读查询可以缓存,那么执行完查询操作后,会查询结果和查询语句写入缓存。
172+
- 当向某个表写入数据的时候,必须将这个表所有的缓存设置为失效,如果缓存空间很大,则消耗也会很大,可能使系统僵死一段时间,因为这个操作是靠全局锁操作来保护的。
173+
- 对 InnoDB 表,当修改一个表时,设置了缓存失效,但是多版本特性会暂时将这修改对其他事务屏蔽,在这个事务提交之前,所有查询都无法使用缓存,直到这个事务被提交,所以长时间的事务,会大大降低查询缓存的命中。
174+
175+
## 总结
176+
177+
MySQL 中的查询缓存虽然能够提升数据库的查询性能,但是查询同时也带来了额外的开销,每次查询后都要做一次缓存操作,失效后还要销毁。
178+
179+
查询缓存是一个适用较少情况的缓存机制。如果你的应用对数据库的更新很少,那么查询缓存将会作用显著。比较典型的如博客系统,一般博客更新相对较慢,数据表相对稳定不变,这时候查询缓存的作用会比较明显。
180+
181+
简单总结一下查询缓存的适用场景:
182+
183+
- 表数据修改不频繁、数据较静态。
184+
- 查询(Select)重复度高。
185+
- 查询结果集小于 1 MB。
186+
187+
对于一个更新频繁的系统来说,查询缓存缓存的作用是很微小的,在某些情况下开启查询缓存会带来性能的下降。
188+
189+
简单总结一下查询缓存不适用的场景:
190+
191+
- 表中的数据、表结构或者索引变动频繁
192+
- 重复的查询很少
193+
- 查询的结果集很大
194+
195+
《高性能 MySQL》这样写到:
196+
197+
> 根据我们的经验,在高并发压力环境中查询缓存会导致系统性能的下降,甚至僵死。如果你一 定要使用查询缓存,那么不要设置太大内存,而且只有在明确收益的时候才使用(数据库内容修改次数较少)。
198+
199+
**确实是这样的!实际项目中,更建议使用本地缓存(比如 Caffeine)或者分布式缓存(比如Redis) ,性能更好,更通用一些。**
200+
201+
## 参考
202+
203+
- 《高性能 MySQL》
204+
- MySQL缓存机制:https://zhuanlan.zhihu.com/p/55947158
205+
- RDS MySQL查询缓存(Query Cache)的设置和使用 - 阿里元云数据库 RDS 文档:https://help.aliyun.com/document_detail/41717.html
206+
- 8.10.3 The MySQL Query Cache - MySQL 官方文档:https://dev.mysql.com/doc/refman/5.7/en/query-cache.html

docs/database/redis/redis-data-structures-01.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -394,7 +394,7 @@ Sorted Set 类似于 Set,但和 Set 相比,Sorted Set 增加了一个权重
394394
| ZSCORE key member | 获取指定有序集合中指定元素的 score 值 |
395395
| ZINTERSTORE destination numkeys key1 key2 ... | 将给定所有有序集合的交集存储在 destination 中,对相同元素对应的 score 值进行 SUM 聚合操作,numkeys 为集合数量 |
396396
| ZUNIONSTORE destination numkeys key1 key2 ... | 求并集,其它和 ZINTERSTORE 类似 |
397-
| ZDIFF destination numkeys key1 key2 ... | 求差集,其它和 ZINTERSTORE 类似 |
397+
| ZDIFFSTORE destination numkeys key1 key2 ... | 求差集,其它和 ZINTERSTORE 类似 |
398398
| ZRANGE key start end | 获取指定有序集合 start 和 end 之间的元素(score 从低到高) |
399399
| ZREVRANGE key start end | 获取指定有序集合 start 和 end 之间的元素(score 从高到底) |
400400
| ZREVRANK key member | 获取指定有序集合中指定元素的排名(score 从大到小排序) |

docs/home.md

+1
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,7 @@ JVM 这部分内容主要参考 [JVM 虚拟机规范-Java8 ](https://docs.oracle
211211
- [MySQL三大日志(binlog、redo log和undo log)详解](./database/mysql/mysql-logs.md)
212212
- [InnoDB 存储引擎对 MVCC 的实现](./database/mysql/innodb-implementation-of-mvcc.md)
213213
- [SQL 语句在 MySQL 中的执行过程](./database/mysql/how-sql-executed-in-mysql.md)
214+
- [MySQL 查询缓存详解](./database/mysql/mysql-query-cache.md)
214215
- [MySQL执行计划分析](./database/mysql/mysql-query-execution-plan.md)
215216
- [MySQL自增主键一定是连续的吗](./database/mysql/mysql-auto-increment-primary-key-continuous.md)
216217
- [MySQL 时间类型数据存储建议](./database/mysql/some-thoughts-on-database-storage-time.md)

0 commit comments

Comments
 (0)