mirror of https://github.com/dunwu/db-tutorial.git
update docs
parent
961094a23c
commit
846bd3b5fe
|
@ -2,29 +2,29 @@
|
||||||
|
|
||||||
<!-- TOC depthFrom:2 depthTo:3 -->
|
<!-- TOC depthFrom:2 depthTo:3 -->
|
||||||
|
|
||||||
- [一、数据结构优化](#一数据结构优化)
|
- [1. 数据结构优化](#1-数据结构优化)
|
||||||
- [数据类型优化](#数据类型优化)
|
- [1.1. 数据类型优化](#11-数据类型优化)
|
||||||
- [表设计](#表设计)
|
- [1.2. 表设计](#12-表设计)
|
||||||
- [范式和反范式](#范式和反范式)
|
- [1.3. 范式和反范式](#13-范式和反范式)
|
||||||
- [索引优化](#索引优化)
|
- [1.4. 索引优化](#14-索引优化)
|
||||||
- [二、SQL 优化](#二sql-优化)
|
- [2. SQL 优化](#2-sql-优化)
|
||||||
- [优化 COUNT() 查询](#优化-count-查询)
|
- [2.1. 优化 `COUNT()` 查询](#21-优化-count-查询)
|
||||||
- [优化关联查询](#优化关联查询)
|
- [2.2. 优化关联查询](#22-优化关联查询)
|
||||||
- [优化 GROUP BY 和 DISTINCT](#优化-group-by-和-distinct)
|
- [2.3. 优化 `GROUP BY` 和 `DISTINCT`](#23-优化-group-by-和-distinct)
|
||||||
- [优化 LIMIT](#优化-limit)
|
- [2.4. 优化 `LIMIT`](#24-优化-limit)
|
||||||
- [优化 UNION](#优化-union)
|
- [2.5. 优化 UNION](#25-优化-union)
|
||||||
- [优化查询方式](#优化查询方式)
|
- [2.6. 优化查询方式](#26-优化查询方式)
|
||||||
- [三、EXPLAIN](#三explain)
|
- [3. 执行计划(`EXPLAIN`)](#3-执行计划explain)
|
||||||
- [四、optimizer trace](#四optimizer-trace)
|
- [4. optimizer trace](#4-optimizer-trace)
|
||||||
- [参考资料](#参考资料)
|
- [5. 参考资料](#5-参考资料)
|
||||||
|
|
||||||
<!-- /TOC -->
|
<!-- /TOC -->
|
||||||
|
|
||||||
## 一、数据结构优化
|
## 1. 数据结构优化
|
||||||
|
|
||||||
良好的逻辑设计和物理设计是高性能的基石。
|
良好的逻辑设计和物理设计是高性能的基石。
|
||||||
|
|
||||||
### 数据类型优化
|
### 1.1. 数据类型优化
|
||||||
|
|
||||||
#### 数据类型优化基本原则
|
#### 数据类型优化基本原则
|
||||||
|
|
||||||
|
@ -43,7 +43,7 @@
|
||||||
- 应该尽量避免用字符串类型作为标识列,因为它们很消耗空间,并且通常比数字类型慢。对于 `MD5`、`SHA`、`UUID` 这类随机字符串,由于比较随机,所以可能分布在很大的空间内,导致 `INSERT` 以及一些 `SELECT` 语句变得很慢。
|
- 应该尽量避免用字符串类型作为标识列,因为它们很消耗空间,并且通常比数字类型慢。对于 `MD5`、`SHA`、`UUID` 这类随机字符串,由于比较随机,所以可能分布在很大的空间内,导致 `INSERT` 以及一些 `SELECT` 语句变得很慢。
|
||||||
- 如果存储 UUID ,应该移除 `-` 符号;更好的做法是,用 `UNHEX()` 函数转换 UUID 值为 16 字节的数字,并存储在一个 `BINARY(16)` 的列中,检索时,可以通过 `HEX()` 函数来格式化为 16 进制格式。
|
- 如果存储 UUID ,应该移除 `-` 符号;更好的做法是,用 `UNHEX()` 函数转换 UUID 值为 16 字节的数字,并存储在一个 `BINARY(16)` 的列中,检索时,可以通过 `HEX()` 函数来格式化为 16 进制格式。
|
||||||
|
|
||||||
### 表设计
|
### 1.2. 表设计
|
||||||
|
|
||||||
应该避免的设计问题:
|
应该避免的设计问题:
|
||||||
|
|
||||||
|
@ -52,7 +52,7 @@
|
||||||
- **枚举** - 尽量不要用枚举,因为添加和删除字符串(枚举选项)必须使用 `ALTER TABLE`。
|
- **枚举** - 尽量不要用枚举,因为添加和删除字符串(枚举选项)必须使用 `ALTER TABLE`。
|
||||||
- 尽量避免 `NULL`
|
- 尽量避免 `NULL`
|
||||||
|
|
||||||
### 范式和反范式
|
### 1.3. 范式和反范式
|
||||||
|
|
||||||
**范式化目标是尽量减少冗余,而反范式化则相反**。
|
**范式化目标是尽量减少冗余,而反范式化则相反**。
|
||||||
|
|
||||||
|
@ -68,7 +68,7 @@
|
||||||
|
|
||||||
在真实世界中,很少会极端地使用范式化或反范式化。实际上,应该权衡范式和反范式的利弊,混合使用。
|
在真实世界中,很少会极端地使用范式化或反范式化。实际上,应该权衡范式和反范式的利弊,混合使用。
|
||||||
|
|
||||||
### 索引优化
|
### 1.4. 索引优化
|
||||||
|
|
||||||
> 索引优化应该是查询性能优化的最有效手段。
|
> 索引优化应该是查询性能优化的最有效手段。
|
||||||
>
|
>
|
||||||
|
@ -97,21 +97,21 @@
|
||||||
- **覆盖索引**
|
- **覆盖索引**
|
||||||
- **自增字段作主键**
|
- **自增字段作主键**
|
||||||
|
|
||||||
## 二、SQL 优化
|
## 2. SQL 优化
|
||||||
|
|
||||||
SQL 优化后,可以通过执行计划(`EXPLAIN`)来查看优化效果。
|
使用 `EXPLAIN` 命令查看当前 SQL 是否使用了索引,优化后,再通过执行计划(`EXPLAIN`)来查看优化效果。
|
||||||
|
|
||||||
SQL 优化基本思路:
|
SQL 优化基本思路:
|
||||||
|
|
||||||
- **只返回必要的列** - 最好不要使用 `SELECT *` 语句。
|
- **只返回必要的列** - 最好不要使用 `SELECT *` 语句。
|
||||||
|
|
||||||
- **只返回必要的行** - 使用 WHERE 语句进行查询过滤,有时候也需要使用 LIMIT 语句来限制返回的数据。
|
- **只返回必要的行** - 使用 `WHERE` 子查询语句进行过滤查询,有时候也需要使用 `LIMIT` 语句来限制返回的数据。
|
||||||
|
|
||||||
- **缓存重复查询的数据** - 应该考虑在客户端使用缓存,尽量不要使用 Mysql 服务器缓存(存在较多问题和限制)。
|
- **缓存重复查询的数据** - 应该考虑在客户端使用缓存,尽量不要使用 Mysql 服务器缓存(存在较多问题和限制)。
|
||||||
|
|
||||||
- **使用索引来覆盖查询**
|
- **使用索引来覆盖查询**
|
||||||
|
|
||||||
### 优化 COUNT() 查询
|
### 2.1. 优化 `COUNT()` 查询
|
||||||
|
|
||||||
`COUNT()` 有两种作用:
|
`COUNT()` 有两种作用:
|
||||||
|
|
||||||
|
@ -135,7 +135,7 @@ FROM world.city WHERE id <= 5;
|
||||||
|
|
||||||
有时候某些业务场景并不需要完全精确的统计值,可以用近似值来代替,`EXPLAIN` 出来的行数就是一个不错的近似值,而且执行 `EXPLAIN` 并不需要真正地去执行查询,所以成本非常低。通常来说,执行 `COUNT()` 都需要扫描大量的行才能获取到精确的数据,因此很难优化,MySQL 层面还能做得也就只有覆盖索引了。如果不还能解决问题,只有从架构层面解决了,比如添加汇总表,或者使用 Redis 这样的外部缓存系统。
|
有时候某些业务场景并不需要完全精确的统计值,可以用近似值来代替,`EXPLAIN` 出来的行数就是一个不错的近似值,而且执行 `EXPLAIN` 并不需要真正地去执行查询,所以成本非常低。通常来说,执行 `COUNT()` 都需要扫描大量的行才能获取到精确的数据,因此很难优化,MySQL 层面还能做得也就只有覆盖索引了。如果不还能解决问题,只有从架构层面解决了,比如添加汇总表,或者使用 Redis 这样的外部缓存系统。
|
||||||
|
|
||||||
### 优化关联查询
|
### 2.2. 优化关联查询
|
||||||
|
|
||||||
在大数据场景下,表与表之间通过一个冗余字段来关联,要比直接使用 `JOIN` 有更好的性能。
|
在大数据场景下,表与表之间通过一个冗余字段来关联,要比直接使用 `JOIN` 有更好的性能。
|
||||||
|
|
||||||
|
@ -172,11 +172,11 @@ while(outer_row) {
|
||||||
|
|
||||||
可以看到,最外层的查询是根据`A.xx`列来查询的,`A.c`上如果有索引的话,整个关联查询也不会使用。再看内层的查询,很明显`B.c`上如果有索引的话,能够加速查询,因此只需要在关联顺序中的第二张表的相应列上创建索引即可。
|
可以看到,最外层的查询是根据`A.xx`列来查询的,`A.c`上如果有索引的话,整个关联查询也不会使用。再看内层的查询,很明显`B.c`上如果有索引的话,能够加速查询,因此只需要在关联顺序中的第二张表的相应列上创建索引即可。
|
||||||
|
|
||||||
### 优化 GROUP BY 和 DISTINCT
|
### 2.3. 优化 `GROUP BY` 和 `DISTINCT`
|
||||||
|
|
||||||
Mysql 优化器会在内部处理的时候相互转化这两类查询。它们都**可以使用索引来优化,这也是最有效的优化方法**。
|
Mysql 优化器会在内部处理的时候相互转化这两类查询。它们都**可以使用索引来优化,这也是最有效的优化方法**。
|
||||||
|
|
||||||
### 优化 LIMIT
|
### 2.4. 优化 `LIMIT`
|
||||||
|
|
||||||
当需要分页操作时,通常会使用 `LIMIT` 加上偏移量的办法实现,同时加上合适的 `ORDER BY` 字句。**如果有对应的索引,通常效率会不错,否则,MySQL 需要做大量的文件排序操作**。
|
当需要分页操作时,通常会使用 `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`等字句“下推”到各个子查询中,以便优化器可以充分利用这些条件先优化。
|
MySQL 总是通过创建并填充临时表的方式来执行 `UNION` 查询。因此很多优化策略在`UNION`查询中都没有办法很好的时候。经常需要手动将`WHERE`、`LIMIT`、`ORDER BY`等字句“下推”到各个子查询中,以便优化器可以充分利用这些条件先优化。
|
||||||
|
|
||||||
除非确实需要服务器去重,否则就一定要使用`UNION ALL`,如果没有`ALL`关键字,MySQL 会给临时表加上`DISTINCT`选项,这会导致整个临时表的数据做唯一性检查,这样做的代价非常高。当然即使使用 ALL 关键字,MySQL 总是将结果放入临时表,然后再读出,再返回给客户端。虽然很多时候没有这个必要,比如有时候可以直接把每个子查询的结果返回给客户端。
|
除非确实需要服务器去重,否则就一定要使用`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);
|
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
|
```sql
|
||||||
mysql> explain select * from user_info where id = 2\G
|
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)
|
1 row in set, 1 warning (0.00 sec)
|
||||||
```
|
```
|
||||||
|
|
||||||
各列含义如下:
|
`EXPLAIN` 参数说明:
|
||||||
|
|
||||||
- id: SELECT 查询的标识符. 每个 SELECT 都会自动分配一个唯一的标识符.
|
- `id`: SELECT 查询的标识符. 每个 SELECT 都会自动分配一个唯一的标识符.
|
||||||
- select_type: SELECT 查询的类型.
|
- `select_type` ⭐ :SELECT 查询的类型.
|
||||||
- SIMPLE, 表示此查询不包含 UNION 查询或子查询
|
- `SIMPLE`:表示此查询不包含 UNION 查询或子查询
|
||||||
- PRIMARY, 表示此查询是最外层的查询
|
- `PRIMARY`:表示此查询是最外层的查询
|
||||||
- UNION, 表示此查询是 UNION 的第二或随后的查询
|
- `UNION`:表示此查询是 UNION 的第二或随后的查询
|
||||||
- DEPENDENT UNION, UNION 中的第二个或后面的查询语句, 取决于外面的查询
|
- `DEPENDENT UNION`:UNION 中的第二个或后面的查询语句, 取决于外面的查询
|
||||||
- UNION RESULT, UNION 的结果
|
- `UNION RESULT`:UNION 的结果
|
||||||
- SUBQUERY, 子查询中的第一个 SELECT
|
- `SUBQUERY`:子查询中的第一个 SELECT
|
||||||
- DEPENDENT SUBQUERY: 子查询中的第一个 SELECT, 取决于外面的查询. 即子查询依赖于外层查询的结果.
|
- `DEPENDENT SUBQUERY`: 子查询中的第一个 SELECT, 取决于外面的查询. 即子查询依赖于外层查询的结果.
|
||||||
- table: 查询的是哪个表
|
- `table`: 查询的是哪个表,如果给表起别名了,则显示别名。
|
||||||
- partitions: 匹配的分区
|
- `partitions`:匹配的分区
|
||||||
- type: join 类型
|
- `type` ⭐:表示从表中查询到行所执行的方式,查询方式是 SQL 优化中一个很重要的指标,结果值从好到差依次是:system > const > eq_ref > ref > range > index > ALL。
|
||||||
- possible_keys: 此次查询中可能选用的索引
|
- `system`/`const`:表中只有一行数据匹配,此时根据索引查询一次就能找到对应的数据。如果是 B + 树索引,我们知道此时索引构造成了多个层级的树,当查询的索引在树的底层时,查询效率就越低。const 表示此时索引在第一层,只需访问一层便能得到数据。
|
||||||
- key: 此次查询中确切使用到的索引.
|
- `eq_ref`:使用唯一索引扫描,常见于多表连接中使用主键和唯一索引作为关联条件。
|
||||||
- ref: 哪个字段或常数与 key 一起被使用
|
- `ref`:非唯一索引扫描,还可见于唯一索引最左原则匹配扫描。
|
||||||
- rows: 显示此查询一共扫描了多少行. 这个是一个估计值.
|
- `range`:索引范围扫描,比如,<,>,between 等操作。
|
||||||
- filtered: 表示此查询条件所过滤的数据的百分比
|
- `index`:索引全表扫描,此时遍历整个索引树。
|
||||||
- extra: 额外的信息
|
- `ALL`:表示全表扫描,需要遍历全表来找到对应的行。
|
||||||
|
- `possible_keys`:此次查询中可能选用的索引。
|
||||||
|
- `key` ⭐:此次查询中实际使用的索引。
|
||||||
|
- `ref`:哪个字段或常数与 key 一起被使用。
|
||||||
|
- `rows` ⭐:显示此查询一共扫描了多少行,这个是一个估计值。
|
||||||
|
- `filtered`:表示此查询条件所过滤的数据的百分比。
|
||||||
|
- `extra`:额外的信息。
|
||||||
|
|
||||||
> 更多内容请参考:[MySQL 性能优化神器 Explain 使用分析](https://segmentfault.com/a/1190000008131735)
|
> 更多内容请参考:[MySQL 性能优化神器 Explain 使用分析](https://segmentfault.com/a/1190000008131735)
|
||||||
|
|
||||||
## 四、optimizer trace
|
## 4. optimizer trace
|
||||||
|
|
||||||
在 MySQL 5.6 及之后的版本中,我们可以使用 optimizer trace 功能查看优化器生成执行计划的整个过程。有了这个功能,我们不仅可以了解优化器的选择过程,更可以了解每一个执行环节的成本,然后依靠这些信息进一步优化查询。
|
在 MySQL 5.6 及之后的版本中,我们可以使用 optimizer trace 功能查看优化器生成执行计划的整个过程。有了这个功能,我们不仅可以了解优化器的选择过程,更可以了解每一个执行环节的成本,然后依靠这些信息进一步优化查询。
|
||||||
|
|
||||||
|
@ -316,7 +322,7 @@ SELECT * FROM information_schema.OPTIMIZER_TRACE;
|
||||||
SET optimizer_trace="enabled=off";
|
SET optimizer_trace="enabled=off";
|
||||||
```
|
```
|
||||||
|
|
||||||
## 参考资料
|
## 5. 参考资料
|
||||||
|
|
||||||
- [《高性能 MySQL》](https://book.douban.com/subject/23008813/)
|
- [《高性能 MySQL》](https://book.douban.com/subject/23008813/)
|
||||||
- [Java 性能调优实战](https://time.geekbang.org/column/intro/100028001)
|
- [Java 性能调优实战](https://time.geekbang.org/column/intro/100028001)
|
||||||
|
|
|
@ -8,27 +8,27 @@
|
||||||
|
|
||||||
<!-- TOC depthFrom:2 depthTo:3 -->
|
<!-- TOC depthFrom:2 depthTo:3 -->
|
||||||
|
|
||||||
- [一、事务简介](#一事务简介)
|
- [1. 事务简介](#1-事务简介)
|
||||||
- [二、事务用法](#二事务用法)
|
- [2. 事务用法](#2-事务用法)
|
||||||
- [事务处理指令](#事务处理指令)
|
- [2.1. 事务处理指令](#21-事务处理指令)
|
||||||
- [AUTOCOMMIT](#autocommit)
|
- [2.2. AUTOCOMMIT](#22-autocommit)
|
||||||
- [三、ACID](#三acid)
|
- [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 -->
|
<!-- /TOC -->
|
||||||
|
|
||||||
## 一、事务简介
|
## 1. 事务简介
|
||||||
|
|
||||||
> 事务简单来说:**一个 Session 中所进行所有的操作,要么同时成功,要么同时失败**。进一步说,事务指的是满足 ACID 特性的一组操作,可以通过 `Commit` 提交一个事务,也可以使用 `Rollback` 进行回滚。
|
> 事务简单来说:**一个 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)
|
![img](http://dunwu.test.upcdn.net/cs/database/RDB/数据库并发一致性-丢失修改.png)
|
||||||
|
|
||||||
## 二、事务用法
|
## 2. 事务用法
|
||||||
|
|
||||||
### 事务处理指令
|
### 2.1. 事务处理指令
|
||||||
|
|
||||||
Mysql 中,使用 `START TRANSACTION` 语句开始一个事务;使用 `COMMIT` 语句提交所有的修改;使用 `ROLLBACK` 语句撤销所有的修改。不能回退 `SELECT` 语句,回退 `SELECT` 语句也没意义;也不能回退 `CREATE` 和 `DROP` 语句。
|
Mysql 中,使用 `START TRANSACTION` 语句开始一个事务;使用 `COMMIT` 语句提交所有的修改;使用 `ROLLBACK` 语句撤销所有的修改。不能回退 `SELECT` 语句,回退 `SELECT` 语句也没意义;也不能回退 `CREATE` 和 `DROP` 语句。
|
||||||
|
|
||||||
|
@ -110,7 +110,7 @@ SELECT * FROM user;
|
||||||
1 root1 root1 xxxx@163.com
|
1 root1 root1 xxxx@163.com
|
||||||
```
|
```
|
||||||
|
|
||||||
### AUTOCOMMIT
|
### 2.2. AUTOCOMMIT
|
||||||
|
|
||||||
**MySQL 默认采用隐式提交策略(`autocommit`)**。每执行一条语句就把这条语句当成一个事务然后进行提交。当出现 `START TRANSACTION` 语句时,会关闭隐式提交;当 `COMMIT` 或 `ROLLBACK` 语句执行后,事务会自动关闭,重新恢复隐式提交。
|
**MySQL 默认采用隐式提交策略(`autocommit`)**。每执行一条语句就把这条语句当成一个事务然后进行提交。当出现 `START TRANSACTION` 语句时,会关闭隐式提交;当 `COMMIT` 或 `ROLLBACK` 语句执行后,事务会自动关闭,重新恢复隐式提交。
|
||||||
|
|
||||||
|
@ -127,7 +127,7 @@ SET autocommit = 0;
|
||||||
SET autocommit = 1;
|
SET autocommit = 1;
|
||||||
```
|
```
|
||||||
|
|
||||||
## 三、ACID
|
## 3. ACID
|
||||||
|
|
||||||
ACID 是数据库事务正确执行的四个基本要素。
|
ACID 是数据库事务正确执行的四个基本要素。
|
||||||
|
|
||||||
|
@ -154,9 +154,9 @@ ACID 是数据库事务正确执行的四个基本要素。
|
||||||
|
|
||||||
> MySQL 默认采用自动提交模式(`AUTO COMMIT`)。也就是说,如果不显式使用 `START TRANSACTION` 语句来开始一个事务,那么每个查询操作都会被当做一个事务并自动提交。
|
> 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;
|
SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE;
|
||||||
```
|
```
|
||||||
|
|
||||||
### 未提交读
|
### 4.2. 未提交读
|
||||||
|
|
||||||
**`未提交读(READ UNCOMMITTED)` 是指:事务中的修改,即使没有提交,对其它事务也是可见的**。
|
**`未提交读(READ UNCOMMITTED)` 是指:事务中的修改,即使没有提交,对其它事务也是可见的**。
|
||||||
|
|
||||||
|
@ -201,7 +201,7 @@ T<sub>1</sub> 修改一个数据,T<sub>2</sub> 随后读取这个数据。如
|
||||||
|
|
||||||
![img](http://dunwu.test.upcdn.net/cs/database/RDB/数据库并发一致性-脏数据.png)
|
![img](http://dunwu.test.upcdn.net/cs/database/RDB/数据库并发一致性-脏数据.png)
|
||||||
|
|
||||||
### 提交读
|
### 4.3. 提交读
|
||||||
|
|
||||||
**`提交读(READ COMMITTED)` 是指:一个事务只能读取已经提交的事务所做的修改**。换句话说,一个事务所做的修改在提交之前对其它事务是不可见的。提交读解决了脏读的问题。
|
**`提交读(READ COMMITTED)` 是指:一个事务只能读取已经提交的事务所做的修改**。换句话说,一个事务所做的修改在提交之前对其它事务是不可见的。提交读解决了脏读的问题。
|
||||||
|
|
||||||
|
@ -213,7 +213,7 @@ T<sub>2</sub> 读取一个数据,T<sub>1</sub> 对该数据做了修改。如
|
||||||
|
|
||||||
![img](http://dunwu.test.upcdn.net/cs/database/RDB/数据库并发一致性-不可重复读.png)
|
![img](http://dunwu.test.upcdn.net/cs/database/RDB/数据库并发一致性-不可重复读.png)
|
||||||
|
|
||||||
### 可重复读
|
### 4.4. 可重复读
|
||||||
|
|
||||||
**`可重复读(REPEATABLE READ)` 是指:保证在同一个事务中多次读取同样数据的结果是一样的**。可重复读解决了不可重复读问题。
|
**`可重复读(REPEATABLE READ)` 是指:保证在同一个事务中多次读取同样数据的结果是一样的**。可重复读解决了不可重复读问题。
|
||||||
|
|
||||||
|
@ -225,13 +225,13 @@ T<sub>1</sub> 读取某个范围的数据,T<sub>2</sub> 在这个范围内插
|
||||||
|
|
||||||
![img](http://dunwu.test.upcdn.net/cs/database/RDB/数据库并发一致性-幻读.png)
|
![img](http://dunwu.test.upcdn.net/cs/database/RDB/数据库并发一致性-幻读.png)
|
||||||
|
|
||||||
### 串行化
|
### 4.5. 串行化
|
||||||
|
|
||||||
**`串行化(SERIALIXABLE)` 是指:强制事务串行执行**。
|
**`串行化(SERIALIXABLE)` 是指:强制事务串行执行**。
|
||||||
|
|
||||||
强制事务串行执行,则避免了所有的并发问题。串行化策略会在读取的每一行数据上都加锁,这可能导致大量的超时和锁竞争。这对于高并发应用基本上是不可接受的,所以一般不会采用这个级别。
|
强制事务串行执行,则避免了所有的并发问题。串行化策略会在读取的每一行数据上都加锁,这可能导致大量的超时和锁竞争。这对于高并发应用基本上是不可接受的,所以一般不会采用这个级别。
|
||||||
|
|
||||||
### 隔离级别小结
|
### 4.6. 隔离级别小结
|
||||||
|
|
||||||
- **`未提交读(READ UNCOMMITTED)`** - 事务中的修改,即使没有提交,对其它事务也是可见的。
|
- **`未提交读(READ UNCOMMITTED)`** - 事务中的修改,即使没有提交,对其它事务也是可见的。
|
||||||
- **`提交读(READ COMMITTED)`** - 一个事务只能读取已经提交的事务所做的修改。换句话说,一个事务所做的修改在提交之前对其它事务是不可见的。
|
- **`提交读(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 事务 都适用于事务中参与方支持操作幂等,对一致性要求不高,业务上能容忍数据不一致到一个人工检查周期,事务涉及的参与方、参与环节较少,业务上有对账/校验系统兜底。
|
- 本地消息表/MQ 事务 都适用于事务中参与方支持操作幂等,对一致性要求不高,业务上能容忍数据不一致到一个人工检查周期,事务涉及的参与方、参与环节较少,业务上有对账/校验系统兜底。
|
||||||
- Saga 事务 由于 Saga 事务不能保证隔离性,需要在业务层控制并发,适合于业务场景事务并发操作同一资源较少的情况。 Saga 相比缺少预提交动作,导致补偿动作的实现比较麻烦,例如业务是发送短信,补偿动作则得再发送一次短信说明撤销,用户体验比较差。Saga 事务较适用于补偿动作容易处理的场景。
|
- 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 则是前面两种的组合,对索引项以其之间的间隙加锁。
|
行锁的具体实现算法有三种: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/)
|
- [《高性能 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/)
|
- [ShardingSphere 分布式事务](https://shardingsphere.apache.org/document/current/cn/features/transaction/)
|
||||||
|
|
Loading…
Reference in New Issue