update docs

pull/8/head
dunwu 2020-12-18 20:51:53 +08:00
parent 961094a23c
commit 846bd3b5fe
2 changed files with 101 additions and 90 deletions

View File

@ -2,29 +2,29 @@
<!-- TOC depthFrom:2 depthTo:3 -->
- [一、数据结构优化](#一数据结构优化)
- [数据类型优化](#数据类型优化)
- [表设计](#表设计)
- [范式和反范式](#范式和反范式)
- [索引优化](#索引优化)
- [二、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-参考资料)
<!-- /TOC -->
## 一、数据结构优化
## 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)

View File

@ -8,27 +8,27 @@
<!-- TOC depthFrom:2 depthTo:3 -->
- [一、事务简介](#一事务简介)
- [二、事务用法](#二事务用法)
- [事务处理指令](#事务处理指令)
- [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-参考资料)
<!-- /TOC -->
## 一、事务简介
## 1. 事务简介
> 事务简单来说:**一个 Session 中所进行所有的操作,要么同时成功,要么同时失败**。进一步说,事务指的是满足 ACID 特性的一组操作,可以通过 `Commit` 提交一个事务,也可以使用 `Rollback` 进行回滚。
@ -46,9 +46,9 @@ T<sub>1</sub> 和 T<sub>2</sub> 两个线程都对一个数据进行修改T<s
![img](http://dunwu.test.upcdn.net/cs/database/RDB/数据库并发一致性-丢失修改.png)
## 二、事务用法
## 2. 事务用法
### 事务处理指令
### 2.1. 事务处理指令
Mysql 中,使用 `START TRANSACTION` 语句开始一个事务;使用 `COMMIT` 语句提交所有的修改;使用 `ROLLBACK` 语句撤销所有的修改。不能回退 `SELECT` 语句,回退 `SELECT` 语句也没意义;也不能回退 `CREATE``DROP` 语句。
@ -110,7 +110,7 @@ SELECT * FROM user;
1 root1 root1 xxxx@163.com
```
### AUTOCOMMIT
### 2.2. AUTOCOMMIT
**MySQL 默认采用隐式提交策略(`autocommit`**。每执行一条语句就把这条语句当成一个事务然后进行提交。当出现 `START TRANSACTION` 语句时,会关闭隐式提交;当 `COMMIT``ROLLBACK` 语句执行后,事务会自动关闭,重新恢复隐式提交。
@ -127,7 +127,7 @@ SET autocommit = 0;
SET autocommit = 1;
```
## 三、ACID
## 3. ACID
ACID 是数据库事务正确执行的四个基本要素。
@ -154,9 +154,9 @@ ACID 是数据库事务正确执行的四个基本要素。
> 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 @@ T<sub>1</sub> 修改一个数据T<sub>2</sub> 随后读取这个数据。如
![img](http://dunwu.test.upcdn.net/cs/database/RDB/数据库并发一致性-脏数据.png)
### 提交读
### 4.3. 提交读
**`提交读READ COMMITTED` 是指:一个事务只能读取已经提交的事务所做的修改**。换句话说,一个事务所做的修改在提交之前对其它事务是不可见的。提交读解决了脏读的问题。
@ -213,7 +213,7 @@ T<sub>2</sub> 读取一个数据T<sub>1</sub> 对该数据做了修改。如
![img](http://dunwu.test.upcdn.net/cs/database/RDB/数据库并发一致性-不可重复读.png)
### 可重复读
### 4.4. 可重复读
**`可重复读REPEATABLE READ` 是指:保证在同一个事务中多次读取同样数据的结果是一样的**。可重复读解决了不可重复读问题。
@ -225,13 +225,13 @@ T<sub>1</sub> 读取某个范围的数据T<sub>2</sub> 在这个范围内插
![img](http://dunwu.test.upcdn.net/cs/database/RDB/数据库并发一致性-幻读.png)
### 串行化
### 4.5. 串行化
**`串行化SERIALIXABLE` 是指:强制事务串行执行**。
强制事务串行执行,则避免了所有的并发问题。串行化策略会在读取的每一行数据上都加锁,这可能导致大量的超时和锁竞争。这对于高并发应用基本上是不可接受的,所以一般不会采用这个级别。
### 隔离级别小结
### 4.6. 隔离级别小结
- **`未提交读READ UNCOMMITTED`** - 事务中的修改,即使没有提交,对其它事务也是可见的。
- **`提交读READ COMMITTED`** - 一个事务只能读取已经提交的事务所做的修改。换句话说,一个事务所做的修改在提交之前对其它事务是不可见的。
@ -247,7 +247,7 @@ T<sub>1</sub> 读取某个范围的数据T<sub>2</sub> 在这个范围内插
| 可重复读 | ✔️ | ✔️ | ❌ |
| 可串行化 | ✔️ | ✔️ | ✔️ |
## 五、分布式事务
## 5. 分布式事务
在单一数据节点中,事务仅限于对单一数据库资源的访问控制,称之为 **本地事务**。几乎所有的成熟的关系型数据库都提供了对本地事务的原生支持。
@ -272,11 +272,11 @@ T<sub>1</sub> 读取某个范围的数据T<sub>2</sub> 在这个范围内插
- 本地消息表/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 @@ T<sub>1</sub> 读取某个范围的数据T<sub>2</sub> 在这个范围内插
#### 缩小事务范围
有时候,数据库并发访问量太大,会出现以下异常:
```
MySQLQueryInterruptedException: Query execution was interrupted
```
高并发时对一条记录进行更新的情况下,由于更新记录所在的事务还可能存在其他操作,导致一个事务比较长,当有大量请求进入时,就可能导致一些请求同时进入到事务中。
又因为锁的竞争是不公平的,当多个事务同时对一条记录进行更新时,极端情况下,一个更新操作进去排队系统后,可能会一直拿不到锁,最后因超时被系统打断踢出。
@ -302,7 +308,7 @@ T<sub>1</sub> 读取某个范围的数据T<sub>2</sub> 在这个范围内插
知道了这个设定,对我们使用事务有什么帮助呢?那就是,如果你的事务中需要锁多个行,要把最可能造成锁冲突、最可能影响并发度的锁尽量往后放。
### 死锁
### 6.2. 死锁
**死锁是指两个或多个事务竞争同一资源,并请求锁定对方占用的资源,从而导致恶性循环的现象**。
@ -312,7 +318,6 @@ T<sub>1</sub> 读取某个范围的数据T<sub>2</sub> 在这个范围内插
- 多个事务同时锁定同一个资源时,也会产生死锁。
#### 死锁的原因
行锁的具体实现算法有三种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/)