文章目录
- 基础篇
- 高级篇
- 第09章_性能分析工具的使用
- 第10章_索引优化与查询优化
- 1. 索引失效场景(案例)
- 2. 关联查询优化
- 3. 子查询优化
- 4. 排序优化
- 5. GROUP BY优化
- 6. 优化分页查询
- 7. 优先考虑覆盖索引
- 8. 如何给字符串添加索引
- 9. 索引下推 Index Condition Pushdown(ICP)
- 10. 普通索引 vs 唯一索引
- 11. 其它查询优化策略
- 12. 淘宝数据库,主键如何设计的?
基础篇
高级篇
第09章_性能分析工具的使用
1. 数据库服务器的优化步骤
当我们遇到数据库调优问题的时候,该如何思考呢?这里把思考的流程整理成下面这张图。
整个流程划分成了 观察(Show status) 和 行动(Action) 两个部分。字母 S 的部分代表观察(会使用相应的分析工具),字母 A 代表的部分是行动(对应分析可以采取的行动)。
2. 查看系统性能参数
在MySQL中,可以使用 SHOW STATUS 语句查询一些MySQL数据库服务器的 性能参数 、 执行频率 。
SHOW STATUS语句语法如下:
一些常用的性能参数如下:
• Connections:连接MySQL服务器的次数。
• Uptime:MySQL服务器的上线时间。
• Slow_queries:慢查询的次数。
• Innodb_rows_read:Select查询返回的行数
• Innodb_rows_inserted:执行INSERT操作插入的行数
• Innodb_rows_updated:执行UPDATE操作更新的行数
• Innodb_rows_deleted:执行DELETE操作删除的行数
• Com_select:查询操作的次数。
• Com_insert:插入操作的次数。对于批量插入的 INSERT 操作,只累加一次。
• Com_update:更新操作的次数。
• Com_delete:删除操作的次数。
3. 统计SQL的查询成本:last_query_cost
4. 定位执行慢的 SQL:慢查询日志
4.1 开启慢查询日志参数
4.2 查看慢查询数目
查询当前系统中有多少条慢查询记录
SHOW GLOBAL STATUS LIKE ‘%Slow_queries%’;
4.4 慢查询日志分析工具:mysqldumpslow
在生产环境中,如果要手工分析日志,查找、分析SQL,显然是个体力活,MySQL提供了日志分析工具
mysqldumpslow 。
查看mysqldumpslow的帮助信息
mysqldumpslow --help
4.5 关闭慢查询日志
5. 查看 SQL 执行成本:SHOW PROFILE
6. 分析查询语句:EXPLAIN
EXPLAIN小结:
- EXPLAIN不考虑各种Cache
- EXPLAIN不能显示MySQL在执行查询时所作的优化工作
- EXPLAIN不会告诉你关于触发器、存储过程的信息或用户自定义函数对查询的影响情况
- 部分统计信息是估算的,并非精确值
EXPLAIN各列的作用
调优重点关注 type、key_len、rows、Extra
列。
列名 | 含义 |
---|---|
id | 在一个大的查询语句(一条sql语句可能包含多个select)中每个SELECT关键字都对应一个 唯一的id。 1. id如果相同,可以认为是一组,从上往下顺序执行 2 .在所有组中,id值越大,优先级越高,越先执行。 3. 关注点:id号每个号码,表示一趟独立的查询, 一个sql的查询趟数越少越好 |
select_type | SELECT关键字对应的那个查询的类型。包括: simple、primary、union/dependent union/uncacheable union、subquery/dependent subquery/uncacheable subquery、union result、derived、materialized共11个值。 slelect_type的取值范围及含义如下: ![]() |
table | 表名。 不论我们的查询语句有多复杂,里边儿 包含了多少个表 ,到最后也是需要对每个表进行 单表访问 的,所以MySQL规定EXPLAIN语句输出的每条记录都对应着某个单表的访问方法,该条记录的table列代表着该表的表名(有时不是真实的表名字,可能是简称)。 |
partitions | 匹配的分区信息。 |
type | 针对单表的访问方法。type非常重要的性能分析列,枚举,共12个值,包括(从上到下的性能优化程度由高到低,如system性能最高、ALL最低): 1. system: 不需要扫描表,Mys ql内部有变量直接维护时 ,如对MyISAM引擎 EXPLAIN SELECT count(*) FROM t,内存中有一个变量维护着记录数计数器; 2. const: 当用主键或唯一二级索引列与常数进行等值匹配时 。 EXPLAIN SELECT * FROM s1 WHERE id = 10005; EXPLAIN SELECT * FROM s1 WHERE key1 > ‘a’ AND key1 < ‘b’;EXPLAIN SELECT * FROM s1 WHERE key1 > ‘a’ AND key1 < ‘b’; 3. eq_ref(结合ref列一起看): 连接查询时,若被驱动表是通过主键或唯一二级索引列等值匹配时(若主键为联合索引时,需要所有列均等值匹配)时 。EXPLAIN SELECT * FROM s1 INNER JOIN s2 ON s1.id = s2.id; 从执行计划的结果中可以看出,MySQL打算将s2作为驱动表,s1作为被驱动表,重点关注s1的访问方法是 eq_ref ,表明在访问s1表的时候可以 通过主键的等值匹配 来进行访问。4. ref: 普通二级索引与常量进行等值匹配时 。EXPLAIN SELECT * FROM s1 WHERE key1 = ‘a’; (ref列的值为const)5. fulltext:全文索引 6. ref_or_null : 普通二级索引与常量进行等值匹配,且索引列的值可能为NULL时 。EXPLAIN SELECT * FROM s1 WHERE key1 = ‘a’ OR key1 IS NULL; (ref列的值为const)7. index_merge : EXPLAIN SELECT * FROM s1 WHERE key1 = ‘a’ OR key3 = ‘a’; 从执行计划的 type 列的值是 index_merge 就可以看出,MySQL 打算使用索引合并的方式来执行对 s1 表的查询(key列的值为id_key1,id_key3)。 8. unique_subquery : 针对一些包含in 子查询的语句,如果查询优化器决定将in转换成exists子查询,且子查询可以使用到主键进行等值匹配时 。下面语句会讲in改为exists子查询,且在子查询中通过主键进行等值匹配,故其type值为unique_subquery:EXPLAIN SELECT * FROM s1 WHERE key2 IN (SELECT id FROM s2 where s1.key1 =s2.key1) OR key3 = ‘a’;9. index_subquery :EXPLAIN SELECT * FROM s1 WHERE common_field IN (SELECT key3 FROM s2 where s1.key1 = s2.key1) OR key3 = ‘a’; 10. range : 通过索引获取某些‘范围区间’的记录时 。EXPLAIN SELECT * FROM s1 WHERE key1 IN (‘a’, ‘b’, ‘c’); 或者 EXPLAIN SELECT * FROM s1 WHERE key1 > ‘a’ AND key1 < ‘b’;11. index : 当可以使用索引覆盖,但需要全表扫描全部的索引记录时 。由于我们为key_part1、key_part2和key_part3建立了联合索引,下面的sql where部分不会使用到该索引,但select 部分的key_part2与where部分的key_part3都在索引中,此时就会使用索引覆盖(即只查索引即可得到结果,无需回表查数据),此时type的值为index。EXPLAIN SELECT key_part2 FROM s1 WHERE key_part3 = ‘a’;12. ALL : 即全表扫描 。EXPLAIN SELECT * FROM s1;。小结: 结果值从最好到最坏依次是: system > const > eq_ref > ref > fulltext > ref_or_null > index_merge >unique_subquery > index_subquery > range > index > ALL 其中比较重要的几个提取出来(见上图中的蓝色)。SQL 性能优化的目标: 至少要达到 range 级别,要求是 ref 级别,最好是 consts级别 。(阿里巴巴开发手册要求)。 |
possible_keys | 可能用到的索引。 |
key | 实际上使用的索引。 |
ken_len | 实际使用到的索引长度(字节数)。 key_len的长度计算公式: 1. varchar(10)变长字段且允许NULL = 10 * ( character set:utf8=3,gbk=2,latin1=1)+1(NULL)+2(变长字段) 2. varchar(10)变长字段且不允许NULL = 10 * ( character set:utf8=3,gbk=2,latin1=1)+2(变长字段) 3. char(10)固定字段且允许NULL = 10 * ( character set:utf8=3,gbk=2,latin1=1)+1(NULL) 4. char(10)固定字段且不允许NULL = 10 * ( character set:utf8=3,gbk=2,latin1=1) |
ref | 当使用索引列等值查询时,与索引列进行等值匹配的对象信息。 |
rows | 预估的需要读取的记录条数。越小越好 |
filtered | 某个表经过搜索条件过滤后剩余记录条数的百分比。对于单表查询,filtered值没有什么参考意义。我们更关注在连接查询中驱动表对于的执行计划的filtered值,它决定了被驱动表要执行的次数(=rows*filtered) 如,对EXPLAIN SELECT * FROM s1 INNER JOIN s2 ON s1.id=s2.id and s1.common_field=‘a’, 输出的驱动表s1的rows为9000,filtered值=10,说明对驱动表s1通过s1.common_field='a’过滤后,得到9000*10%=900条记录,这900条记录都需要进一步从被驱动表s2中进行关联匹配,即900为被驱动表s2要执行的次数。 |
Extra | 用来说明一些额外的信息,包括不适合在其它列中显示但十分重要的额外信息,我们可以通过这些额外信息来更准确地理解mysql到底如何执行给定的查询语句 。典型常见的值举例如下:1. No tables used: 没有from时 。 EXPLAIN SELECT 1;2. Impossible WHERE: where 条件不可能成立时 。EXPLAIN SELECT * FROM s1 WHERE 1 != 1; 3. Using where: 当使用全表扫描来执行对某个表的查询,并且where条件有(只要有即可,还可能包含其他包含使用到索引的条件)针对该表的搜索条件时, 。EXPLAIN SELECT * FROM s1 WHERE common_field = ‘a’; 4. No matching min/max row: 当select 为min、max等统计函数,但数据库中没有满足where条件的记录(key1 = 'abcdefg')时 。EXPLAIN SELECT MIN(key1) FROM s1 WHERE key1 = ‘abcdefg’; 5. Using Index:· 使用覆盖索引时 。 EXPLAIN SELECT key1 FROM s1 WHERE key1 = ‘a’; 7. Using index condition: 使用索引条件下推时 。 EXPLAIN SELECT * FROM s1 WHERE key1 > ‘z’ AND key1 LIKE ‘%b’; 8. Using join buffer (Block Nested Loop): 在连接查询过程中,当被驱动表不能有效地利用索引时,MYSQL一般会为其分配一块名叫‘join_buffer’的内存块来加快查询速度,也就是我们所说的‘基于块的嵌套循环算法’ 。EXPLAIN SELECT * FROM s1 INNER JOIN s2 ON s1.common_field =s2.common_field; common_field没有建索引,因此该字段值为Using where, Using join buffer 9. Not exists: 当使用左外连接时,如果where子句中包含要求被驱动表的某个列(s2.id)等于NULL值的搜索条件(s2.id IS NULL),但那个列定义为NOT NULL时 。EXPLAIN SELECT * FROM s1 LEFT JOIN s2 ON s1.key1 = s2.key1 WHERE s2.id IS NULL;10. Using intersect(…) 、 Using union(…) 和 Using sort_union(…): 对应type=index_merge ,EXPLAIN SELECT * FROM s1 WHERE key1 = ‘a’ OR key3 = ‘a’; (结果显示using union(idx_key1, idx_key3); using where) 11. Zero limit: EXPLAIN SELECT * FROM s1 LIMIT 0 ; 11. NULL: 对结果集中的记录进行排序时可以利用到索引时 。EXPLAIN SELECT * FROM s1 ORDER BY key1 LIMIT 10; 12. Using filesort: 与NULL相反,排序操作无法使用到索引(common_field 没有定义索引)时,只能在内存中(记录较少时)或磁盘中(记录较多时)进行排序,MySQL将这两情况的排序统称为文件排序(filesort) , EXPLAIN SELECT * FROM s1 ORDER BY common_field LIMIT 10; 13. Using temporary: 使用到临时表时,一般用在 对distinct、group by、union等操作无法有效利用索引来完成时,此值需要尽量避免 。EXPLAIN SELECT DISTINCT common_field FROM s1; |
7. EXPLAIN的进一步使用
7.1 EXPLAIN四种输出格式
这里谈谈EXPLAIN的输出格式。EXPLAIN可以输出四种格式: 传统格式 , JSON格式 , TREE格式 以及 可
视化输出 。用户可以根据需要选择适用于自己的格式。
7.2 SHOW WARNINGS的使用
8. 分析优化器执行计划:trace
9. MySQL监控分析视图-sys schema
9.1 Sys schema视图摘要
9.2 Sys schema视图使用场景
第10章_索引优化与查询优化
1. 索引失效场景(案例)
1. 计算、函数、类型转换(自动或手动)导致索引失效
type为“ALL”,表示没有使用到索引,查询时间为 3.62 秒,查询效率较之前低很多。
2. 类型转换导致索引失效
3. 范围条件右边的列索引失效(范围匹配应该放到where最后)
create index idx_age_name_classid on student(age,name,classid);
4. 不等于(!= 或者<>)索引失效
5. is null可以使用索引,is not null无法使用索引
6. like以通配符%开头索引失效
7. OR 前后存在非索引的列,索引失效
8. 不同的 字符集 进行比较前需要进行 转换 会造成索引失效
统一使用utf8mb4( 5.5.3版本以上支持)兼容性更好,统一字符集可以避免由于字符集转换产生的乱码。不
同的 字符集 进行比较前需要进行 转换 会造成索引失效。
2. 关联查询优化
关联查询优化小结
:
保证被驱动表的JOIN字段已经创建了索引。
需要JOIN 的字段,数据类型保持绝对一致。
LEFT JOIN 时,选择小表作为驱动表, 大表作为被驱动表 。减少外层循环的次数。
INNER JOIN 时,MySQL会自动将 小结果集的表选为驱动表 。选择相信MySQL优化策略。
能够直接多表关联的尽量直接关联,不用子查询。(减少查询的趟数)。
不建议使用子查询,建议将子查询SQL拆开结合程序多次查询,或使用 JOIN 来代替子查询。
衍生表建不了索引。
什么叫作“小表”?
在决定哪个表做驱动表的时候,应该是两个表按照各自的条件过滤,过滤完成之后,计算参与join的各个字段的总数据量,数据量小的那个表,就是“小表”,应该作为驱动表。
2.1 内连接 inner join
SELECT SQL_NO_CACHE * FROM type
inner JOIN book ON type.card = book.card;
分三种情况
:
- 若两个表的连接条件字段
均创建了索引
,则MySQL优化器会自动选择小表作为驱动表,即小表驱动大表
- 若两个表的连接条件字段(type.card和book.card)
均未创建索引
,则两个表都用全表扫描,效率最低,此时MySQL优化器会同样选择 小表驱动大表
。- 若两个表的连接条件字段
只有其中一个创建了索引
,则MySQL优化器会自动选择创建了索引的表作为被驱动表
。
2.2 左外连接 left [outer] join
2.2.1 左外连接 left [outer] join 不带where 条件
EXPLAIN SELECT SQL_NO_CACHE * FROM type
LEFT JOIN book ON type.card = book.card;
在右表book的关联字段添加索引
ALTER TABLE book ADD INDEX Y ( card); #【被驱动表】,可以避免全表扫描,优化生效
条件用于确定如何从右表(被驱动表
)搜索行,左表(驱动表
)的数据一定全都有,所以 右表是我们的关键点,一定需要建立索引 。
例如,下方sql需要为book.card建立索引进行优化。
但为左表添加索引则不生效:
ALTER TABLE type
ADD INDEX X (card); #【驱动表】,无法避免全表扫描,优化失效
2.2.2 左外连接 left [outer] join 带where 条件
EXPLAIN SELECT SQL_NO_CACHE * FROM type
LEFT JOIN book ON type.card = book.card where type.s1=book.s1
;
添加where 条件后,MySQL优化器会将其优化为内连接,因此可能会选择被连接表 book作为驱动表。
2.2 Index Nested-Loop Join (NLJ) 、Simple Nested-Loop Join 和 Block Nested-Loop Join
2.2.1 Index Nested-Loop Join (NLJ)
EXPLAIN SELECT * FROM t1 STRAIGHT_JOIN
t2 ON (t1.a=t2.a);
如果直接使用join语句,MySQL优化器可能会选择表t1或t2作为驱动表,这样会影响我们分析SQL语句的执行过程。所以,为了便于分析执行过程中的性能问题,我改用 straight_join 让MySQL使用固定的连接方式执行查询,这样优化器只会按照我们指定的方式去join。在这个语句里,t1 是驱动表,t2是被驱动表。
两个结论:
1. 使用join语句,性能比强行拆成多个单表执行SQL语句的性能要好;
2. 如果使用join语句的话,需要让小表做驱动表。
2.2.2 Simple Nested-Loop Join
2.2.3 Block Nested-Loop Join
3. 子查询优化
MySQL从4.1版本开始支持子查询,使用子查询可以进行SELECT语句的嵌套查询,即一个SELECT查询的结果作为另一个SELECT语句的条件。 子查询可以一次性完成很多逻辑上需要多个步骤才能完成的SQL操作 。 子查询是 MySQL 的一项重要的功能,可以帮助我们通过一个 SQL 语句实现比较复杂的查询
。
但是,子查询的执行效率不高。原因:
① 执行子查询时,MySQL需要为内层查询语句的查询结果 建立一个临时表
,然后外层查询语句从临时中查询记录。查询完毕后,再撤销这些临时表
。这样会消耗过多的CPU和IO资源,产生大量的慢查询。
② 子查询的结果集存储的临时表,不论是内存临时表还是磁盘临时表都 不会存在索引
,所以查询性能会受到一定的影响。
③ 对于返回结果集比较大的子查询,其对查询性能的影响也就越大。
·在MySQL中,可以使用连接(JOIN)查询来替代子查询·。连接查询 ·不需要建立临时表 ·,其 ·速度比子查询要快· ,如果查询中使用索引的话,性能就会更好。
结论:尽量不要使用NOT IN 或者 NOT EXISTS,用LEFT JOIN xxx ON xx WHERE xx IS NULL替代
4. 排序优化
问题
:在 WHERE 条件字段上加索引,但是为什么在 ORDER BY 字段上还要加索引呢?
1优化建议1:
- SQL 中,可以在 WHERE 子句和 ORDER BY 子句中使用索引,目的是在 WHERE 子句中
避免全表扫描
,在 ORDER BY 子句避免使用 FileSort 排序
。当然,某些情况下全表扫描,或者 FileSort 排序不一定比索引慢。但总的来说,我们还是要避免,以提高查询效率。 - 尽量使用 Index 完成 ORDER BY 排序。如果 WHERE 和 ORDER BY 后面是相同的列就使用单索引列;如果不同就使用联合索引。
- 无法使用 Index 时,需要对 FileSort 方式进行调优。
结论
:
- 两个索引同时存在,mysql自动选择最优的方案。(对于这个例子,mysql选择
idx_age_stuno_name)。但是, 随着数据量的变化,选择的索引也会随之变化的 。 当【范围条件】和【group by 或者 order by】的字段出现二选一时,优先观察条件字段的过滤数量,如果过滤的数据足够多,而需要排序的数据并不多时,优先把索引放在范围字段上。反之亦然
。
5. GROUP BY优化
- group by 使用索引的原则几乎跟order by一致 ,group by 即使没有过滤条件用到索引,也可以直接使用索引。
- group by 先排序再分组,遵照索引建的最佳左前缀法则
- 当无法使用索引列,增大 max_length_for_sort_data 和 sort_buffer_size 参数的设置
- where效率高于having,能写在where限定的条件就不要写在having中了
- 减少使用order by,和业务沟通能不排序就不排序,或将排序放到程序端去做。Order by、group by、distinct这些语句较为耗费CPU,数据库的CPU资源是极其宝贵的。
- 包含了order by、group by、distinct这些查询的语句,where条件过滤出来的结果集请保持在1000行以内,否则SQL会很慢。
6. 优化分页查询
7. 优先考虑覆盖索引
7.1 什么是覆盖索引
简单说就是, 覆盖索引 指 索引列+主键包含 SELECT 到 FROM之间查询的列,此时通过索引即可获取所需列的数据,不需要再回表去读取行了
。
理解方式一
:索引是高效找到行的一个方法,但是一般数据库也能使用索引找到一个列的数据,因此它不必读取整个行。毕竟索引叶子节点存储了它们索引的数据;当能通过读取索引就可以得到想要的数据,那就不需要读取行了
。一个索引包含了满足查询结果的数据就叫做覆盖索引。
理解方式二
:非聚簇复合索引的一种形式,它包括在查询里的SELECT、JOIN和WHERE子句用到的所有列(即建索引的字段正好是覆盖查询条件中所涉及的字段)。
7.2 覆盖索引的利弊
好处
:- 避免Innodb表进行索引的二次查询(回表)
- 可以把随机IO变成顺序IO加快查询效率
弊端
:- 索引字段的维护 总是有代价的。因此,在建立冗余索引来支持覆盖索引时就需要权衡考虑了。这是业务DBA,或者称为业务数据架构师的工作。
8. 如何给字符串添加索引
对字符串,使用前缀索引,定义好合适的(区分度90%以上)长度,就可以做到既节省空间,又不用额外增加太多的查询成本。前面已经讲过区分度,区分度越高越好。因为区分度越高,意味着重复的键值越少。
前缀索引对覆盖索引的影响
使用前缀索引就用不上覆盖索引对查询性能的优化了
,这也是你在选择是否使用前缀索引时需要考虑的一个因素。
9. 索引下推 Index Condition Pushdown(ICP)
9.1 索引下推概念
将where条件的索引过滤处理,由server层下推到存储引擎层来处理,即索引下推
9.2 使用 索引下推的条件
① 只能用于二级索引(secondary index)
②explain显示的执行计划中type值(join 类型)为 range 、 ref 、 eq_ref 或者 ref_or_null 。
③ 并非全部where条件都可以用ICP筛选,如果where条件的字段不在索引列中,还是要读取整表的记录到server端做where过滤。
④ ICP可以用于MyISAM和InnnoDB存储引擎
⑤ MySQL 5.6版本的不支持分区表的ICP功能,5.7版本的开始支持。
⑥ 当SQL使用覆盖索引时,不支持ICP优化方法。
9.3 使用索引下推的加速效果
使用前,存储层多返回了需要被index filter过滤掉的整行记录
使用ICP后,直接就去掉了不满足index filter条件的记录,省去了他们回表和传递到server层的成本。
ICP的 加速效果 取决于在存储引擎内通过 ICP筛选 掉的数据的比例
10. 普通索引 vs 唯一索引
结论:建议使用普通索引
。
10.1 查询过程
查询过程 普通索引和唯一索引性能差别不大。
10.2 更新过程
建议 尽量选择普通索引
,普通索引
可以配合使用change buffer
,对于 数据量大
的表的更新优化还是很明显的。唯一索引无法使用change buffer,对数据量大的表的更新效果不佳。
什么是change buffer
为了说明普通索引和唯一索引对更新语句
性能的影响这个问题,介绍一下change buffer
。
当需要更新一个数据页时,如果数据页在内存中就直接更新,而如果这个数据页还没有在内存中的话,在不影响数据一致性的前提下, InooDB会将这些更新操作缓存在change buffer中
,这样就不需要从磁盘中读入这个数据页了。在下次查询需要访问这个数据页的时候,将数据页读入内存,然后执行change buffer中与这个页有关的操作。通过这种方式就能保证这个数据逻辑的正确性。
将change buffer中的操作应用到原数据页,得到最新结果的过程称为 merge
。除了 访问这个数据页 会触发merge外,系统有 后台线程会定期 merge。在 数据库正常关闭(shutdown) 的过程中,也会执行merge操作。
如果能够将更新操作先记录在change buffer, 减少读磁盘 ,语句的执行速度会得到明显的提升。而且,数据读入内存是需要占用 buffer pool 的,所以这种方式还能够 避免占用内存 ,提高内存利用率。
唯一索引的更新就不能使用change buffer ,实际上也只有普通索引可以使用。
change buffer的使用场景
- 普通索引和唯一索引应该怎么选择?其实,这两类索引在查询能力上是没差别的,主要考虑的是对 更新性能 的影响。所以,建议你 尽量选择普通索引 。
- 在实际使用中会发现, 普通索引 和 change buffer 的配合使用,对于 数据量大 的表的更新优化还是很明显的。
- 如果所有的更新后面,都马上 伴随着对这个记录的查询 ,那么你应该 关闭change buffer 。而在其他情况下,change buffer都能提升更新性能。
- 由于唯一索引用不上change buffer的优化机制,因此如果 业务可以接受 ,从性能角度出发建议优先考虑非唯一索引。但是如果"业务可能无法确保"的情况下,怎么处理呢?
- 首先, 业务正确性优先 。我们的前提是“业务代码已经保证不会写入重复数据”的情况下,讨论性能问题。如果业务不能保证,或者业务就是要求数据库来做约束,那么没得选,必须创建唯一索引。
这种情况下,本节的意义在于,如果碰上了大量插入数据慢、内存命中率低的时候,给你多提供一个排查思路。 - 然后,在一些“ 归档库 ”的场景,你是可以考虑使用唯一索引的。比如,线上数据只需要保留半年,然后历史数据保存在归档库。这时候,归档数据已经是确保没有唯一键冲突了。要提高归档效率,可以考虑把表里面的唯一索引改成普通索引。
- 首先, 业务正确性优先 。我们的前提是“业务代码已经保证不会写入重复数据”的情况下,讨论性能问题。如果业务不能保证,或者业务就是要求数据库来做约束,那么没得选,必须创建唯一索引。
11. 其它查询优化策略
11.1 EXISTS 和 IN 的区分
问题:
不太理解哪种情况下应该使用 EXISTS,哪种情况应该用 IN。选择的标准是看能否使用表的索引吗?
11.2 COUNT(*)与COUNT(具体字段)效率
问:在 MySQL 中统计数据表的行数,可以使用三种方式: SELECT COUNT(*) 、 SELECT COUNT(1) 和
SELECT COUNT(具体字段) ,使用这三者之间的查询效率是怎样的?
11.3 关于SELECT(*) --不推荐
在表查询中,建议明确字段,不要使用 * 作为查询的字段列表
,推荐使用SELECT <字段列表> 查询。原因:
① MySQL 在解析的过程中,会通过 查询数据字典 将"*"按序转换成所有列名,这会大大的耗费资源和时间。
② 无法使用 覆盖索引
11.4 LIMIT 1 对优化的影响
针对的是会扫描全表的 SQL 语句,如果你可以确定结果集只有一条,那么加上 LIMIT 1
的时候,当找到一条结果的时候就不会继续扫描了,这样会加快查询速度。
如果数据表已经对字段建立了唯一索引,那么可以通过索引进行查询,不会全表扫描的话,就不需要加上 LIMIT 1
了。
11.5 多使用COMMIT
只要有可能,在程序中尽量多使用 COMMIT,这样程序的性能得到提高,需求也会因为 COMMIT 所释放的资源而减少。
COMMIT 所释放的资源:
- 回滚段上用于恢复数据的信息
- 被程序语句获得的锁
- redo / undo log buffer 中的空间
- 管理上述 3 种资源中的内部花费
12. 淘宝数据库,主键如何设计的?
不建议用自增做主键
:不建议业务字段做主键
:作为项目设计的技术人员,我们谁也无法预测在项目的整个生命周期中,哪个业务字段会因为项目的业务需求而有重复,或者重用之类的情况出现。主键设计至少应该是全局唯一且是单调递增
。全局唯一保证在各系统之间都是唯一的,单调
递增是希望插入时不影响数据库性能。
推荐使用有序的UUID
作为业务主键。
默认的UUID是无序的,因为它将时间低位放在最前面,而这部分的数据是一直在变化的,并且是无序。
通过将时间高低位互换,就变成了有序的UUID。
MySQL 8.0可以更换时间低位和
时间高位的存储方式,这样UUID就是有序的UUID了。
MySQL 8.0还解决了UUID存在的空间占用的问题,除去了UUID字符串中无意义的"-"字符串,并且将字符串用二进制类型保存,这样存储空间降低为了16字节。可以通过MySQL8.0提供的uuid_to_bin函数实现上述功能,同样的,MySQL也提供了bin_to_uuid函数进行转化:
SET @uuid = UUID();
SELECT @uuid,uuid_to_bin(@uuid),uuid_to_bin(@uuid,TRUE);
以上MySQL 8.0 通过函数uuid_to_bin(@uuid,true)将UUID转化为有序UUID了。全局唯一 + 单调递增,这不就是我们想要的主键!
如果不是MySQL8.0 肿么办?手动赋值字段做主键!