From 846bd3b5fe8f20ea1ba795070b17b0c16574d301 Mon Sep 17 00:00:00 2001 From: dunwu Date: Fri, 18 Dec 2020 20:51:53 +0800 Subject: [PATCH] update docs --- docs/sql/mysql/mysql-optimization.md | 112 ++++++++++++++------------- docs/sql/mysql/mysql-transaction.md | 79 ++++++++++--------- 2 files changed, 101 insertions(+), 90 deletions(-) diff --git a/docs/sql/mysql/mysql-optimization.md b/docs/sql/mysql/mysql-optimization.md index 1a05efa..225100b 100644 --- a/docs/sql/mysql/mysql-optimization.md +++ b/docs/sql/mysql/mysql-optimization.md @@ -2,29 +2,29 @@ -- [一、数据结构优化](#一数据结构优化) - - [数据类型优化](#数据类型优化) - - [表设计](#表设计) - - [范式和反范式](#范式和反范式) - - [索引优化](#索引优化) -- [二、SQL 优化](#二sql-优化) - - [优化 COUNT() 查询](#优化-count-查询) - - [优化关联查询](#优化关联查询) - - [优化 GROUP BY 和 DISTINCT](#优化-group-by-和-distinct) - - [优化 LIMIT](#优化-limit) - - [优化 UNION](#优化-union) - - [优化查询方式](#优化查询方式) -- [三、EXPLAIN](#三explain) -- [四、optimizer trace](#四optimizer-trace) -- [参考资料](#参考资料) +- [1. 数据结构优化](#1-数据结构优化) + - [1.1. 数据类型优化](#11-数据类型优化) + - [1.2. 表设计](#12-表设计) + - [1.3. 范式和反范式](#13-范式和反范式) + - [1.4. 索引优化](#14-索引优化) +- [2. SQL 优化](#2-sql-优化) + - [2.1. 优化 `COUNT()` 查询](#21-优化-count-查询) + - [2.2. 优化关联查询](#22-优化关联查询) + - [2.3. 优化 `GROUP BY` 和 `DISTINCT`](#23-优化-group-by-和-distinct) + - [2.4. 优化 `LIMIT`](#24-优化-limit) + - [2.5. 优化 UNION](#25-优化-union) + - [2.6. 优化查询方式](#26-优化查询方式) +- [3. 执行计划(`EXPLAIN`)](#3-执行计划explain) +- [4. optimizer trace](#4-optimizer-trace) +- [5. 参考资料](#5-参考资料) -## 一、数据结构优化 +## 1. 数据结构优化 良好的逻辑设计和物理设计是高性能的基石。 -### 数据类型优化 +### 1.1. 数据类型优化 #### 数据类型优化基本原则 @@ -43,7 +43,7 @@ - 应该尽量避免用字符串类型作为标识列,因为它们很消耗空间,并且通常比数字类型慢。对于 `MD5`、`SHA`、`UUID` 这类随机字符串,由于比较随机,所以可能分布在很大的空间内,导致 `INSERT` 以及一些 `SELECT` 语句变得很慢。 - 如果存储 UUID ,应该移除 `-` 符号;更好的做法是,用 `UNHEX()` 函数转换 UUID 值为 16 字节的数字,并存储在一个 `BINARY(16)` 的列中,检索时,可以通过 `HEX()` 函数来格式化为 16 进制格式。 -### 表设计 +### 1.2. 表设计 应该避免的设计问题: @@ -52,7 +52,7 @@ - **枚举** - 尽量不要用枚举,因为添加和删除字符串(枚举选项)必须使用 `ALTER TABLE`。 - 尽量避免 `NULL` -### 范式和反范式 +### 1.3. 范式和反范式 **范式化目标是尽量减少冗余,而反范式化则相反**。 @@ -68,7 +68,7 @@ 在真实世界中,很少会极端地使用范式化或反范式化。实际上,应该权衡范式和反范式的利弊,混合使用。 -### 索引优化 +### 1.4. 索引优化 > 索引优化应该是查询性能优化的最有效手段。 > @@ -97,21 +97,21 @@ - **覆盖索引** - **自增字段作主键** -## 二、SQL 优化 +## 2. SQL 优化 -SQL 优化后,可以通过执行计划(`EXPLAIN`)来查看优化效果。 +使用 `EXPLAIN` 命令查看当前 SQL 是否使用了索引,优化后,再通过执行计划(`EXPLAIN`)来查看优化效果。 SQL 优化基本思路: - **只返回必要的列** - 最好不要使用 `SELECT *` 语句。 -- **只返回必要的行** - 使用 WHERE 语句进行查询过滤,有时候也需要使用 LIMIT 语句来限制返回的数据。 +- **只返回必要的行** - 使用 `WHERE` 子查询语句进行过滤查询,有时候也需要使用 `LIMIT` 语句来限制返回的数据。 - **缓存重复查询的数据** - 应该考虑在客户端使用缓存,尽量不要使用 Mysql 服务器缓存(存在较多问题和限制)。 - **使用索引来覆盖查询** -### 优化 COUNT() 查询 +### 2.1. 优化 `COUNT()` 查询 `COUNT()` 有两种作用: @@ -135,7 +135,7 @@ FROM world.city WHERE id <= 5; 有时候某些业务场景并不需要完全精确的统计值,可以用近似值来代替,`EXPLAIN` 出来的行数就是一个不错的近似值,而且执行 `EXPLAIN` 并不需要真正地去执行查询,所以成本非常低。通常来说,执行 `COUNT()` 都需要扫描大量的行才能获取到精确的数据,因此很难优化,MySQL 层面还能做得也就只有覆盖索引了。如果不还能解决问题,只有从架构层面解决了,比如添加汇总表,或者使用 Redis 这样的外部缓存系统。 -### 优化关联查询 +### 2.2. 优化关联查询 在大数据场景下,表与表之间通过一个冗余字段来关联,要比直接使用 `JOIN` 有更好的性能。 @@ -172,11 +172,11 @@ while(outer_row) { 可以看到,最外层的查询是根据`A.xx`列来查询的,`A.c`上如果有索引的话,整个关联查询也不会使用。再看内层的查询,很明显`B.c`上如果有索引的话,能够加速查询,因此只需要在关联顺序中的第二张表的相应列上创建索引即可。 -### 优化 GROUP BY 和 DISTINCT +### 2.3. 优化 `GROUP BY` 和 `DISTINCT` Mysql 优化器会在内部处理的时候相互转化这两类查询。它们都**可以使用索引来优化,这也是最有效的优化方法**。 -### 优化 LIMIT +### 2.4. 优化 `LIMIT` 当需要分页操作时,通常会使用 `LIMIT` 加上偏移量的办法实现,同时加上合适的 `ORDER BY` 字句。**如果有对应的索引,通常效率会不错,否则,MySQL 需要做大量的文件排序操作**。 @@ -209,13 +209,13 @@ SELECT id FROM t WHERE id > 10000 LIMIT 10; 其他优化的办法还包括使用预先计算的汇总表,或者关联到一个冗余表,冗余表中只包含主键列和需要做排序的列。 -### 优化 UNION +### 2.5. 优化 UNION MySQL 总是通过创建并填充临时表的方式来执行 `UNION` 查询。因此很多优化策略在`UNION`查询中都没有办法很好的时候。经常需要手动将`WHERE`、`LIMIT`、`ORDER BY`等字句“下推”到各个子查询中,以便优化器可以充分利用这些条件先优化。 除非确实需要服务器去重,否则就一定要使用`UNION ALL`,如果没有`ALL`关键字,MySQL 会给临时表加上`DISTINCT`选项,这会导致整个临时表的数据做唯一性检查,这样做的代价非常高。当然即使使用 ALL 关键字,MySQL 总是将结果放入临时表,然后再读出,再返回给客户端。虽然很多时候没有这个必要,比如有时候可以直接把每个子查询的结果返回给客户端。 -### 优化查询方式 +### 2.6. 优化查询方式 #### 切分大查询 @@ -256,11 +256,11 @@ SELECT * FROM tag_post WHERE tag_id=1234; SELECT * FROM post WHERE post.id IN (123,456,567,9098,8904); ``` -## 三、EXPLAIN +## 3. 执行计划(`EXPLAIN`) -如何检验修改后的 SQL 确实有优化效果?这就需要用到执行计划(`EXPLAIN`)。 +如何判断当前 SQL 是否使用了索引?如何检验修改后的 SQL 确实有优化效果? -使用执行计划 `EXPLAIN` 用来分析 `SELECT` 查询效率,开发人员可以通过分析 `EXPLAIN` 结果来优化查询语句。 +在 SQL 中,可以通过执行计划(`EXPLAIN`)分析 `SELECT` 查询效率。 ```sql mysql> explain select * from user_info where id = 2\G @@ -280,30 +280,36 @@ possible_keys: PRIMARY 1 row in set, 1 warning (0.00 sec) ``` -各列含义如下: +`EXPLAIN` 参数说明: -- id: SELECT 查询的标识符. 每个 SELECT 都会自动分配一个唯一的标识符. -- select_type: SELECT 查询的类型. - - SIMPLE, 表示此查询不包含 UNION 查询或子查询 - - PRIMARY, 表示此查询是最外层的查询 - - UNION, 表示此查询是 UNION 的第二或随后的查询 - - DEPENDENT UNION, UNION 中的第二个或后面的查询语句, 取决于外面的查询 - - UNION RESULT, UNION 的结果 - - SUBQUERY, 子查询中的第一个 SELECT - - DEPENDENT SUBQUERY: 子查询中的第一个 SELECT, 取决于外面的查询. 即子查询依赖于外层查询的结果. -- table: 查询的是哪个表 -- partitions: 匹配的分区 -- type: join 类型 -- possible_keys: 此次查询中可能选用的索引 -- key: 此次查询中确切使用到的索引. -- ref: 哪个字段或常数与 key 一起被使用 -- rows: 显示此查询一共扫描了多少行. 这个是一个估计值. -- filtered: 表示此查询条件所过滤的数据的百分比 -- extra: 额外的信息 +- `id`: SELECT 查询的标识符. 每个 SELECT 都会自动分配一个唯一的标识符. +- `select_type` ⭐ :SELECT 查询的类型. + - `SIMPLE`:表示此查询不包含 UNION 查询或子查询 + - `PRIMARY`:表示此查询是最外层的查询 + - `UNION`:表示此查询是 UNION 的第二或随后的查询 + - `DEPENDENT UNION`:UNION 中的第二个或后面的查询语句, 取决于外面的查询 + - `UNION RESULT`:UNION 的结果 + - `SUBQUERY`:子查询中的第一个 SELECT + - `DEPENDENT SUBQUERY`: 子查询中的第一个 SELECT, 取决于外面的查询. 即子查询依赖于外层查询的结果. +- `table`: 查询的是哪个表,如果给表起别名了,则显示别名。 +- `partitions`:匹配的分区 +- `type` ⭐:表示从表中查询到行所执行的方式,查询方式是 SQL 优化中一个很重要的指标,结果值从好到差依次是:system > const > eq_ref > ref > range > index > ALL。 + - `system`/`const`:表中只有一行数据匹配,此时根据索引查询一次就能找到对应的数据。如果是 B + 树索引,我们知道此时索引构造成了多个层级的树,当查询的索引在树的底层时,查询效率就越低。const 表示此时索引在第一层,只需访问一层便能得到数据。 + - `eq_ref`:使用唯一索引扫描,常见于多表连接中使用主键和唯一索引作为关联条件。 + - `ref`:非唯一索引扫描,还可见于唯一索引最左原则匹配扫描。 + - `range`:索引范围扫描,比如,<,>,between 等操作。 + - `index`:索引全表扫描,此时遍历整个索引树。 + - `ALL`:表示全表扫描,需要遍历全表来找到对应的行。 +- `possible_keys`:此次查询中可能选用的索引。 +- `key` ⭐:此次查询中实际使用的索引。 +- `ref`:哪个字段或常数与 key 一起被使用。 +- `rows` ⭐:显示此查询一共扫描了多少行,这个是一个估计值。 +- `filtered`:表示此查询条件所过滤的数据的百分比。 +- `extra`:额外的信息。 > 更多内容请参考:[MySQL 性能优化神器 Explain 使用分析](https://segmentfault.com/a/1190000008131735) -## 四、optimizer trace +## 4. optimizer trace 在 MySQL 5.6 及之后的版本中,我们可以使用 optimizer trace 功能查看优化器生成执行计划的整个过程。有了这个功能,我们不仅可以了解优化器的选择过程,更可以了解每一个执行环节的成本,然后依靠这些信息进一步优化查询。 @@ -316,7 +322,7 @@ SELECT * FROM information_schema.OPTIMIZER_TRACE; SET optimizer_trace="enabled=off"; ``` -## 参考资料 +## 5. 参考资料 - [《高性能 MySQL》](https://book.douban.com/subject/23008813/) - [Java 性能调优实战](https://time.geekbang.org/column/intro/100028001) diff --git a/docs/sql/mysql/mysql-transaction.md b/docs/sql/mysql/mysql-transaction.md index 787af5a..602e9af 100644 --- a/docs/sql/mysql/mysql-transaction.md +++ b/docs/sql/mysql/mysql-transaction.md @@ -8,27 +8,27 @@ -- [一、事务简介](#一事务简介) -- [二、事务用法](#二事务用法) - - [事务处理指令](#事务处理指令) - - [AUTOCOMMIT](#autocommit) -- [三、ACID](#三acid) -- [四、事务隔离级别](#四事务隔离级别) - - [事务隔离简介](#事务隔离简介) - - [未提交读](#未提交读) - - [提交读](#提交读) - - [可重复读](#可重复读) - - [串行化](#串行化) - - [隔离级别小结](#隔离级别小结) -- [五、分布式事务](#五分布式事务) -- [六、事务最佳实践](#六事务最佳实践) - - [优化事务](#优化事务) - - [死锁](#死锁) -- [参考资料](#参考资料) +- [1. 事务简介](#1-事务简介) +- [2. 事务用法](#2-事务用法) + - [2.1. 事务处理指令](#21-事务处理指令) + - [2.2. AUTOCOMMIT](#22-autocommit) +- [3. ACID](#3-acid) +- [4. 事务隔离级别](#4-事务隔离级别) + - [4.1. 事务隔离简介](#41-事务隔离简介) + - [4.2. 未提交读](#42-未提交读) + - [4.3. 提交读](#43-提交读) + - [4.4. 可重复读](#44-可重复读) + - [4.5. 串行化](#45-串行化) + - [4.6. 隔离级别小结](#46-隔离级别小结) +- [5. 分布式事务](#5-分布式事务) +- [6. 事务最佳实践](#6-事务最佳实践) + - [6.1. 优化事务](#61-优化事务) + - [6.2. 死锁](#62-死锁) +- [7. 参考资料](#7-参考资料) -## 一、事务简介 +## 1. 事务简介 > 事务简单来说:**一个 Session 中所进行所有的操作,要么同时成功,要么同时失败**。进一步说,事务指的是满足 ACID 特性的一组操作,可以通过 `Commit` 提交一个事务,也可以使用 `Rollback` 进行回滚。 @@ -46,9 +46,9 @@ T1 和 T2 两个线程都对一个数据进行修改,T MySQL 默认采用自动提交模式(`AUTO COMMIT`)。也就是说,如果不显式使用 `START TRANSACTION` 语句来开始一个事务,那么每个查询操作都会被当做一个事务并自动提交。 -## 四、事务隔离级别 +## 4. 事务隔离级别 -### 事务隔离简介 +### 4.1. 事务隔离简介 在并发环境下,事务的隔离性很难保证,因此会出现很多并发一致性问题: @@ -191,7 +191,7 @@ SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ; SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE; ``` -### 未提交读 +### 4.2. 未提交读 **`未提交读(READ UNCOMMITTED)` 是指:事务中的修改,即使没有提交,对其它事务也是可见的**。 @@ -201,7 +201,7 @@ T1 修改一个数据,T2 随后读取这个数据。如 ![img](http://dunwu.test.upcdn.net/cs/database/RDB/数据库并发一致性-脏数据.png) -### 提交读 +### 4.3. 提交读 **`提交读(READ COMMITTED)` 是指:一个事务只能读取已经提交的事务所做的修改**。换句话说,一个事务所做的修改在提交之前对其它事务是不可见的。提交读解决了脏读的问题。 @@ -213,7 +213,7 @@ T2 读取一个数据,T1 对该数据做了修改。如 ![img](http://dunwu.test.upcdn.net/cs/database/RDB/数据库并发一致性-不可重复读.png) -### 可重复读 +### 4.4. 可重复读 **`可重复读(REPEATABLE READ)` 是指:保证在同一个事务中多次读取同样数据的结果是一样的**。可重复读解决了不可重复读问题。 @@ -225,13 +225,13 @@ T1 读取某个范围的数据,T2 在这个范围内插 ![img](http://dunwu.test.upcdn.net/cs/database/RDB/数据库并发一致性-幻读.png) -### 串行化 +### 4.5. 串行化 **`串行化(SERIALIXABLE)` 是指:强制事务串行执行**。 强制事务串行执行,则避免了所有的并发问题。串行化策略会在读取的每一行数据上都加锁,这可能导致大量的超时和锁竞争。这对于高并发应用基本上是不可接受的,所以一般不会采用这个级别。 -### 隔离级别小结 +### 4.6. 隔离级别小结 - **`未提交读(READ UNCOMMITTED)`** - 事务中的修改,即使没有提交,对其它事务也是可见的。 - **`提交读(READ COMMITTED)`** - 一个事务只能读取已经提交的事务所做的修改。换句话说,一个事务所做的修改在提交之前对其它事务是不可见的。 @@ -247,7 +247,7 @@ T1 读取某个范围的数据,T2 在这个范围内插 | 可重复读 | ✔️ | ✔️ | ❌ | | 可串行化 | ✔️ | ✔️ | ✔️ | -## 五、分布式事务 +## 5. 分布式事务 在单一数据节点中,事务仅限于对单一数据库资源的访问控制,称之为 **本地事务**。几乎所有的成熟的关系型数据库都提供了对本地事务的原生支持。 @@ -272,11 +272,11 @@ T1 读取某个范围的数据,T2 在这个范围内插 - 本地消息表/MQ 事务 都适用于事务中参与方支持操作幂等,对一致性要求不高,业务上能容忍数据不一致到一个人工检查周期,事务涉及的参与方、参与环节较少,业务上有对账/校验系统兜底。 - Saga 事务 由于 Saga 事务不能保证隔离性,需要在业务层控制并发,适合于业务场景事务并发操作同一资源较少的情况。 Saga 相比缺少预提交动作,导致补偿动作的实现比较麻烦,例如业务是发送短信,补偿动作则得再发送一次短信说明撤销,用户体验比较差。Saga 事务较适用于补偿动作容易处理的场景。 -> 分布式事务详细说明、分析请参考:[分布式事务基本原理](https://github.com/dunwu/blog/blob/master/source/_posts/theory/distributed-transaction.md) +> 分布式事务详细说明、分析请参考:[分布式事务基本原理](https://github.com/dunwu/blog/blob/master/source/_posts/theory/distributed-transaction.md) -## 六、事务最佳实践 +## 6. 事务最佳实践 -### 优化事务 +### 6.1. 优化事务 高并发场景下的事务到底该如何调优? @@ -290,6 +290,12 @@ T1 读取某个范围的数据,T2 在这个范围内插 #### 缩小事务范围 +有时候,数据库并发访问量太大,会出现以下异常: + +``` +MySQLQueryInterruptedException: Query execution was interrupted +``` + 高并发时对一条记录进行更新的情况下,由于更新记录所在的事务还可能存在其他操作,导致一个事务比较长,当有大量请求进入时,就可能导致一些请求同时进入到事务中。 又因为锁的竞争是不公平的,当多个事务同时对一条记录进行更新时,极端情况下,一个更新操作进去排队系统后,可能会一直拿不到锁,最后因超时被系统打断踢出。 @@ -302,7 +308,7 @@ T1 读取某个范围的数据,T2 在这个范围内插 知道了这个设定,对我们使用事务有什么帮助呢?那就是,如果你的事务中需要锁多个行,要把最可能造成锁冲突、最可能影响并发度的锁尽量往后放。 -### 死锁 +### 6.2. 死锁 **死锁是指两个或多个事务竞争同一资源,并请求锁定对方占用的资源,从而导致恶性循环的现象**。 @@ -312,7 +318,6 @@ T1 读取某个范围的数据,T2 在这个范围内插 - 多个事务同时锁定同一个资源时,也会产生死锁。 - #### 死锁的原因 行锁的具体实现算法有三种:record lock、gap lock 以及 next-key lock。record lock 是专门对索引项加锁;gap lock 是对索引项之间的间隙加锁;next-key lock 则是前面两种的组合,对索引项以其之间的间隙加锁。 @@ -366,8 +371,8 @@ InnoDB 存储引擎的主键索引为聚簇索引,其它索引为辅助索引 主动死锁检测在发生死锁的时候,是能够快速发现并进行处理的,但是它也是有额外负担的。你可以想象一下这个过程:每当一个事务被锁的时候,就要看看它所依赖的线程有没有被别人锁住,如此循环,最后判断是否出现了循环等待,也就是死锁。 -## 参考资料 +## 7. 参考资料 - [《高性能 MySQL》](https://book.douban.com/subject/23008813/) -- [Java 性能调优实战](https://time.geekbang.org/column/intro/100028001) +- [《Java 性能调优实战》](https://time.geekbang.org/column/intro/100028001) - [ShardingSphere 分布式事务](https://shardingsphere.apache.org/document/current/cn/features/transaction/)