mirror of https://github.com/dunwu/db-tutorial.git
update docs
parent
7091f28380
commit
53f78a3b19
82
README.md
82
README.md
|
@ -1,14 +1,24 @@
|
|||
# 数据库教程
|
||||
<p align="center">
|
||||
<a href="https://github.com/dunwu/db-tutorial/" target="_blank" rel="noopener noreferrer">
|
||||
<img src="http://dunwu.test.upcdn.net/common/logo/dunwu-logo.png" alt="logo" width="150px"/>
|
||||
</a>
|
||||
</p>
|
||||
|
||||
![license](https://badgen.net/github/license/dunwu/db-tutorial)
|
||||
![build](https://api.travis-ci.com/dunwu/db-tutorial.svg?branch=master)
|
||||
<p align="center">
|
||||
<img src="https://badgen.net/github/license/dunwu/db-tutorial" alt="license">
|
||||
<img src="https://api.travis-ci.com/dunwu/db-tutorial.svg?branch=master" alt="build">
|
||||
</p>
|
||||
|
||||
> 💾 db-tutorial 是一个数据库教程。
|
||||
<h1 align="center">DB-TUTORIAL</h1>
|
||||
|
||||
> 💾 **db-tutorial** 是一个数据库教程。
|
||||
>
|
||||
> - 🔁 项目同步维护:[Github](https://github.com/dunwu/db-tutorial/) | [Gitee](https://gitee.com/turnon/db-tutorial/)
|
||||
> - 📖 电子书阅读:[Github Pages](https://dunwu.github.io/db-tutorial/) | [Gitee Pages](https://turnon.gitee.io/db-tutorial/)
|
||||
|
||||
## 关系型数据库
|
||||
## 📖 内容
|
||||
|
||||
### 关系型数据库
|
||||
|
||||
> [关系型数据库](docs/sql) 整理主流关系型数据库知识点。
|
||||
|
||||
|
@ -17,7 +27,7 @@
|
|||
- [Mysql](docs/sql/mysql) 📚
|
||||
- [Mysql 基本原理](docs/sql/mysql/mysql-theory.md)
|
||||
- [Mysql 索引](docs/sql/mysql/mysql-index.md)
|
||||
- [Mysql 并发控制](docs/sql/mysql/mysql-concurrency-control.md)
|
||||
- [Mysql 锁](docs/sql/mysql/mysql-lock.md)
|
||||
- [Mysql 事务](docs/sql/mysql/mysql-transaction.md)
|
||||
- [Mysql 优化](docs/sql/mysql/mysql-optimization.md)
|
||||
- [Mysql 运维](docs/sql/mysql/mysql-ops.md) 🔨
|
||||
|
@ -26,12 +36,16 @@
|
|||
- [SqLite 入门指南](docs/sql/sqlite.md)
|
||||
- [PostgreSQL 入门指南](docs/sql/postgresql.md)
|
||||
|
||||
## Nosql 数据库
|
||||
### Nosql 数据库
|
||||
|
||||
> [Nosql 数据库](docs/nosql) 整理主流 Nosql 数据库知识点。
|
||||
|
||||
- [Nosql 技术选型](docs/nosql/nosql-selection.md)
|
||||
- [Redis](docs/nosql/redis) 📚
|
||||
|
||||
#### Redis
|
||||
|
||||
> [Redis](docs/nosql/redis) 📚
|
||||
|
||||
- [Redis 面试总结](docs/nosql/redis/redis-interview.md) 💯
|
||||
- [Redis 入门指南](docs/nosql/redis/redis-quickstart.md) ⚡
|
||||
- [Redis 数据类型和应用](docs/nosql/redis/redis-datatype.md)
|
||||
|
@ -43,7 +57,57 @@
|
|||
- [Redis 发布与订阅](docs/nosql/redis/redis-pub-sub.md)
|
||||
- [Redis 运维](docs/nosql/redis/redis-ops.md) 🔨
|
||||
|
||||
## 中间件
|
||||
#### HBase
|
||||
|
||||
> [HBase](https://dunwu.github.io/bigdata-tutorial/hbase) 📚 因为常用于大数据项目,所以将其文档和源码整理在 [bigdata-tutorial](https://dunwu.github.io/bigdata-tutorial/) 项目中。
|
||||
|
||||
- [HBase 应用指南](https://github.com/dunwu/bigdata-tutorial/blob/master/docs/hbase/hbase-quickstart.md) ⚡
|
||||
- [HBase 命令](https://github.com/dunwu/bigdata-tutorial/blob/master/docs/hbase/hbase-cli.md)
|
||||
- [HBase Java API](https://github.com/dunwu/bigdata-tutorial/blob/master/docs/hbase/hbase-api.md)
|
||||
- [HBase 配置](https://github.com/dunwu/bigdata-tutorial/blob/master/docs/hbase/hbase-ops.md)
|
||||
|
||||
### 中间件
|
||||
|
||||
- [版本管理中间件 flyway](docs/middleware/flyway.md)
|
||||
- [分库分表中间件 ShardingSphere](docs/middleware/shardingsphere.md)
|
||||
|
||||
## 📚 资料
|
||||
|
||||
- [《SQL 必知必会》](https://item.jd.com/11232698.html)
|
||||
|
||||
- **Mysql**
|
||||
- **官方**
|
||||
- [Mysql 官网](https://www.mysql.com/)
|
||||
- [Mysql 官方文档](https://dev.mysql.com/doc/refman/8.0/en/)
|
||||
- [Mysql 官方文档之命令行客户端](https://dev.mysql.com/doc/refman/8.0/en/mysql.html)
|
||||
- **书籍**
|
||||
- [《高性能 MySQL》](https://book.douban.com/subject/23008813/) - 经典,适合 DBA 或作为开发者的参考手册
|
||||
- [《MySQL 必知必会》](https://book.douban.com/subject/3354490/) - 适合入门者
|
||||
- **教程**
|
||||
- [runoob.com MySQL 教程](http://www.runoob.com/mysql/mysql-tutorial.html) - 入门级 SQL 教程
|
||||
- [mysql-tutorial](https://github.com/jaywcjlove/mysql-tutorial)
|
||||
- **更多资源**
|
||||
- [awesome-mysql](https://github.com/jobbole/awesome-mysql-cn)
|
||||
- **Redis**
|
||||
- **官网**
|
||||
- [Redis 官网](https://redis.io/)
|
||||
- [Redis github](https://github.com/antirez/redis)
|
||||
- [Redis 官方文档中文版](http://redis.cn/)
|
||||
- [Redis 官方文档翻译版本](http://redisdoc.com/topic/sentinel.html)
|
||||
- **书籍**
|
||||
- [《Redis 实战》](https://item.jd.com/11791607.html)
|
||||
- [《Redis 设计与实现》](https://item.jd.com/11486101.html)
|
||||
- 源码
|
||||
- [《Redis 实战》配套 Python 源码](https://github.com/josiahcarlson/redis-in-action)
|
||||
- **资源汇总**
|
||||
- [awesome-redis](https://github.com/JamzyWang/awesome-redis)
|
||||
- **Redis Client**
|
||||
- [spring-data-redis 官方文档](https://docs.spring.io/spring-data/redis/docs/1.8.13.RELEASE/reference/html/)
|
||||
- [redisson 官方文档(中文,略有滞后)](https://github.com/redisson/redisson/wiki/%E7%9B%AE%E5%BD%95)
|
||||
- [redisson 官方文档(英文)](https://github.com/redisson/redisson/wiki/Table-of-Content)
|
||||
- [CRUG | Redisson PRO vs. Jedis: Which Is Faster? 翻译](https://www.jianshu.com/p/82f0d5abb002)
|
||||
- [redis 分布锁 Redisson 性能测试](https://blog.csdn.net/everlasting_188/article/details/51073505)
|
||||
|
||||
## 🚪 传送门
|
||||
|
||||
◾ 🏠 [LINUX-TUTORIAL 首页](https://github.com/dunwu/linux-tutorial) ◾ 🎯 [我的博客](https://github.com/dunwu/blog) ◾
|
||||
|
|
|
@ -12,12 +12,14 @@ footer: CC-BY-SA-4.0 Licensed | Copyright © 2018-Now Dunwu
|
|||
![license](https://badgen.net/github/license/dunwu/db-tutorial)
|
||||
![build](https://api.travis-ci.com/dunwu/db-tutorial.svg?branch=master)
|
||||
|
||||
> 💾 db-tutorial 是一个数据库教程。
|
||||
> 💾 **db-tutorial** 是一个数据库教程。
|
||||
>
|
||||
> - 🔁 项目同步维护:[Github](https://github.com/dunwu/db-tutorial/) | [Gitee](https://gitee.com/turnon/db-tutorial/)
|
||||
> - 📖 电子书阅读:[Github Pages](https://dunwu.github.io/db-tutorial/) | [Gitee Pages](https://turnon.gitee.io/db-tutorial/)
|
||||
|
||||
## 关系型数据库
|
||||
## 📖 内容
|
||||
|
||||
### 关系型数据库
|
||||
|
||||
> [关系型数据库](sql) 整理主流关系型数据库知识点。
|
||||
|
||||
|
@ -26,7 +28,7 @@ footer: CC-BY-SA-4.0 Licensed | Copyright © 2018-Now Dunwu
|
|||
- [Mysql](sql/mysql) 📚
|
||||
- [Mysql 基本原理](sql/mysql/mysql-theory.md)
|
||||
- [Mysql 索引](sql/mysql/mysql-index.md)
|
||||
- [Mysql 并发控制](sql/mysql/mysql-concurrency-control.md)
|
||||
- [Mysql 锁](sql/mysql/mysql-lock.md)
|
||||
- [Mysql 事务](sql/mysql/mysql-transaction.md)
|
||||
- [Mysql 优化](sql/mysql/mysql-optimization.md)
|
||||
- [Mysql 运维](sql/mysql/mysql-ops.md) 🔨
|
||||
|
@ -35,7 +37,7 @@ footer: CC-BY-SA-4.0 Licensed | Copyright © 2018-Now Dunwu
|
|||
- [SqLite 入门指南](sql/sqlite.md)
|
||||
- [PostgreSQL 入门指南](sql/postgresql.md)
|
||||
|
||||
## Nosql 数据库
|
||||
### Nosql 数据库
|
||||
|
||||
> [Nosql 数据库](nosql) 整理主流 Nosql 数据库知识点。
|
||||
|
||||
|
@ -52,7 +54,46 @@ footer: CC-BY-SA-4.0 Licensed | Copyright © 2018-Now Dunwu
|
|||
- [Redis 发布与订阅](nosql/redis/redis-pub-sub.md)
|
||||
- [Redis 运维](nosql/redis/redis-ops.md) 🔨
|
||||
|
||||
## 中间件
|
||||
### 中间件
|
||||
|
||||
- [版本管理中间件 flyway](middleware/flyway.md)
|
||||
- [分库分表中间件 ShardingSphere](middleware/shardingsphere.md)
|
||||
|
||||
## 📚 资料
|
||||
|
||||
- **Mysql**
|
||||
- **官方**
|
||||
- [Mysql 官网](https://www.mysql.com/)
|
||||
- [Mysql 官方文档](https://dev.mysql.com/doc/refman/8.0/en/)
|
||||
- [Mysql 官方文档之命令行客户端](https://dev.mysql.com/doc/refman/8.0/en/mysql.html)
|
||||
- **书籍**
|
||||
- [《高性能 MySQL》](https://book.douban.com/subject/23008813/) - 经典,适合 DBA 或作为开发者的参考手册
|
||||
- [《MySQL 必知必会》](https://book.douban.com/subject/3354490/) - 适合入门者
|
||||
- **教程**
|
||||
- [runoob.com MySQL 教程](http://www.runoob.com/mysql/mysql-tutorial.html) - 入门级 SQL 教程
|
||||
- [mysql-tutorial](https://github.com/jaywcjlove/mysql-tutorial)
|
||||
- **更多资源**
|
||||
- [awesome-mysql](https://github.com/jobbole/awesome-mysql-cn)
|
||||
- **Redis**
|
||||
- **官网**
|
||||
- [Redis 官网](https://redis.io/)
|
||||
- [Redis github](https://github.com/antirez/redis)
|
||||
- [Redis 官方文档中文版](http://redis.cn/)
|
||||
- [Redis 官方文档翻译版本](http://redisdoc.com/topic/sentinel.html)
|
||||
- **书籍**
|
||||
- [《Redis 实战》](https://item.jd.com/11791607.html)
|
||||
- [《Redis 设计与实现》](https://item.jd.com/11486101.html)
|
||||
- 源码
|
||||
- [《Redis 实战》配套 Python 源码](https://github.com/josiahcarlson/redis-in-action)
|
||||
- **资源汇总**
|
||||
- [awesome-redis](https://github.com/JamzyWang/awesome-redis)
|
||||
- **Redis Client**
|
||||
- [spring-data-redis 官方文档](https://docs.spring.io/spring-data/redis/docs/1.8.13.RELEASE/reference/html/)
|
||||
- [redisson 官方文档(中文,略有滞后)](https://github.com/redisson/redisson/wiki/%E7%9B%AE%E5%BD%95)
|
||||
- [redisson 官方文档(英文)](https://github.com/redisson/redisson/wiki/Table-of-Content)
|
||||
- [CRUG | Redisson PRO vs. Jedis: Which Is Faster? 翻译](https://www.jianshu.com/p/82f0d5abb002)
|
||||
- [redis 分布锁 Redisson 性能测试](https://blog.csdn.net/everlasting_188/article/details/51073505)
|
||||
|
||||
## 🚪 传送门
|
||||
|
||||
◾ 🏠 [LINUX-TUTORIAL 首页](https://github.com/dunwu/linux-tutorial) ◾ 🎯 [我的博客](https://github.com/dunwu/blog) ◾
|
||||
|
|
|
@ -2,8 +2,12 @@
|
|||
|
||||
## 📖 内容
|
||||
|
||||
- [Mysql 原理](mysql-theory.md)
|
||||
- [Mysql 运维 🔨](mysql-ops.md)
|
||||
- [Mysql 基本原理](mysql-theory.md)
|
||||
- [Mysql 索引](mysql-index.md)
|
||||
- [Mysql 锁](mysql-lock.md)
|
||||
- [Mysql 事务](mysql-transaction.md)
|
||||
- [Mysql 优化](mysql-optimization.md)
|
||||
- [Mysql 运维](mysql-ops.md) 🔨
|
||||
- [Mysql 配置](mysql-config.md)
|
||||
|
||||
## 📚 资料
|
||||
|
|
|
@ -12,26 +12,103 @@
|
|||
|
||||
B+ 树索引,按照顺序存储数据,所以 Mysql 可以用来做 ORDER BY 和 GROUP BY 操作。因为数据是有序的,所以 B+ 树也就会将相关的列值都存储在一起。最后,因为索引中存储了实际的列值,所以某些查询只使用索引就能够完成全部查询。
|
||||
|
||||
基于以上,索引的优点:
|
||||
✔ 索引的优点:
|
||||
|
||||
- 大大减少了服务器需要扫描的数据行数。
|
||||
- 帮助服务器避免进行排序和创建临时表(B+Tree 索引是有序的,可以用来做 ORDER BY 和 GROUP BY 操作);
|
||||
- 将随机 I/O 变为顺序 I/O(B+Tree 索引是有序的,也就将相邻的数据都存储在一起)。
|
||||
- 索引大大减少了服务器需要扫描的数据量,从而加快检索速度。
|
||||
- 支持行级锁的数据库,如 InnoDB 会在访问行的时候加锁。使用索引可以减少访问的行数,从而减少锁的竞争,提高并发。
|
||||
- 索引可以帮助服务器避免排序和临时表。
|
||||
- 索引可以将随机 I/O 变为顺序 I/O。
|
||||
- 唯一索引可以确保每一行数据的唯一性,通过使用索引,可以在查询的过程中使用优化隐藏器,提高系统的性能。
|
||||
|
||||
索引的缺点:
|
||||
❌ 索引的缺点:
|
||||
|
||||
1. 创建索引和维护索引要耗费时间,这种时间随着数据量的增加而增加。
|
||||
2. 索引需要占物理空间,除了数据表占数据空间之外,每一个索引还要占一定的物理空间,如果要建立组合索引那么需要的空间就会更大。
|
||||
3. 当对表中的数据进行增加、删除和修改的时候,索引也要动态的维护,这样就降低了数据的维护速度。
|
||||
- 创建和维护索引要耗费时间,这会随着数据量的增加而增加。
|
||||
- **索引需要占用额外的物理空间**,除了数据表占数据空间之外,每一个索引还要占一定的物理空间,如果要建立组合索引那么需要的空间就会更大。
|
||||
- 写操作(`INSERT`/`UPDATE`/`DELETE`)时很可能需要更新索引,导致数据库的写操作性能降低。
|
||||
|
||||
### 何时使用索引
|
||||
|
||||
- 对于非常小的表,大部分情况下简单的全表扫描更高效。
|
||||
- 对于中、大型表,索引非常有效。
|
||||
- 对于特大型表,建立和使用索引的代价将随之增长。可以考虑使用分区技术。
|
||||
- 如果表的数量特别多,可以建立一个元数据信息表,用来查询需要用到的某些特性。
|
||||
> 索引能够轻易将查询性能提升几个数量级。
|
||||
|
||||
## 二、索引数据结构
|
||||
✔ 什么情况**适用**索引:
|
||||
|
||||
- 表经常进行 `SELECT` 操作;
|
||||
- 表的数据量比较大;
|
||||
- 列名经常出现在 `WHERE` 或连接(`JOIN`)条件中
|
||||
|
||||
❌ 什么情况**不适用**索引:
|
||||
|
||||
- **频繁写操作**( `INSERT`/`UPDATE`/`DELETE` )- 需要更新索引空间;
|
||||
- **非常小的表** - 对于非常小的表,大部分情况下简单的全表扫描更高效。
|
||||
- 列名不经常出现在 `WHERE` 或连接(`JOIN`)条件中 - 索引就会经常不命中,没有意义,还增加空间开销。
|
||||
- 对于特大型表,建立和使用索引的代价将随之增长。可以考虑使用分区技术或 Nosql。
|
||||
|
||||
## 二、索引的类型
|
||||
|
||||
主流的关系型数据库一般都支持以下索引类型:
|
||||
|
||||
从逻辑类型上划分(即一般创建表时设置的索引类型
|
||||
|
||||
#### 普通索引(`INDEX`)
|
||||
|
||||
普通索引:最基本的索引,没有任何限制。
|
||||
|
||||
```sql
|
||||
CREATE TABLE `table` (
|
||||
...
|
||||
INDEX index_name (title(length))
|
||||
)
|
||||
```
|
||||
|
||||
#### 唯一索引(`UNIQUE`)
|
||||
|
||||
唯一索引:索引列的值必须唯一,但允许有空值。如果是组合索引,则列值的组合必须唯一。
|
||||
|
||||
```sql
|
||||
CREATE TABLE `table` (
|
||||
...
|
||||
UNIQUE indexName (title(length))
|
||||
)
|
||||
```
|
||||
|
||||
#### 主键索引(`PRIMARY`)
|
||||
|
||||
主键索引:一种特殊的唯一索引,一个表只能有一个主键,不允许有空值。一般是在建表的时候同时创建主键索引。
|
||||
|
||||
```sql
|
||||
CREATE TABLE `table` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
...
|
||||
PRIMARY KEY (`id`)
|
||||
)
|
||||
```
|
||||
|
||||
#### 组合索引
|
||||
|
||||
组合索引:多个字段上创建的索引,只有在查询条件中使用了创建索引时的第一个字段,索引才会被使用。使用组合索引时遵循最左前缀集合。
|
||||
|
||||
```sql
|
||||
CREATE TABLE `table` (
|
||||
...
|
||||
INDEX index_name (title(length), title(length), ...)
|
||||
)
|
||||
```
|
||||
|
||||
#### 全文索引
|
||||
|
||||
全文索引:主要用来查找文本中的关键字,而不是直接与索引中的值相比较。
|
||||
|
||||
全文索引跟其它索引大不相同,它更像是一个搜索引擎,而不是简单的 WHERE 语句的参数匹配。全文索引配合 `match against` 操作使用,而不是一般的 WHERE 语句加 LIKE。它可以在 `CREATE TABLE`,`ALTER TABLE` ,`CREATE INDEX` 使用,不过目前只有 `char`、`varchar`,`text` 列上可以创建全文索引。值得一提的是,在数据量较大时候,现将数据放入一个没有全局索引的表中,然后再用 `CREATE INDEX` 创建全文索引,要比先为一张表建立全文索引然后再将数据写入的速度快很多。
|
||||
|
||||
```sql
|
||||
CREATE TABLE `table` (
|
||||
`content` text CHARACTER NULL,
|
||||
...
|
||||
FULLTEXT (content)
|
||||
)
|
||||
```
|
||||
|
||||
## 三、索引的数据结构
|
||||
|
||||
### B 树索引
|
||||
|
||||
|
@ -39,11 +116,13 @@ B+ 树索引,按照顺序存储数据,所以 Mysql 可以用来做 ORDER BY
|
|||
|
||||
`B+Tree`中的 B 是指`balance`,意为平衡。需要注意的是,B+树索引并不能找到一个给定键值的具体行,它找到的只是被查找数据行所在的页,接着数据库会把页读入到内存,再在内存中进行查找,最后得到要查找的数据。
|
||||
|
||||
在介绍`B+Tree`前,先了解一下二叉查找树,它是一种经典的数据结构,其左子树的值总是小于根的值,右子树的值总是大于根的值,如下图 ①。如果要在这课树中查找值为 5 的记录,其大致流程:先找到根,其值为 6,大于 5,所以查找左子树,找到 3,而 5 大于 3,接着找 3 的右子树,总共找了 3 次。同样的方法,如果查找值为 8 的记录,也需要查找 3 次。所以二叉查找树的平均查找次数为(3 + 3 + 3 + 2 + 2 + 1) / 6 = 2.3 次,而顺序查找的话,查找值为 2 的记录,仅需要 1 次,但查找值为 8 的记录则需要 6 次,所以顺序查找的平均查找次数为:(1 + 2 + 3 + 4 + 5 + 6) / 6 = 3.3 次,因此大多数情况下二叉查找树的平均查找速度比顺序查找要快。
|
||||
#### 二叉查找树
|
||||
|
||||
在介绍`B+Tree`前,先了解一下二叉查找树,它是一种经典的数据结构,其左子树的值总是小于根的值,右子树的值总是大于根的值,如下图 ①。如果要在这课树中查找值为 5 的记录,其大致流程:先找到根,其值为 6,大于 5,所以查找左子树,找到 3,而 5 大于 3,接着找 3 的右子树,总共找了 3 次。同样的方法,如果查找值为 8 的记录,也需要查找 3 次。所以二叉查找树的平均查找次数为 $$(3 + 3 + 3 + 2 + 2 + 1) / 6 = 2.3$$ 次,而顺序查找的话,查找值为 2 的记录,仅需要 1 次,但查找值为 8 的记录则需要 6 次,所以顺序查找的平均查找次数为:$$(1 + 2 + 3 + 4 + 5 + 6) / 6 = 3.3$$ 次,因此大多数情况下二叉查找树的平均查找速度比顺序查找要快。
|
||||
|
||||
![img](https:////upload-images.jianshu.io/upload_images/175724-272c1245eba594f5.png?imageMogr2/auto-orient/strip|imageView2/2/w/618/format/webp)
|
||||
|
||||
二叉查找树和平衡二叉树
|
||||
#### 平衡二叉树
|
||||
|
||||
由于二叉查找树可以任意构造,同样的值,可以构造出如图 ② 的二叉查找树,显然这棵二叉树的查询效率和顺序查找差不多。若想二叉查找数的查询性能最高,需要这棵二叉查找树是平衡的,也即平衡二叉树(AVL 树)。
|
||||
|
||||
|
@ -51,7 +130,7 @@ B+ 树索引,按照顺序存储数据,所以 Mysql 可以用来做 ORDER BY
|
|||
|
||||
![img](https:////upload-images.jianshu.io/upload_images/175724-c806af2d9defcbab.png?imageMogr2/auto-orient/strip|imageView2/2/w/538/format/webp)
|
||||
|
||||
平衡二叉树旋转
|
||||
##### 平衡二叉树旋转
|
||||
|
||||
通过一次左旋操作就将插入后的树重新变为平衡二叉树是最简单的情况了,实际应用场景中可能需要旋转多次。至此我们可以考虑一个问题,平衡二叉树的查找效率还不错,实现也非常简单,相应的维护成本还能接受,为什么 MySQL 索引不直接使用平衡二叉树?
|
||||
|
||||
|
@ -61,15 +140,45 @@ B+ 树索引,按照顺序存储数据,所以 Mysql 可以用来做 ORDER BY
|
|||
|
||||
![img](https:////upload-images.jianshu.io/upload_images/175724-52306456815a0919.png?imageMogr2/auto-orient/strip|imageView2/2/w/993/format/webp)
|
||||
|
||||
简化 B+Tree
|
||||
#### B+ 树
|
||||
|
||||
怎么理解这两个特征?MySQL 将每个节点的大小设置为一个页的整数倍(原因下文会介绍),也就是在节点空间大小一定的情况下,每个节点可以存储更多的内结点,这样每个结点能索引的范围更大更精确。所有的叶子节点使用指针链接的好处是可以进行区间访问,比如上图中,如果查找大于 20 而小于 30 的记录,只需要找到节点 20,就可以遍历指针依次找到 25、30。如果没有链接指针的话,就无法进行区间查找。这也是 MySQL 使用`B+Tree`作为索引存储结构的重要原因。
|
||||
##### B+ 树特性
|
||||
|
||||
B+ 树索引适用于全键值、键值范围和键前缀查找,其中键前缀查找只适用于最左前缀查找。
|
||||
|
||||
InnoDB 的 B+Tree 索引分为主索引和辅助索引。
|
||||
|
||||
主索引的叶子节点 data 域记录着完整的数据记录,这种索引方式被称为聚簇索引。因为无法把数据行存放在两个不同的地方,所以一个表只能有一个聚簇索引。
|
||||
|
||||
<div align="center">
|
||||
<img src="http://upload-images.jianshu.io/upload_images/3101171-28ea7c1487bd12bb.png"/>
|
||||
</div>
|
||||
|
||||
辅助索引的叶子节点的 data 域记录着主键的值,因此在使用辅助索引进行查找时,需要先查找到主键值,然后再到主索引中进行查找。
|
||||
|
||||
<div align="center">
|
||||
<img src="http://upload-images.jianshu.io/upload_images/3101171-96c6c85468df0f89.png"/>
|
||||
</div>
|
||||
|
||||
定义一条数据记录为一个二元组 [key, data],B-Tree 是满足下列条件的数据结构:
|
||||
|
||||
- 所有叶节点具有相同的深度,也就是说 B-Tree 是平衡的;
|
||||
- 一个节点中的 key 从左到右非递减排列;
|
||||
- 如果某个指针的左右相邻 key 分别是 keyi 和 keyi+1,且不为 null,则该指针指向节点的所有 key 大于等于 keyi 且小于等于 keyi+1。
|
||||
|
||||
##### B+ 树原理
|
||||
|
||||
> B+ 树查找算法:首先在根节点进行二分查找,如果找到则返回对应节点的 data,否则在相应区间的指针指向的节点递归进行查找。
|
||||
>
|
||||
> 由于插入删除新的数据记录会破坏 B-Tree 的性质,因此在插入删除时,需要对树进行一个分裂、合并、旋转等操作以保持 B-Tree 性质。
|
||||
|
||||
MySQL 将每个节点的大小设置为一个页的整数倍(原因下文会介绍),也就是在节点空间大小一定的情况下,每个节点可以存储更多的内结点,这样每个结点能索引的范围更大更精确。所有的叶子节点使用指针链接的好处是可以进行区间访问,比如上图中,如果查找大于 20 而小于 30 的记录,只需要找到节点 20,就可以遍历指针依次找到 25、30。如果没有链接指针的话,就无法进行区间查找。这也是 MySQL 使用`B+Tree`作为索引存储结构的重要原因。
|
||||
|
||||
MySQL 为何将节点大小设置为页的整数倍,这就需要理解磁盘的存储原理。磁盘本身存取就比主存慢很多,在加上机械运动损耗(特别是普通的机械硬盘),磁盘的存取速度往往是主存的几百万分之一,为了尽量减少磁盘 I/O,磁盘往往不是严格按需读取,而是每次都会预读,即使只需要一个字节,磁盘也会从这个位置开始,顺序向后读取一定长度的数据放入内存,预读的长度一般为页的整数倍。
|
||||
|
||||
> 页是计算机管理存储器的逻辑块,硬件及 OS 往往将主存和磁盘存储区分割为连续的大小相等的块,每个存储块称为一页(许多 OS 中,页的大小通常为 4K)。主存和磁盘以页为单位交换数据。当程序要读取的数据不在主存中时,会触发一个缺页异常,此时系统会向磁盘发出读盘信号,磁盘会找到数据的起始位置并向后连续读取一页或几页载入内存中,然后一起返回,程序继续运行。
|
||||
|
||||
MySQL 巧妙利用了磁盘预读原理,将一个节点的大小设为等于一个页,这样每个节点只需要一次 I/O 就可以完全载入。为了达到这个目的,每次新建节点时,直接申请一个页的空间,这样就保证一个节点物理上也存储在一个页里,加之计算机存储分配都是按页对齐的,就实现了读取一个节点只需一次 I/O。假设`B+Tree`的高度为 h,一次检索最多需要`h-1`次 I/O(根节点常驻内存),复杂度 O(h) = O(logmN)。实际应用场景中,M 通常较大,常常超过 100,因此树的高度一般都比较小,通常不超过 3。
|
||||
MySQL 巧妙利用了磁盘预读原理,将一个节点的大小设为等于一个页,这样每个节点只需要一次 I/O 就可以完全载入。为了达到这个目的,每次新建节点时,直接申请一个页的空间,这样就保证一个节点物理上也存储在一个页里,加之计算机存储分配都是按页对齐的,就实现了读取一个节点只需一次 I/O。假设`B+Tree`的高度为 h,一次检索最多需要`h-1`次 I/O(根节点常驻内存),复杂度 $$O(h) = O(logmN)$$。实际应用场景中,M 通常较大,常常超过 100,因此树的高度一般都比较小,通常不超过 3。
|
||||
|
||||
最后简单了解下`B+Tree`节点的操作,在整体上对索引的维护有一个大概的了解,虽然索引可以大大提高查询效率,但维护索引仍要花费很大的代价,因此合理的创建索引也就尤为重要。
|
||||
|
||||
|
@ -77,31 +186,31 @@ MySQL 巧妙利用了磁盘预读原理,将一个节点的大小设为等于
|
|||
|
||||
![img](https:////upload-images.jianshu.io/upload_images/175724-a862bb909a8ed6a0.png?imageMogr2/auto-orient/strip|imageView2/2/w/950/format/webp)
|
||||
|
||||
leaf page 和 index page 都没有满
|
||||
(1)leaf page 和 index page 都没有满
|
||||
|
||||
接着插入下一个节点 70,在 Index Page 中查询后得知应该插入到 50 - 70 之间的叶子节点,但叶子节点已满,这时候就需要进行也分裂的操作,当前的叶子节点起点为 50,所以根据中间值来拆分叶子节点,如下图所示。
|
||||
|
||||
![img](https:////upload-images.jianshu.io/upload_images/175724-ce47c776928bc6b8.png?imageMogr2/auto-orient/strip|imageView2/2/w/928/format/webp)
|
||||
|
||||
Leaf Page 拆分
|
||||
(2)Leaf Page 拆分
|
||||
|
||||
最后插入一个节点 95,这时候 Index Page 和 Leaf Page 都满了,就需要做两次拆分,如下图所示。
|
||||
|
||||
![img](https:////upload-images.jianshu.io/upload_images/175724-33cee181795b3dcc.png?imageMogr2/auto-orient/strip|imageView2/2/w/909/format/webp)
|
||||
|
||||
Leaf Page 与 Index Page 拆分
|
||||
(3)Leaf Page 与 Index Page 拆分
|
||||
|
||||
拆分后最终形成了这样一颗树。
|
||||
|
||||
![img](https:////upload-images.jianshu.io/upload_images/175724-5289c6ec5d11216e.png?imageMogr2/auto-orient/strip|imageView2/2/w/986/format/webp)
|
||||
|
||||
最终树
|
||||
(4)最终树
|
||||
|
||||
`B+Tree`为了保持平衡,对于新插入的值需要做大量的拆分页操作,而页的拆分需要 I/O 操作,为了尽可能的减少页的拆分操作,`B+Tree`也提供了类似于平衡二叉树的旋转功能。当 Leaf Page 已满但其左右兄弟节点没有满的情况下,`B+Tree`并不急于去做拆分操作,而是将记录移到当前所在页的兄弟节点上。通常情况下,左兄弟会被先检查用来做旋转操作。就比如上面第二个示例,当插入 70 的时候,并不会去做页拆分,而是左旋操作。
|
||||
|
||||
![img](https:////upload-images.jianshu.io/upload_images/175724-bafd2fbc93cf45ae.png?imageMogr2/auto-orient/strip|imageView2/2/w/739/format/webp)
|
||||
|
||||
左旋操作
|
||||
(5)左旋操作
|
||||
|
||||
通过旋转操作可以最大限度的减少页分裂,从而减少索引维护过程中的磁盘的 I/O 操作,也提高索引维护效率。需要注意的是,删除节点跟插入节点类似,仍然需要旋转和拆分操作,这里就不再说明。
|
||||
|
||||
|
@ -125,12 +234,20 @@ CREATE TABLE People(
|
|||
|
||||
### 哈希索引
|
||||
|
||||
InnoDB 引擎有一个特殊的功能叫“自适应哈希索引”,当某个索引值被使用的非常频繁时,会在 B+Tree 索引之上再创建一个哈希索引,这样就让 B+Tree 索引具有哈希索引的一些优点,比如快速的哈希查找。
|
||||
> Hash 索引只有精确匹配索引所有列的查询才有效。
|
||||
|
||||
哈希索引能以 O(1) 时间进行查找,但是失去了有序性,它具有以下限制:
|
||||
对于每一行数据,对所有的索引列计算一个 `hashcode`。哈希索引将所有的 `hashcode` 存储在索引中,同时在 Hash 表中保存指向每个数据行的指针。
|
||||
|
||||
- 无法用于排序与分组;
|
||||
- 只支持精确查找,无法用于部分查找和范围查找;
|
||||
哈希结构索引的优点:
|
||||
|
||||
- 因为索引数据结构紧凑,所以查询速度非常快。
|
||||
|
||||
哈希结构索引的缺点:
|
||||
|
||||
- 哈希索引数据不是按照索引值顺序存储的,所以无法用于排序。
|
||||
- 哈希索引不支持部分索引匹配查找。如,在数据列 (A,B) 上建立哈希索引,如果查询只有数据列 A,无法使用该索引。
|
||||
- 哈希索引只支持等值比较查询,不支持任何范围查询,如 WHERE price > 100。
|
||||
- 哈希索引有可能出现哈希冲突,出现哈希冲突时,必须遍历链表中所有的行指针,逐行比较,直到找到符合条件的行。
|
||||
|
||||
### 全文索引
|
||||
|
||||
|
@ -152,17 +269,103 @@ MyISAM 存储引擎支持空间数据索引(R-Tree),可以用于地理数
|
|||
|
||||
聚簇表示数据行和相邻的键紧凑地存储在一起。
|
||||
|
||||
![](https://raw.githubusercontent.com/dunwu/images/master/snap/20200304235424.jpg)
|
||||
- **聚集索引**(`Clustered`):表中各行的物理顺序与键值的逻辑(索引)顺序相同,每个表只能有一个。
|
||||
- **非聚集索引**(`Non-clustered`):非聚集索引指定表的逻辑顺序,也可以视为二级索引。数据存储在一个位置,索引存储在另一个位置,索引中包含指向数据存储位置的指针。可以有多个,小于 249 个。
|
||||
|
||||
![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200304235424.jpg)
|
||||
|
||||
如上图所示,InnoDB 的聚簇索引,其叶子节点包含了行的全部数据,而非叶子节点则包含了索引列。
|
||||
|
||||
如果没有定义主键,InnoDB 会选择一个唯一的非空索引代替。如果没有这样的索引,InnoDB 会隐式定义一个主键来作为聚簇索引。
|
||||
|
||||
## 四、索引策略
|
||||
## 四、索引的策略
|
||||
|
||||
### 索引基本原则
|
||||
|
||||
- 索引不是越多越好,不要为所有列都创建索引。
|
||||
- 要尽量避免冗余和重复索引;
|
||||
- 要考虑删除未使用的索引;
|
||||
- 尽量的扩展索引,不要新建索引;
|
||||
- 频繁作为 `WHERE` 过滤条件的列应该考虑添加索引
|
||||
|
||||
### 独立的列
|
||||
|
||||
**如果查询中的列不是独立的列,则数据库不会使用索引**。
|
||||
|
||||
“独立的列” 是指索引列不能是表达式的一部分,也不能是函数的参数。
|
||||
|
||||
❌ 错误示例:
|
||||
|
||||
```sql
|
||||
SELECT actor_id FROM actor WHERE actor_id + 1 = 5;
|
||||
SELECT ... WHERE TO_DAYS(current_date) - TO_DAYS(date_col) <= 10;
|
||||
```
|
||||
|
||||
### 前缀索引
|
||||
|
||||
有时候需要索引很长的字符列,这会让索引变得大且慢。
|
||||
|
||||
解决方法是:可以索引开始的部分字符,这样可以大大节约索引空间,从而提高索引效率。但这样也会降低索引的选择性。
|
||||
|
||||
**索引的选择性**是指:不重复的索引值和数据表记录总数的比值。最大值为 1,此时每个记录都有唯一的索引与其对应。选择性越高,查询效率也越高。
|
||||
|
||||
对于 `BLOB`/`TEXT`/`VARCHAR` 这种文本类型的列,必须使用前缀索引,因为数据库往往不允许索引这些列的完整长度。
|
||||
|
||||
要选择足够长的前缀以保证较高的选择性,同时又不能太长(节约空间)。
|
||||
|
||||
❌ 低效示例:
|
||||
|
||||
```sql
|
||||
SELECT COUNT(*) AS cnt, city FROM sakila.city_demo
|
||||
GROUP BY city ORDER BY cnt DESC LIMIT 10;
|
||||
```
|
||||
|
||||
✔ 高效示例:
|
||||
|
||||
```sql
|
||||
SELECT COUNT(*) AS cnt, LEFT(city, 3) AS pref FROM sakila.city_demo
|
||||
GROUP BY city ORDER BY cnt DESC LIMIT 10;
|
||||
```
|
||||
|
||||
### 多列索引
|
||||
|
||||
**不要为每个列都创建独立索引**。
|
||||
|
||||
**将选择性高的列或基数大的列优先排在多列索引最前列**。但有时,也需要考虑 WHERE 子句中的排序、分组和范围条件等因素,这些因素也会对查询性能造成较大影响。
|
||||
|
||||
举例来说,有一张 user 表,其中含 name, sex, age 三个列,如果将这三者组合为多列索引,应该用什么样的顺序呢?从选择性高的角度来看:`name > age > sex`。
|
||||
|
||||
### 聚簇索引
|
||||
|
||||
聚簇索引不是一种单独的索引类型,而是一种数据存储方式。具体细节依赖于实现方式。如 **InnoDB 的聚簇索引实际是在同一个结构中保存了 B 树的索引和数据行**。
|
||||
|
||||
**聚簇表示数据行和相邻的键值紧凑地存储在一起,因为数据紧凑,所以访问快**。因为无法同时把数据行存放在两个不同的地方,所以**一个表只能有一个聚簇索引**。
|
||||
|
||||
若没有定义主键,InnoDB 会隐式定义一个主键来作为聚簇索引。
|
||||
|
||||
### 覆盖索引
|
||||
|
||||
索引包含所有需要查询的字段的值。
|
||||
|
||||
具有以下优点:
|
||||
|
||||
- 因为索引条目通常远小于数据行的大小,所以若只读取索引,能大大减少数据访问量。
|
||||
- 一些存储引擎(例如 MyISAM)在内存中只缓存索引,而数据依赖于操作系统来缓存。因此,只访问索引可以不使用系统调用(通常比较费时)。
|
||||
- 对于 InnoDB 引擎,若辅助索引能够覆盖查询,则无需访问主索引。
|
||||
|
||||
### 使用索引扫描来做排序
|
||||
|
||||
Mysql 有两种方式可以生成排序结果:通过排序操作;或者按索引顺序扫描。
|
||||
|
||||
**索引最好既满足排序,又用于查找行**。这样,就可以使用索引来对结果排序。
|
||||
|
||||
### 最左前缀匹配原则
|
||||
|
||||
mysql 会一直向右匹配直到遇到范围查询(>、<、between、like)就停止匹配。
|
||||
MySQL 会一直向右匹配直到遇到范围查询 `(>,<,BETWEEN,LIKE)` 就停止匹配。
|
||||
|
||||
- 索引可以简单如一个列(a),也可以复杂如多个列(a, b, c, d),即**联合索引**。
|
||||
- 如果是联合索引,那么 key 也由多个列组成,同时,索引只能用于查找 key 是否**存在(相等)**,遇到范围查询(>、<、between、like 左匹配)等就**不能进一步匹配**了,后续退化为线性查找。
|
||||
- 因此,**列的排列顺序决定了可命中索引的列数**。
|
||||
|
||||
例如:`a = 1 and b = 2 and c > 3 and d = 4`,如果建立(a,b,c,d)顺序的索引,d 是用不到索引的,如果建立(a,b,d,c)的索引则都可以用到,a,b,d 的顺序可以任意调整。
|
||||
|
||||
|
@ -185,47 +388,12 @@ customer_id_selectivity: 0.0373
|
|||
|
||||
### = 和 in 可以乱序
|
||||
|
||||
比如 a = 1 and b = 2 and c = 3 建立(a,b,c)索引可以任意顺序,mysql 的查询优化器会帮你优化成索引可以识别的形式。
|
||||
**不需要考虑=、in 等的顺序**,Mysql 会自动优化这些条件的顺序,以匹配尽可能多的索引列。
|
||||
|
||||
### 索引列不能参与计算
|
||||
|
||||
在进行查询时,索引列不能是表达式的一部分,也不能是函数的参数,否则无法使用索引。
|
||||
|
||||
例如下面的查询不能使用 actor_id 列的索引:
|
||||
|
||||
```sql
|
||||
SELECT actor_id FROM sakila.actor WHERE actor_id + 1 = 5;
|
||||
```
|
||||
|
||||
### 尽量的扩展索引,不要新建索引
|
||||
|
||||
比如表中已经有 a 的索引,现在要加(a,b)的索引,那么只需要修改原来的索引即可。
|
||||
|
||||
### 多列索引
|
||||
|
||||
在需要使用多个列作为条件进行查询时,使用多列索引比使用多个单列索引性能更好。例如下面的语句中,最好把 actor_id 和 film_id 设置为多列索引。
|
||||
|
||||
```sql
|
||||
SELECT film_id, actor_ id FROM sakila.film_actor
|
||||
WhERE actor_id = 1 AND film_id = 1;
|
||||
```
|
||||
|
||||
### 前缀索引
|
||||
|
||||
对于 BLOB、TEXT 和 VARCHAR 类型的列,必须使用前缀索引,只索引开始的部分字符。
|
||||
|
||||
对于前缀长度的选取需要根据索引选择性来确定。
|
||||
|
||||
### 覆盖索引
|
||||
|
||||
索引包含所有需要查询的字段的值。
|
||||
|
||||
具有以下优点:
|
||||
|
||||
- 因为索引条目通常远小于数据行的大小,所以若只读取索引,能大大减少数据访问量。
|
||||
- 一些存储引擎(例如 MyISAM)在内存中只缓存索引,而数据依赖于操作系统来缓存。因此,只访问索引可以不使用系统调用(通常比较费时)。
|
||||
- 对于 InnoDB 引擎,若辅助索引能够覆盖查询,则无需访问主索引。
|
||||
例子:如有索引(a, b, c, d),查询条件 c > 3 and b = 2 and a = 1 and d < 4 与 a = 1 and c > 3 and b = 2 and d < 4 等顺序都是可以的,MySQL 会自动优化为 a = 1 and b = 2 and c > 3 and d < 4,依次命中 a、b、c。
|
||||
|
||||
## 参考资料
|
||||
|
||||
- [《高性能 MySQL》](https://book.douban.com/subject/23008813/)
|
||||
- [数据库两大神器【索引和锁】](https://juejin.im/post/5b55b842f265da0f9e589e79)
|
||||
- [MySQL 索引背后的数据结构及算法原理](http://blog.codinglabs.org/articles/theory-of-mysql-index.html)
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
# Mysql 并发控制
|
||||
# Mysql 锁
|
||||
|
||||
## 乐观锁和悲观锁
|
||||
|
||||
确保在多个事务同时存取数据库中同一数据时不破坏事务的隔离性和统一性以及数据库的统一性,**乐观锁和悲观锁是并发控制主要采用的技术手段。**
|
||||
|
||||
- **`悲观锁`** - 假定会发生并发冲突,屏蔽一切可能违反数据完整性的操作
|
||||
- 在查询完数据的时候就把事务锁起来,直到提交事务(COMMIT)
|
||||
- 在查询完数据的时候就把事务锁起来,直到提交事务(`COMMIT`)
|
||||
- 实现方式:**使用数据库中的锁机制**。
|
||||
- **`乐观锁`** - 假设不会发生并发冲突,只在提交操作时检查是否违反数据完整性。
|
||||
- 在修改数据的时候把事务锁起来,通过 version 的方式来进行锁定
|
||||
|
@ -18,24 +18,26 @@
|
|||
- **表级锁(table lock)** - 锁定整张表。用户对表进行写操作前,需要先获得写锁,这会阻塞其他用户对该表的所有读写操作。只有没有写锁时,其他用户才能获得读锁,读锁之间不会相互阻塞。
|
||||
- **行级锁(row lock)** - 锁定指定的行记录。这样其它进程还是可以对同一个表中的其它记录进行操作。
|
||||
|
||||
应该尽量只锁定需要修改的那部分数据,而不是所有的资源。锁定的数据量越少,发生锁争用的可能就越小,系统的并发程度就越高。但是加锁需要消耗资源,锁的各种操作(包括获取锁、释放锁、以及检查锁状态)都会增加系统开销。因此封锁粒度越小,系统开销就越大。
|
||||
应该尽量只锁定需要修改的那部分数据,而不是所有的资源。**锁定的数据量越少,锁竞争的发生频率就越小,系统的并发程度就越高**。但是加锁需要消耗资源,锁的各种操作(包括获取锁、释放锁、以及检查锁状态)都会增加系统开销。因此**锁粒度越小,系统开销就越大**。
|
||||
|
||||
在选择封锁粒度时,需要在锁开销和并发程度之间做一个权衡。
|
||||
在选择锁粒度时,需要在锁开销和并发程度之间做一个权衡。
|
||||
|
||||
在 `InnoDB` 中,行锁是通过给索引上的索引项加锁来实现的。**如果没有索引,`InnoDB` 将会通过隐藏的聚簇索引来对记录加锁**。
|
||||
在 `InnoDB` 中,**行锁是通过给索引上的索引项加锁来实现的**。**如果没有索引,`InnoDB` 将会通过隐藏的聚簇索引来对记录加锁**。
|
||||
|
||||
## 读写锁
|
||||
|
||||
- 独享锁(Exclusive),简写为 X 锁,又称写锁。使用方式:`SELECT ... FOR UPDATE;`
|
||||
- 共享锁(Shared),简写为 S 锁,又称读锁。使用方式:`SELECT ... LOCK IN SHARE MODE;`
|
||||
|
||||
写锁和读锁的关系,简言之:独享锁存在,其他事务就不能做任何操作。
|
||||
写锁和读锁的关系,简言之:**独享锁存在,其他事务就不能做任何操作**。
|
||||
|
||||
`InnoDB` 下的行锁、间隙锁、next-key 锁统统属于独享锁。
|
||||
**`InnoDB` 下的行锁、间隙锁、next-key 锁统统属于独享锁**。
|
||||
|
||||
### 意向锁
|
||||
## 意向锁
|
||||
|
||||
使用意向锁(Intention Locks)可以更容易地支持多粒度封锁。
|
||||
**当存在表级锁和行级锁的情况下,必须先申请意向锁(表级锁,但不是真的加锁),再获取行级锁**。使用意向锁(Intention Locks)可以更容易地支持多粒度封锁。
|
||||
|
||||
**意向锁是 `InnoDB` 自动加的,不需要用户干预**。
|
||||
|
||||
在存在行级锁和表级锁的情况下,事务 T 想要对表 A 加 X 锁,就需要先检测是否有其它事务对表 A 或者表 A 中的任意一行加了锁,那么就需要对表 A 的每一行都检测一次,这是非常耗时的。
|
||||
|
||||
|
@ -62,30 +64,31 @@
|
|||
- 任意 IS/IX 锁之间都是兼容的,因为它们只表示想要对表加锁,而不是真正加锁;
|
||||
- 这里兼容关系针对的是表级锁,而表级的 IX 锁和行级的 X 锁兼容,两个事务可以对两个数据行加 X 锁。(事务 T1 想要对数据行 R1 加 X 锁,事务 T2 想要对同一个表的数据行 R2 加 X 锁,两个事务都需要对该表加 IX 锁,但是 IX 锁是兼容的,并且 IX 锁与行级的 X 锁也是兼容的,因此两个事务都能加锁成功,对同一个表中的两个数据行做修改。)
|
||||
|
||||
**意向锁是 `InnoDB` 自动加的,不需要用户干预**。
|
||||
|
||||
## MVCC
|
||||
|
||||
不仅是 Mysql,包括 Oracle、PostgreSQL 等其他数据库都实现了各自的 MVCC,实现机制没有统一标准。
|
||||
|
||||
多版本并发控制(Multi-Version Concurrency Control, MVCC)可以视为行级锁的一个变种。它在很多情况下都避免了加锁操作,因此开销更低。
|
||||
|
||||
是 `InnoDB` 存储引擎实现隔离级别的一种具体方式,**用于实现提交读和可重复读这两种隔离级别**。而未提交读隔离级别总是读取最新的数据行,要求很低,无需使用 MVCC。可串行化隔离级别需要对所有读取的行都加锁,单纯使用 MVCC 无法实现。
|
||||
MVCC 是 `InnoDB` 存储引擎实现隔离级别的一种具体方式,**用于实现提交读和可重复读这两种隔离级别**。而未提交读隔离级别总是读取最新的数据行,要求很低,无需使用 MVCC。可串行化隔离级别需要对所有读取的行都加锁,单纯使用 MVCC 无法实现。
|
||||
|
||||
### 基本思想
|
||||
|
||||
在数据库锁一节中提到,加锁能解决多个事务同时执行时出现的并发一致性问题。在实际场景中读操作往往多于写操作,因此又引入了读写锁来避免不必要的加锁操作,例如读和读没有互斥关系。读写锁中读和写操作仍然是互斥的。
|
||||
加锁能解决多个事务同时执行时出现的并发一致性问题。在实际场景中读操作往往多于写操作,因此又引入了读写锁来避免不必要的加锁操作,例如读和读没有互斥关系。读写锁中读和写操作仍然是互斥的。
|
||||
|
||||
MVCC 的思想是:保存数据在某个时间点的快照。**写操作更新最新的版本快照,而读操作去读旧版本快照,没有互斥关系**,这一点和 `CopyOnWrite` 类似。
|
||||
MVCC 的思想是:
|
||||
|
||||
#### 版本号
|
||||
- 保存数据在某个时间点的快照。**写操作(DELETE、INSERT、UPDATE)更新最新的版本快照,而读操作去读旧版本快照,没有互斥关系**,这一点和 `CopyOnWrite` 类似。
|
||||
- 脏读和不可重复读最根本的原因是**事务读取到其它事务未提交的修改**。在事务进行读取操作时,为了解决脏读和不可重复读问题,**MVCC 规定只能读取已经提交的快照**。当然一个事务可以读取自身未提交的快照,这不算是脏读。
|
||||
|
||||
### 版本号
|
||||
|
||||
InnoDB 的 MVCC 实现是:在每行记录后面保存两个隐藏列,一个列保存行的创建时间,另一个列保存行的过期时间(这里的时间是指系统版本号)。每开始一个新事务,系统版本号会自动递增,事务开始时刻的系统版本号会作为事务的版本号,用来和查询到的每行记录的版本号进行比较。
|
||||
|
||||
- 系统版本号 SYS_ID:是一个递增的数字,每开始一个新的事务,系统版本号就会自动递增。
|
||||
- 事务版本号 TRX_ID :事务开始时的系统版本号。
|
||||
- 系统版本号 `SYS_ID`:是一个递增的数字,每开始一个新的事务,系统版本号就会自动递增。
|
||||
- 事务版本号 `TRX_ID` :事务开始时的系统版本号。
|
||||
|
||||
#### Undo 日志
|
||||
### Undo 日志
|
||||
|
||||
MVCC 的多版本指的是多个版本的快照,快照存储在 Undo 日志中,该日志通过回滚指针 ROLL_PTR 把一个数据行的所有快照连接起来。
|
||||
|
||||
|
@ -101,9 +104,9 @@ UPDATE t SET x="c" WHERE id=1;
|
|||
|
||||
`INSERT`、`UPDATE`、`DELETE` 操作会创建一个日志,并将事务版本号 `TRX_ID` 写入。`DELETE` 可以看成是一个特殊的 `UPDATE`,还会额外将 DEL 字段设置为 1。
|
||||
|
||||
#### ReadView
|
||||
### ReadView
|
||||
|
||||
MVCC 维护了一个 `ReadView` 结构,主要包含了当前系统未提交的事务列表 TRX_IDs {TRX_ID_1, TRX_ID_2, ...},还有该列表的最小值 `TRX_ID_MIN` 和 `TRX_ID_MAX`。
|
||||
MVCC 维护了一个 `ReadView` 结构,主要包含了当前系统未提交的事务列表 `TRX_IDs {TRX_ID_1, TRX_ID_2, ...}`,还有该列表的最小值 `TRX_ID_MIN` 和 `TRX_ID_MAX`。
|
||||
|
||||
在进行 `SELECT` 操作时,根据数据行快照的 `TRX_ID` 与 `TRX_ID_MIN` 和 `TRX_ID_MAX` 之间的关系,从而判断数据行快照是否可以使用:
|
||||
|
||||
|
@ -115,9 +118,9 @@ MVCC 维护了一个 `ReadView` 结构,主要包含了当前系统未提交的
|
|||
|
||||
在数据行快照不可使用的情况下,需要沿着 Undo Log 的回滚指针 ROLL_PTR 找到下一个快照,再进行上面的判断。
|
||||
|
||||
#### 快照读与当前读
|
||||
### 快照读与当前读
|
||||
|
||||
##### 快照读
|
||||
快照读
|
||||
|
||||
MVCC 的 SELECT 操作是快照中的数据,不需要进行加锁操作。
|
||||
|
||||
|
@ -125,7 +128,7 @@ MVCC 的 SELECT 操作是快照中的数据,不需要进行加锁操作。
|
|||
SELECT * FROM table ...;
|
||||
```
|
||||
|
||||
##### 当前读
|
||||
当前读
|
||||
|
||||
MVCC 其它会对数据库进行修改的操作(INSERT、UPDATE、DELETE)需要进行加锁操作,从而读取最新的数据。可以看到 MVCC 并不是完全不用加锁,而只是避免了 SELECT 的加锁操作。
|
||||
|
||||
|
@ -142,11 +145,11 @@ SELECT * FROM table WHERE ? lock in share mode;
|
|||
SELECT * FROM table WHERE ? for update;
|
||||
```
|
||||
|
||||
### Next-key 锁
|
||||
## Next-key 锁
|
||||
|
||||
Next-Key 锁是 MySQL 的 `InnoDB` 存储引擎的一种锁实现。
|
||||
|
||||
MVCC 不能解决幻影读问题,Next-Key 锁就是为了解决这个问题而存在的。在可重复读(REPEATABLE READ)隔离级别下,使用 **MVCC + Next-Key 锁** 可以解决幻读问题。
|
||||
MVCC 不能解决幻读问题,**Next-Key 锁就是为了解决幻读问题**。在可重复读(`REPEATABLE READ`)隔离级别下,使用 **MVCC + Next-Key 锁** 可以解决幻读问题。
|
||||
|
||||
另外,根据针对 SQL 语句检索条件的不同,加锁又有以下三种情形需要我们掌握。
|
||||
|
||||
|
@ -161,3 +164,5 @@ MVCC 不能解决幻影读问题,Next-Key 锁就是为了解决这个问题而
|
|||
## 参考资料
|
||||
|
||||
- [《高性能 MySQL》](https://book.douban.com/subject/23008813/)
|
||||
- [数据库系统原理](https://github.com/CyC2018/Interview-Notebook/blob/master/notes/数据库系统原理.md)
|
||||
- [数据库两大神器【索引和锁】](https://juejin.im/post/5b55b842f265da0f9e589e79)
|
|
@ -164,6 +164,18 @@ CREATE USER 'pig'@'%' IDENTIFIED BY '';
|
|||
CREATE USER 'pig'@'%';
|
||||
```
|
||||
|
||||
> 注意:在 Mysql 8 中,默认密码验证不再是 `password`。所以在创建用户时,`create user 'username'@'%' identified by 'password';` 客户端是无法连接服务的。
|
||||
>
|
||||
> 所以,需要加上 `IDENTIFIED WITH mysql_native_password`,例如:`CREATE USER 'slave'@'%' IDENTIFIED WITH mysql_native_password BY '123456';`
|
||||
|
||||
### 查看用户
|
||||
|
||||
```sql
|
||||
-- 查看所有用户
|
||||
SELECT DISTINCT CONCAT('User: ''', user, '''@''', host, ''';') AS query
|
||||
FROM mysql.user;
|
||||
```
|
||||
|
||||
### 授权
|
||||
|
||||
命令:
|
||||
|
@ -191,7 +203,10 @@ GRANT ALL ON maindataplus.* TO 'pig'@'%';
|
|||
用以上命令授权的用户不能给其它用户授权,如果想让该用户可以授权,用以下命令:
|
||||
|
||||
```sql
|
||||
-- 为指定用户配置指定权限
|
||||
GRANT privileges ON databasename.tablename TO 'username'@'host' WITH GRANT OPTION;
|
||||
-- 为 root 用户分配所有权限
|
||||
GRANT ALL ON *.* TO 'root'@'%' IDENTIFIED BY '密码' WITH GRANT OPTION;
|
||||
```
|
||||
|
||||
### 撤销授权
|
||||
|
@ -218,6 +233,13 @@ REVOKE SELECT ON *.* FROM 'pig'@'%';
|
|||
|
||||
具体信息可以用命令`SHOW GRANTS FOR 'pig'@'%';` 查看。
|
||||
|
||||
### 查看授权
|
||||
|
||||
```SQL
|
||||
-- 查看用户权限
|
||||
SHOW GRANTS FOR 'root'@'%';
|
||||
```
|
||||
|
||||
### 更改用户密码
|
||||
|
||||
```sql
|
||||
|
@ -273,7 +295,7 @@ mysqldump -u <username> -p --all-databases > backup.sql
|
|||
|
||||
#### 恢复一个数据库
|
||||
|
||||
Mysql 恢复数据使用 mysqldump 命令。
|
||||
Mysql 恢复数据使用 mysql 命令。
|
||||
|
||||
语法:
|
||||
|
||||
|
@ -347,12 +369,40 @@ Password:
|
|||
执行以下 SQL:
|
||||
|
||||
```sql
|
||||
-- 创建 slave1 用户,并指定该用户只能在主机 192.168.8.11 上登录
|
||||
mysql> CREATE USER 'slave1'@'192.168.8.11' IDENTIFIED WITH mysql_native_password BY '密码';
|
||||
-- 为 slave1 赋予 REPLICATION SLAVE 权限
|
||||
mysql> GRANT REPLICATION SLAVE ON *.* TO 'slave1'@'192.168.8.11';
|
||||
-- a. 创建 slave 用户
|
||||
CREATE USER 'slave'@'%' IDENTIFIED WITH mysql_native_password BY '密码';
|
||||
-- 为 slave 赋予 REPLICATION SLAVE 权限
|
||||
GRANT REPLICATION SLAVE ON *.* TO 'slave'@'%';
|
||||
|
||||
-- b. 或者,创建 slave 用户,并指定该用户能在任意主机上登录
|
||||
-- 如果有多个从节点,又想让所有从节点都使用统一的用户名、密码认证,可以考虑这种方式
|
||||
CREATE USER 'slave'@'%' IDENTIFIED WITH mysql_native_password BY '密码';
|
||||
GRANT REPLICATION SLAVE ON *.* TO 'slave'@'%';
|
||||
|
||||
-- 刷新授权表信息
|
||||
mysql> FLUSH PRIVILEGES;
|
||||
FLUSH PRIVILEGES;
|
||||
```
|
||||
|
||||
> 注意:在 Mysql 8 中,默认密码验证不再是 `password`。所以在创建用户时,`create user 'username'@'%' identified by 'password';` 客户端是无法连接服务的。所以,需要加上 `IDENTIFIED WITH mysql_native_password BY 'password'`
|
||||
|
||||
补充用户管理 SQL:
|
||||
|
||||
```sql
|
||||
-- 查看所有用户
|
||||
SELECT DISTINCT CONCAT('User: ''', user, '''@''', host, ''';') AS query
|
||||
FROM mysql.user;
|
||||
|
||||
-- 查看用户权限
|
||||
SHOW GRANTS FOR 'root'@'%';
|
||||
|
||||
-- 创建用户
|
||||
-- a. 创建 slave 用户,并指定该用户只能在主机 192.168.8.11 上登录
|
||||
CREATE USER 'slave'@'192.168.8.11' IDENTIFIED WITH mysql_native_password BY '密码';
|
||||
-- 为 slave 赋予 REPLICATION SLAVE 权限
|
||||
GRANT REPLICATION SLAVE ON *.* TO 'slave'@'192.168.8.11';
|
||||
|
||||
-- 删除用户
|
||||
DROP USER 'slave'@'192.168.8.11';
|
||||
```
|
||||
|
||||
(3)加读锁
|
||||
|
@ -435,17 +485,20 @@ Password:
|
|||
|
||||
```sql
|
||||
-- 停止从节点服务
|
||||
mysql> STOP SLAVE;
|
||||
STOP SLAVE;
|
||||
|
||||
mysql> CHANGE MASTER TO
|
||||
-> MASTER_HOST='192.168.8.10',
|
||||
-> MASTER_USER='slave1',
|
||||
-> MASTER_PASSWORD='密码6',
|
||||
-> MASTER_LOG_FILE='binlog.000001',
|
||||
-> MASTER_LOG_POS=4202;
|
||||
-- 注意:MASTER_USER 和
|
||||
CHANGE MASTER TO
|
||||
MASTER_HOST='192.168.8.10',
|
||||
MASTER_USER='slave',
|
||||
MASTER_PASSWORD='密码',
|
||||
MASTER_LOG_FILE='binlog.000001',
|
||||
MASTER_LOG_POS=4202;
|
||||
```
|
||||
|
||||
`MASTER_LOG_FILE` 和 `MASTER_LOG_POS` 参数要分别与 `show master status` 指令获得的 `File` 和 `Position` 属性值对应。
|
||||
- `MASTER_LOG_FILE` 和 `MASTER_LOG_POS` 参数要分别与 `show master status` 指令获得的 `File` 和 `Position` 属性值对应。
|
||||
- `MASTER_HOST` 是主节点的 HOST。
|
||||
- `MASTER_USER` 和 `MASTER_PASSWORD` 是在主节点上注册的用户及密码。
|
||||
|
||||
(4)启动 slave 进程
|
||||
|
||||
|
@ -484,7 +537,7 @@ mysql> show global variables like "%read_only%";
|
|||
|
||||
## 三、配置
|
||||
|
||||
> ***大部分情况下,默认的基本配置已经足够应付大多数场景,不要轻易修改 Mysql 服务器配置,除非你明确知道修改项是有益的。***
|
||||
> **_大部分情况下,默认的基本配置已经足够应付大多数场景,不要轻易修改 Mysql 服务器配置,除非你明确知道修改项是有益的。_**
|
||||
|
||||
### 配置文件路径
|
||||
|
||||
|
|
|
@ -450,6 +450,20 @@ MySQL 总是通过创建并填充临时表的方式来执行 `UNION` 查询。
|
|||
|
||||
除非确实需要服务器去重,否则就一定要使用`UNION ALL`,如果没有`ALL`关键字,MySQL 会给临时表加上`DISTINCT`选项,这会导致整个临时表的数据做唯一性检查,这样做的代价非常高。当然即使使用 ALL 关键字,MySQL 总是将结果放入临时表,然后再读出,再返回给客户端。虽然很多时候没有这个必要,比如有时候可以直接把每个子查询的结果返回给客户端。
|
||||
|
||||
## 五、执行计划
|
||||
|
||||
如何检验修改后的 SQL 确实有优化效果?这就需要用到执行计划(`EXPLAIN`)。
|
||||
|
||||
使用执行计划 `EXPLAIN` 用来分析 `SELECT` 查询效率,开发人员可以通过分析 `EXPLAIN` 结果来优化查询语句。
|
||||
|
||||
比较重要的字段有:
|
||||
|
||||
- `select_type` - 查询类型,有简单查询、联合查询、子查询等
|
||||
- `key` - 使用的索引
|
||||
- `rows` - 扫描的行数
|
||||
|
||||
> 更多内容请参考:[MySQL 性能优化神器 Explain 使用分析](https://segmentfault.com/a/1190000008131735)
|
||||
|
||||
## 参考资料
|
||||
|
||||
- [《高性能 MySQL》](https://book.douban.com/subject/23008813/)
|
||||
|
|
|
@ -1,14 +1,12 @@
|
|||
# Mysql 基本原理
|
||||
|
||||
> 关键词:存储引擎,数据类型,事务,MVCC,索引,执行计划,主从复制
|
||||
## 一、存储引擎
|
||||
|
||||
## 1. 存储引擎
|
||||
在文件系统中,Mysql 将每个数据库(也可以成为 schema)保存为数据目录下的一个子目录。创建表示,Mysql 会在数据库子目录下创建一个和表同名的 `.frm` 文件保存表的定义。因为 Mysql 使用文件系统的目录和文件来保存数据库和表的定义,大小写敏感性和具体平台密切相关。Windows 中大小写不敏感;类 Unix 中大小写敏感。**不同的存储引擎保存数据和索引的方式是不同的,但表的定义则是在 Mysql 服务层统一处理的。**
|
||||
|
||||
在文件系统中,Mysql 将每个数据库(也可以成为 schema)保存为数据目录下的一个子目录。创建表示,Mysql 会在数据库子目录下创建一个和表同名的 .frm 文件保存表的定义。因为 Mysql 使用文件系统的目录和文件来保存数据库和表的定义,大小写敏感性和具体平台密切相关。Windows 中大小写不敏感;类 Unix 中大小写敏感。**不同的存储引擎保存数据和索引的方式是不同的,但表的定义则是在 Mysql 服务层统一处理的。**
|
||||
### 选择存储引擎
|
||||
|
||||
### 1.1. 选择存储引擎
|
||||
|
||||
#### 1.1.1. Mysql 内置的存储引擎
|
||||
#### Mysql 内置的存储引擎
|
||||
|
||||
```shell
|
||||
mysql> SHOW ENGINES;
|
||||
|
@ -34,20 +32,20 @@ mysql> SHOW ENGINES;
|
|||
- **Memory** - 适合快速访问数据,且数据不会被修改,重启丢失也没有关系。
|
||||
- **NDB** - 用于 Mysql 集群场景。
|
||||
|
||||
#### 1.1.2. 如何选择合适的存储引擎
|
||||
#### 如何选择合适的存储引擎
|
||||
|
||||
大多数情况下,InnoDB 都是正确的选择,除非需要用到 InnoDB 不具备的特性。
|
||||
|
||||
如果应用需要选择 InnoDB 以外的存储引擎,可以考虑以下因素:
|
||||
|
||||
- 事务:如果需要支持事务,InnoDB 是首选。如果不需要支持事务,且主要是 SELECT 和 INSERT 操作,MyISAM 是不错的选择。
|
||||
- 事务:如果需要支持事务,InnoDB 是首选。如果不需要支持事务,且主要是 SELECT 和 INSERT 操作,MyISAM 是不错的选择。所以,如果 Mysql 部署方式为主备模式,并进行读写分离。那么可以这么做:主节点只支持写操作,默认引擎为 InnoDB;备节点只支持读操作,默认引擎为 MyISAM。
|
||||
- 并发:MyISAM 只支持表级锁,而 InnoDB 还支持行级锁。所以,InnoDB 并发性能更高。
|
||||
- 外键:InnoDB 支持外键。
|
||||
- 备份:InnoDB 支持在线热备份。
|
||||
- 崩溃恢复:MyISAM 崩溃后发生损坏的概率比 InnoDB 高很多,而且恢复的速度也更慢。
|
||||
- 其它特性:MyISAM 支持压缩表和空间数据索引。
|
||||
|
||||
#### 1.1.3. 转换表的存储引擎
|
||||
#### 转换表的存储引擎
|
||||
|
||||
下面的语句可以将 mytable 表的引擎修改为 InnoDB
|
||||
|
||||
|
@ -55,7 +53,7 @@ mysql> SHOW ENGINES;
|
|||
ALTER TABLE mytable ENGINE = InnoDB
|
||||
```
|
||||
|
||||
### 1.2. MyISAM
|
||||
### MyISAM
|
||||
|
||||
MyISAM 设计简单,数据以紧密格式存储。对于只读数据,或者表比较小、可以容忍修复操作,则依然可以使用 MyISAM。
|
||||
|
||||
|
@ -63,7 +61,7 @@ MyISAM 引擎使用 B+Tree 作为索引结构,**叶节点的 data 域存放的
|
|||
|
||||
MyISAM 提供了大量的特性,包括:全文索引、压缩表、空间函数等。但是,MyISAM 不支持事务和行级锁。并且 MyISAM 不支持崩溃后的安全恢复。
|
||||
|
||||
### 1.3. InnoDB
|
||||
### InnoDB
|
||||
|
||||
InnoDB 是 MySQL 默认的事务型存储引擎,只有在需要 InnoDB 不支持的特性时,才考虑使用其它存储引擎。
|
||||
|
||||
|
@ -77,33 +75,42 @@ InnoDB 是基于聚簇索引建立的,与其他存储引擎有很大不同。
|
|||
|
||||
支持真正的在线热备份。其它存储引擎不支持在线热备份,要获取一致性视图需要停止对所有表的写入,而在读写混合场景中,停止写入可能也意味着停止读取。
|
||||
|
||||
## 2. 数据类型
|
||||
## 二、数据类型
|
||||
|
||||
### 2.1. 整型
|
||||
Mysql 支持的数据类型大体分为 4 类:
|
||||
|
||||
TINYINT, SMALLINT, MEDIUMINT, INT, BIGINT 分别使用 8, 16, 24, 32, 64 位存储空间,一般情况下越小的列越好。
|
||||
- 整型
|
||||
- 浮点型
|
||||
- 字符串
|
||||
- 时间和日期
|
||||
|
||||
### 整型
|
||||
|
||||
`TINYINT`, `SMALLINT`, `MEDIUMINT`, `INT`, `BIGINT` 分别使用 `8`, `16`, `24`, `32`, `64` 位存储空间,一般情况下越小的列越好。
|
||||
|
||||
INT(11) 中的数字只是规定了交互工具显示字符的个数,对于存储和计算来说是没有意义的。
|
||||
|
||||
### 2.2. 浮点数
|
||||
### 浮点型
|
||||
|
||||
FLOAT 和 DOUBLE 为浮点类型,DECIMAL 为高精度小数类型。CPU 原生支持浮点运算,但是不支持 DECIMAl 类型的计算,因此 DECIMAL 的计算比浮点类型需要更高的代价。
|
||||
`FLOAT` 和 `DOUBLE` 为浮点类型,`DECIMAL` 为高精度小数类型。
|
||||
|
||||
FLOAT、DOUBLE 和 DECIMAL 都可以指定列宽,例如 DECIMAL(18, 9) 表示总共 18 位,取 9 位存储小数部分,剩下 9 位存储整数部分。
|
||||
CPU 原生支持浮点运算,但是不支持 `DECIMAl` 类型的计算,因此 `DECIMAL` 的计算比浮点类型需要更高的代价。
|
||||
|
||||
### 2.3. 字符串
|
||||
`FLOAT`、`DOUBLE` 和 `DECIMAL` 都可以指定列宽,例如 DECIMAL(18, 9) 表示总共 18 位,取 9 位存储小数部分,剩下 9 位存储整数部分。
|
||||
|
||||
主要有 CHAR 和 VARCHAR 两种类型,一种是定长的,一种是变长的。
|
||||
### 字符串
|
||||
|
||||
主要有 `CHAR` 和 `VARCHAR` 两种类型,一种是定长的,一种是变长的。
|
||||
|
||||
VARCHAR 这种变长类型能够节省空间,因为只需要存储必要的内容。但是在执行 UPDATE 时可能会使行变得比原来长,当超出一个页所能容纳的大小时,就要执行额外的操作。MyISAM 会将行拆成不同的片段存储,而 InnoDB 则需要分裂页来使行放进页内。
|
||||
|
||||
VARCHAR 会保留字符串末尾的空格,而 CHAR 会删除。
|
||||
|
||||
### 2.4. 时间和日期
|
||||
### 时间和日期
|
||||
|
||||
MySQL 提供了两种相似的日期时间类型:DATATIME 和 TIMESTAMP。
|
||||
MySQL 提供了两种相似的日期时间类型:`DATATIME` 和 `TIMESTAMP`。
|
||||
|
||||
#### 2.4.1. DATATIME
|
||||
#### DATATIME
|
||||
|
||||
能够保存从 1001 年到 9999 年的日期和时间,精度为秒,使用 8 字节的存储空间。
|
||||
|
||||
|
@ -111,7 +118,7 @@ MySQL 提供了两种相似的日期时间类型:DATATIME 和 TIMESTAMP。
|
|||
|
||||
默认情况下,MySQL 以一种可排序的、无歧义的格式显示 DATATIME 值,例如“2008-01-16 22:37:08”,这是 ANSI 标准定义的日期和时间表示方法。
|
||||
|
||||
#### 2.4.2. TIMESTAMP
|
||||
#### TIMESTAMP
|
||||
|
||||
和 UNIX 时间戳相同,保存从 1970 年 1 月 1 日午夜(格林威治时间)以来的秒数,使用 4 个字节,只能表示从 1970 年 到 2038 年。
|
||||
|
||||
|
@ -123,319 +130,15 @@ MySQL 提供了 FROM_UNIXTIME() 函数把 UNIX 时间戳转换为日期,并提
|
|||
|
||||
应该尽量使用 TIMESTAMP,因为它比 DATETIME 空间效率更高。
|
||||
|
||||
## 3. 事务
|
||||
## 索引
|
||||
|
||||
事务指的是满足 ACID 特性的一组操作。
|
||||
## 锁
|
||||
|
||||
Mysql 中,使用 `START TRANSACTION` 语句开始一个事务;使用 `COMMIT` 语句提交所有的修改;使用 `ROLLBACK` 语句撤销所有的修改。
|
||||
## 事务
|
||||
|
||||
Mysql 不是所有的存储引擎都实现了事务处理。支持事务的存储引擎有:InnoDB 和 NDB Cluster。
|
||||
## 查询性能优化
|
||||
|
||||
用户可以根据业务是否需要事务处理(事务处理可以保证数据安全,但会增加系统开销),选择合适的存储引擎。
|
||||
|
||||
Mysql 默认采用自动提交(AUTOCOMMIT)模式。
|
||||
|
||||
### 3.1. 事务隔离级别
|
||||
|
||||
InnoDB 支持 SQL 标准的四种隔离级别,默认的级别是可重复读。并且,通过间隙锁(next-key locking)策略防止幻读的出现。
|
||||
|
||||
### 3.2. 死锁
|
||||
|
||||
在 Mysql 中,锁的行为和顺序与存储引擎相关。
|
||||
|
||||
InnoDB 中解决死锁问题的方法是:将持有最少行级排他锁的事务进行回滚。
|
||||
|
||||
## 4. MVCC
|
||||
|
||||
InnoDB 的 MVCC,是通过在每行记录后面保存两个隐藏的列来实现的。这两个列,一个保存了行的创建时间,一个保存行的过期时间。当然,存储的并不是实际的时间值,而是系统版本号。每开始一个新事务,系统版本号就会自动递增。事务开始时的系统版本号会作为事务的版本号,用来和查询到的每行记录的版本号进行比较。
|
||||
|
||||
下面是在可重复读隔离级别下,MVCC 的具体操作:
|
||||
|
||||
**SELECT**
|
||||
|
||||
当开始新一个事务时,该事务的版本号肯定会大于当前所有数据行快照的创建版本号,理解这一点很关键。
|
||||
|
||||
多个事务必须读取到同一个数据行的快照,并且这个快照是距离现在最近的一个有效快照。但是也有例外,如果有一个事务正在修改该数据行,那么它可以读取事务本身所做的修改,而不用和其它事务的读取结果一致。
|
||||
|
||||
把没有对一个数据行做修改的事务称为 T,T 所要读取的数据行快照的创建版本号必须小于 T 的版本号,因为如果大于或者等于 T 的版本号,那么表示该数据行快照是其它事务的最新修改,因此不能去读取它。
|
||||
|
||||
除了上面的要求,T 所要读取的数据行快照的删除版本号必须大于 T 的版本号,因为如果小于等于 T 的版本号,那么表示该数据行快照是已经被删除的,不应该去读取它。
|
||||
|
||||
**INSERT**
|
||||
|
||||
将当前系统版本号作为数据行快照的创建版本号。
|
||||
|
||||
**DELETE**
|
||||
|
||||
将当前系统版本号作为数据行快照的删除版本号。
|
||||
|
||||
**UPDATE**
|
||||
|
||||
将当前系统版本号作为更新后的数据行快照的创建版本号,同时将当前系统版本号作为更新前的数据行快照的删除版本号。可以理解为先执行 DELETE 后执行 INSERT。
|
||||
|
||||
## 5. 索引
|
||||
|
||||
索引能够轻易将查询性能提升几个数量级。
|
||||
|
||||
对于非常小的表、大部分情况下简单的全表扫描比建立索引更高效。对于中到大型的表,索引就非常有效。但是对于特大型的表,建立和维护索引的代价将会随之增长。这种情况下,需要用到一种技术可以直接区分出需要查询的一组数据,而不是一条记录一条记录地匹配,例如可以使用分区技术。
|
||||
|
||||
索引是在存储引擎层实现的,而不是在服务器层实现的,所以不同存储引擎具有不同的索引类型和实现。
|
||||
|
||||
### 5.1. 索引的优点和缺点
|
||||
|
||||
优点:
|
||||
|
||||
- 大大减少了服务器需要扫描的数据行数。
|
||||
- 帮助服务器避免进行排序和创建临时表(B+Tree 索引是有序的,可以用来做 ORDER BY 和 GROUP BY 操作);
|
||||
- 将随机 I/O 变为顺序 I/O(B+Tree 索引是有序的,也就将相邻的数据都存储在一起)。
|
||||
|
||||
缺点:
|
||||
|
||||
1. 创建索引和维护索引要耗费时间,这种时间随着数据量的增加而增加。
|
||||
2. 索引需要占物理空间,除了数据表占数据空间之外,每一个索引还要占一定的物理空间,如果要建立组合索引那么需要的空间就会更大。
|
||||
3. 当对表中的数据进行增加、删除和修改的时候,索引也要动态的维护,这样就降低了数据的维护速度。
|
||||
|
||||
### 5.2. 索引类型
|
||||
|
||||
MySQL 目前主要有以下几种索引类型:
|
||||
|
||||
#### 5.2.1. 普通索引
|
||||
|
||||
普通索引:最基本的索引,没有任何限制。
|
||||
|
||||
```sql
|
||||
CREATE TABLE `table` (
|
||||
...
|
||||
INDEX index_name (title(length))
|
||||
)
|
||||
```
|
||||
|
||||
#### 5.2.2. 唯一索引
|
||||
|
||||
唯一索引:索引列的值必须唯一,但允许有空值。如果是组合索引,则列值的组合必须唯一。
|
||||
|
||||
```sql
|
||||
CREATE TABLE `table` (
|
||||
...
|
||||
UNIQUE indexName (title(length))
|
||||
)
|
||||
```
|
||||
|
||||
#### 5.2.3. 主键索引
|
||||
|
||||
主键索引:一种特殊的唯一索引,一个表只能有一个主键,不允许有空值。一般是在建表的时候同时创建主键索引。
|
||||
|
||||
```sql
|
||||
CREATE TABLE `table` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
...
|
||||
PRIMARY KEY (`id`)
|
||||
)
|
||||
```
|
||||
|
||||
#### 5.2.4. 组合索引
|
||||
|
||||
组合索引:多个字段上创建的索引,只有在查询条件中使用了创建索引时的第一个字段,索引才会被使用。使用组合索引时遵循最左前缀集合。
|
||||
|
||||
```sql
|
||||
CREATE TABLE `table` (
|
||||
...
|
||||
INDEX index_name (title(length), title(length), ...)
|
||||
)
|
||||
```
|
||||
|
||||
#### 5.2.5. 全文索引
|
||||
|
||||
全文索引:主要用来查找文本中的关键字,而不是直接与索引中的值相比较。fulltext 索引跟其它索引大不相同,它更像是一个搜索引擎,而不是简单的 WHERE 语句的参数匹配。fulltext 索引配合 match against 操作使用,而不是一般的 WHERE 语句加 LIKE。它可以在 CREATE TABLE,ALTER TABLE ,CREATE INDEX 使用,不过目前只有 char、varchar,text 列上可以创建全文索引。值得一提的是,在数据量较大时候,现将数据放入一个没有全局索引的表中,然后再用 CREATE INDEX 创建 fulltext 索引,要比先为一张表建立 fulltext 然后再将数据写入的速度快很多。
|
||||
|
||||
```sql
|
||||
CREATE TABLE `table` (
|
||||
`content` text CHARACTER NULL,
|
||||
...
|
||||
FULLTEXT (content)
|
||||
)
|
||||
```
|
||||
|
||||
### 5.3. 索引数据结构
|
||||
|
||||
#### 5.3.1. B+Tree 索引
|
||||
|
||||
B+Tree 索引是大多数 MySQL 存储引擎的默认索引类型。
|
||||
|
||||
因为不再需要进行全表扫描,只需要对树进行搜索即可,因此查找速度快很多。除了用于查找,还可以用于排序和分组。
|
||||
|
||||
可以指定多个列作为索引列,多个索引列共同组成键。
|
||||
|
||||
B+Tree 索引适用于全键值、键值范围和键前缀查找,其中键前缀查找只适用于最左前缀查找。
|
||||
|
||||
如果不是按照索引列的顺序进行查找,则无法使用索引。
|
||||
|
||||
InnoDB 的 B+Tree 索引分为主索引和辅助索引。
|
||||
|
||||
主索引的叶子节点 data 域记录着完整的数据记录,这种索引方式被称为聚簇索引。因为无法把数据行存放在两个不同的地方,所以一个表只能有一个聚簇索引。
|
||||
|
||||
<div align="center">
|
||||
<img src="http://upload-images.jianshu.io/upload_images/3101171-28ea7c1487bd12bb.png"/>
|
||||
</div>
|
||||
|
||||
辅助索引的叶子节点的 data 域记录着主键的值,因此在使用辅助索引进行查找时,需要先查找到主键值,然后再到主索引中进行查找。
|
||||
|
||||
<div align="center">
|
||||
<img src="http://upload-images.jianshu.io/upload_images/3101171-96c6c85468df0f89.png"/>
|
||||
</div>
|
||||
|
||||
##### B Tree 原理
|
||||
|
||||
###### B-Tree
|
||||
|
||||
<div align="center">
|
||||
<img src="http://upload-images.jianshu.io/upload_images/3101171-5594de9a48e524e7.png"/>
|
||||
</div>
|
||||
|
||||
定义一条数据记录为一个二元组 [key, data],B-Tree 是满足下列条件的数据结构:
|
||||
|
||||
- 所有叶节点具有相同的深度,也就是说 B-Tree 是平衡的;
|
||||
- 一个节点中的 key 从左到右非递减排列;
|
||||
- 如果某个指针的左右相邻 key 分别是 keyi 和 keyi+1,且不为 null,则该指针指向节点的所有 key 大于等于 keyi 且小于等于 keyi+1。
|
||||
|
||||
查找算法:首先在根节点进行二分查找,如果找到则返回对应节点的 data,否则在相应区间的指针指向的节点递归进行查找。
|
||||
|
||||
由于插入删除新的数据记录会破坏 B-Tree 的性质,因此在插入删除时,需要对树进行一个分裂、合并、旋转等操作以保持 B-Tree 性质。
|
||||
|
||||
###### B+Tree
|
||||
|
||||
<div align="center">
|
||||
<img src="http://upload-images.jianshu.io/upload_images/3101171-b5a68f67743141a1.png"/>
|
||||
</div>
|
||||
|
||||
与 B-Tree 相比,B+Tree 有以下不同点:
|
||||
|
||||
- 每个节点的指针上限为 2d 而不是 2d+1(d 为节点的出度);
|
||||
- 内节点不存储 data,只存储 key;
|
||||
- 叶子节点不存储指针。
|
||||
|
||||
###### 顺序访问指针的 B+Tree
|
||||
|
||||
<div align="center">
|
||||
<img src="http://upload-images.jianshu.io/upload_images/3101171-d97c1144093e2841.png"/>
|
||||
</div>
|
||||
|
||||
一般在数据库系统或文件系统中使用的 B+Tree 结构都在经典 B+Tree 基础上进行了优化,在叶子节点增加了顺序访问指针,做这个优化的目的是为了提高区间访问的性能。
|
||||
|
||||
###### 优势
|
||||
|
||||
红黑树等平衡树也可以用来实现索引,但是文件系统及数据库系统普遍采用 B Tree 作为索引结构,主要有以下两个原因:
|
||||
|
||||
(一)更少的检索次数
|
||||
|
||||
平衡树检索数据的时间复杂度等于树高 h,而树高大致为 O(h)=O(logdN),其中 d 为每个节点的出度。
|
||||
|
||||
红黑树的出度为 2,而 B Tree 的出度一般都非常大。红黑树的树高 h 很明显比 B Tree 大非常多,因此检索的次数也就更多。
|
||||
|
||||
B+Tree 相比于 B-Tree 更适合外存索引,因为 B+Tree 内节点去掉了 data 域,因此可以拥有更大的出度,检索效率会更高。
|
||||
|
||||
(二)利用计算机预读特性
|
||||
|
||||
为了减少磁盘 I/O,磁盘往往不是严格按需读取,而是每次都会预读。这样做的理论依据是计算机科学中著名的局部性原理:当一个数据被用到时,其附近的数据也通常会马上被使用。预读过程中,磁盘进行顺序读取,顺序读取不需要进行磁盘寻道,并且只需要很短的旋转时间,因此速度会非常快。
|
||||
|
||||
操作系统一般将内存和磁盘分割成固态大小的块,每一块称为一页,内存与磁盘以页为单位交换数据。数据库系统将索引的一个节点的大小设置为页的大小,使得一次 I/O 就能完全载入一个节点,并且可以利用预读特性,相邻的节点也能够被预先载入。
|
||||
|
||||
更多内容请参考:[MySQL 索引背后的数据结构及算法原理](http://blog.codinglabs.org/articles/theory-of-mysql-index.html)
|
||||
|
||||
#### 5.3.2. 哈希索引
|
||||
|
||||
InnoDB 引擎有一个特殊的功能叫“自适应哈希索引”,当某个索引值被使用的非常频繁时,会在 B+Tree 索引之上再创建一个哈希索引,这样就让 B+Tree 索引具有哈希索引的一些优点,比如快速的哈希查找。
|
||||
|
||||
哈希索引能以 O(1) 时间进行查找,但是失去了有序性,它具有以下限制:
|
||||
|
||||
- 无法用于排序与分组;
|
||||
- 只支持精确查找,无法用于部分查找和范围查找;
|
||||
|
||||
#### 5.3.3. 全文索引
|
||||
|
||||
MyISAM 存储引擎支持全文索引,用于查找文本中的关键词,而不是直接比较是否相等。查找条件使用 MATCH AGAINST,而不是普通的 WHERE。
|
||||
|
||||
全文索引一般使用倒排索引实现,它记录着关键词到其所在文档的映射。
|
||||
|
||||
InnoDB 存储引擎在 MySQL 5.6.4 版本中也开始支持全文索引。
|
||||
|
||||
#### 5.3.4. 空间数据索引(R-Tree)
|
||||
|
||||
MyISAM 存储引擎支持空间数据索引,可以用于地理数据存储。空间数据索引会从所有维度来索引数据,可以有效地使用任意维度来进行组合查询。
|
||||
|
||||
必须使用 GIS 相关的函数来维护数据。
|
||||
|
||||
### 5.4. 索引原则
|
||||
|
||||
#### 5.4.1. 最左前缀匹配原则
|
||||
|
||||
mysql 会一直向右匹配直到遇到范围查询(>、<、between、like)就停止匹配。
|
||||
|
||||
例如:`a = 1 and b = 2 and c > 3 and d = 4`,如果建立(a,b,c,d)顺序的索引,d 是用不到索引的,如果建立(a,b,d,c)的索引则都可以用到,a,b,d 的顺序可以任意调整。
|
||||
|
||||
让选择性最强的索引列放在前面,索引的选择性是指:不重复的索引值和记录总数的比值。最大值为 1,此时每个记录都有唯一的索引与其对应。选择性越高,查询效率也越高。
|
||||
|
||||
例如下面显示的结果中 customer_id 的选择性比 staff_id 更高,因此最好把 customer_id 列放在多列索引的前面。
|
||||
|
||||
```sql
|
||||
SELECT COUNT(DISTINCT staff_id)/COUNT(*) AS staff_id_selectivity,
|
||||
COUNT(DISTINCT customer_id)/COUNT(*) AS customer_id_selectivity,
|
||||
COUNT(*)
|
||||
FROM payment;
|
||||
```
|
||||
|
||||
```batch
|
||||
staff_id_selectivity: 0.0001
|
||||
customer_id_selectivity: 0.0373
|
||||
COUNT(*): 16049
|
||||
```
|
||||
|
||||
#### 5.4.2. = 和 in 可以乱序
|
||||
|
||||
比如 a = 1 and b = 2 and c = 3 建立(a,b,c)索引可以任意顺序,mysql 的查询优化器会帮你优化成索引可以识别的形式。
|
||||
|
||||
#### 5.4.3. 索引列不能参与计算
|
||||
|
||||
在进行查询时,索引列不能是表达式的一部分,也不能是函数的参数,否则无法使用索引。
|
||||
|
||||
例如下面的查询不能使用 actor_id 列的索引:
|
||||
|
||||
```sql
|
||||
SELECT actor_id FROM sakila.actor WHERE actor_id + 1 = 5;
|
||||
```
|
||||
|
||||
#### 5.4.4. 尽量的扩展索引,不要新建索引
|
||||
|
||||
比如表中已经有 a 的索引,现在要加(a,b)的索引,那么只需要修改原来的索引即可。
|
||||
|
||||
#### 5.4.5. 多列索引
|
||||
|
||||
在需要使用多个列作为条件进行查询时,使用多列索引比使用多个单列索引性能更好。例如下面的语句中,最好把 actor_id 和 film_id 设置为多列索引。
|
||||
|
||||
```sql
|
||||
SELECT film_id, actor_ id FROM sakila.film_actor
|
||||
WhERE actor_id = 1 AND film_id = 1;
|
||||
```
|
||||
|
||||
#### 5.4.6. 前缀索引
|
||||
|
||||
对于 BLOB、TEXT 和 VARCHAR 类型的列,必须使用前缀索引,只索引开始的部分字符。
|
||||
|
||||
对于前缀长度的选取需要根据索引选择性来确定。
|
||||
|
||||
#### 5.4.7. 覆盖索引
|
||||
|
||||
索引包含所有需要查询的字段的值。
|
||||
|
||||
具有以下优点:
|
||||
|
||||
- 因为索引条目通常远小于数据行的大小,所以若只读取索引,能大大减少数据访问量。
|
||||
- 一些存储引擎(例如 MyISAM)在内存中只缓存索引,而数据依赖于操作系统来缓存。因此,只访问索引可以不使用系统调用(通常比较费时)。
|
||||
- 对于 InnoDB 引擎,若辅助索引能够覆盖查询,则无需访问主索引。
|
||||
|
||||
## 6. 查询性能优化
|
||||
|
||||
### 6.1. 使用 Explain 进行分析
|
||||
### 使用 Explain 进行分析
|
||||
|
||||
Explain 用来分析 SELECT 查询语句,开发人员可以通过分析 Explain 结果来优化查询语句。
|
||||
|
||||
|
@ -447,9 +150,9 @@ Explain 用来分析 SELECT 查询语句,开发人员可以通过分析 Explai
|
|||
|
||||
更多内容请参考:[MySQL 性能优化神器 Explain 使用分析](https://segmentfault.com/a/1190000008131735)
|
||||
|
||||
### 6.2. 优化数据访问
|
||||
### 优化数据访问
|
||||
|
||||
#### 6.2.1. 减少请求的数据量
|
||||
#### 减少请求的数据量
|
||||
|
||||
(一)只返回必要的列
|
||||
|
||||
|
@ -463,13 +166,13 @@ Explain 用来分析 SELECT 查询语句,开发人员可以通过分析 Explai
|
|||
|
||||
使用缓存可以避免在数据库中进行查询,特别要查询的数据经常被重复查询,缓存可以带来的查询性能提升将会是非常明显的。
|
||||
|
||||
#### 6.2.2. 减少服务器端扫描的行数
|
||||
#### 减少服务器端扫描的行数
|
||||
|
||||
最有效的方式是使用索引来覆盖查询。
|
||||
|
||||
### 6.3. 重构查询方式
|
||||
### 重构查询方式
|
||||
|
||||
#### 6.3.1. 切分大查询
|
||||
#### 切分大查询
|
||||
|
||||
一个大查询如果一次性执行的话,可能一次锁住很多数据、占满整个事务日志、耗尽系统资源、阻塞很多小的但重要的查询。
|
||||
|
||||
|
@ -485,7 +188,7 @@ do {
|
|||
} while rows_affected > 0
|
||||
```
|
||||
|
||||
#### 6.3.2. 分解大连接查询
|
||||
#### 分解大连接查询
|
||||
|
||||
将一个大连接查询(JOIN)分解成对每一个表进行一次单表查询,然后将结果在应用程序中进行关联,这样做的好处有:
|
||||
|
||||
|
@ -508,9 +211,9 @@ SELECT * FROM tag_post WHERE tag_id=1234;
|
|||
SELECT * FROM post WHERE post.id IN (123,456,567,9098,8904);
|
||||
```
|
||||
|
||||
## 7. 复制
|
||||
## 复制
|
||||
|
||||
### 7.1. 主从复制
|
||||
### 主从复制
|
||||
|
||||
Mysql 支持两种复制:基于行的复制和基于语句的复制。
|
||||
|
||||
|
@ -526,7 +229,7 @@ Mysql 支持两种复制:基于行的复制和基于语句的复制。
|
|||
<img src="http://dunwu.test.upcdn.net/cs/database/mysql/master-slave.png!zp" />
|
||||
</div>
|
||||
|
||||
### 7.2. 读写分离
|
||||
### 读写分离
|
||||
|
||||
主服务器用来处理写操作以及实时性要求比较高的读操作,而从服务器用来处理读操作。
|
||||
|
||||
|
@ -542,14 +245,14 @@ MySQL 读写分离能提高性能的原因在于:
|
|||
<img src="http://dunwu.test.upcdn.net/cs/database/mysql/master-slave-proxy.png!zp" />
|
||||
</div>
|
||||
|
||||
## 8. 参考资料
|
||||
## 参考资料
|
||||
|
||||
- BaronScbwartz, PeterZaitsev, VadimTkacbenko 等. 高性能 MySQL[M]. 电子工业出版社, 2013.
|
||||
- [《高性能 MySQL》](https://book.douban.com/subject/23008813/)
|
||||
- 姜承尧. MySQL 技术内幕: InnoDB 存储引擎 [M]. 机械工业出版社, 2011.
|
||||
- [20+ 条 MySQL 性能优化的最佳经验](https://www.jfox.info/20-tiao-mysql-xing-nen-you-hua-de-zui-jia-jing-yan.html)
|
||||
- [How to create unique row ID in sharded databases?](https://stackoverflow.com/questions/788829/how-to-create-unique-row-id-in-sharded-databases)
|
||||
- [SQL Azure Federation – Introduction](http://geekswithblogs.net/shaunxu/archive/2012/01/07/sql-azure-federation-ndash-introduction.aspx)
|
||||
|
||||
## 9. :door: 传送门
|
||||
## 传送门
|
||||
|
||||
| [我的 Github 博客](https://github.com/dunwu/blog) | [db-tutorial 首页](https://github.com/dunwu/db-tutorial) |
|
||||
◾ 🏠 [DB-TUTORIAL 首页](https://github.com/dunwu/db-tutorial) ◾ 🎯 [我的博客](https://github.com/dunwu/blog) ◾
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
## 一、事务简介
|
||||
|
||||
> 事务简单来说:**一个 Session 中所进行所有的操作,要么同时成功,要么同时失败**。进一步说,事务指的是满足 ACID 特性的一组操作,可以通过 `Commit` 提交一个事务,也可以使用 `Rollback` 进行回滚。
|
||||
|
||||
![img](http://dunwu.test.upcdn.net/cs/database/RDB/数据库事务.png)
|
||||
|
||||
**事务就是一组原子性的 SQL 语句**。具体来说,事务指的是满足 ACID 特性的一组操作。
|
||||
|
@ -95,7 +97,6 @@ SHOW VARIABLES LIKE 'AUTOCOMMIT';
|
|||
-- 关闭 AUTOCOMMIT
|
||||
SET autocommit = 0;
|
||||
|
||||
|
||||
-- 开启 AUTOCOMMIT
|
||||
SET autocommit = 1;
|
||||
```
|
||||
|
@ -125,7 +126,7 @@ ACID 是数据库事务正确执行的四个基本要素。
|
|||
|
||||
![img](http://dunwu.test.upcdn.net/cs/database/RDB/数据库ACID.png)
|
||||
|
||||
> MySQL 默认采用自动提交模式(AUTO COMMIT)。也就是说,如果不显式使用`START TRANSACTION`语句来开始一个事务,那么每个查询操作都会被当做一个事务并自动提交。
|
||||
> MySQL 默认采用自动提交模式(`AUTO COMMIT`)。也就是说,如果不显式使用 `START TRANSACTION` 语句来开始一个事务,那么每个查询操作都会被当做一个事务并自动提交。
|
||||
|
||||
## 四、事务隔离级别
|
||||
|
||||
|
@ -199,6 +200,11 @@ T<sub>1</sub> 读取某个范围的数据,T<sub>2</sub> 在这个范围内插
|
|||
|
||||
### 隔离级别小结
|
||||
|
||||
- **`未提交读(READ UNCOMMITTED)`** - 事务中的修改,即使没有提交,对其它事务也是可见的。
|
||||
- **`提交读(READ COMMITTED)`** - 一个事务只能读取已经提交的事务所做的修改。换句话说,一个事务所做的修改在提交之前对其它事务是不可见的。
|
||||
- **`重复读(REPEATABLE READ)`** - 保证在同一个事务中多次读取同样数据的结果是一样的。
|
||||
- **`串行化(SERIALIXABLE)`** - 强制事务串行执行。
|
||||
|
||||
数据库隔离级别解决的问题:
|
||||
|
||||
| 隔离级别 | 脏读 | 不可重复读 | 幻读 |
|
||||
|
@ -218,7 +224,7 @@ T<sub>1</sub> 读取某个范围的数据,T<sub>2</sub> 在这个范围内插
|
|||
|
||||
多个事务同时锁定同一个资源时,也会产生死锁。
|
||||
|
||||
为了解决死锁问题,不同数据库实现了各自的死锁检测和超时机制。InnoDB 的处理策略是:将持有最少行级排它锁的事务进行回滚。
|
||||
为了解决死锁问题,不同数据库实现了各自的死锁检测和超时机制。InnoDB 的处理策略是:**将持有最少行级排它锁的事务进行回滚**。
|
||||
|
||||
## 六、分布式事务
|
||||
|
||||
|
@ -257,3 +263,4 @@ T<sub>1</sub> 读取某个范围的数据,T<sub>2</sub> 在这个范围内插
|
|||
## 参考资料
|
||||
|
||||
- [《高性能 MySQL》](https://book.douban.com/subject/23008813/)
|
||||
- [ShardingSphere 分布式事务](https://shardingsphere.apache.org/document/current/cn/features/transaction/)
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
# SQL Cheat Sheet
|
||||
|
||||
> 本文针对关系型数据库的一般语法。限于篇幅,本文侧重说明用法,不会展开讲解特性、原理。
|
||||
>
|
||||
> 本文语法主要针对 Mysql。
|
||||
|
||||
![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200115160512.png)
|
||||
|
||||
|
|
|
@ -2,25 +2,13 @@
|
|||
|
||||
## 一、索引和约束
|
||||
|
||||
### 何时使用索引
|
||||
### 什么是索引
|
||||
|
||||
索引能够轻易将查询性能提升几个数量级。
|
||||
|
||||
什么情况**适用**索引:
|
||||
|
||||
- 表经常进行 `SELECT` 操作;
|
||||
- 表的数据量比较大;
|
||||
- 列名经常出现在 `WHERE` 或连接条件中
|
||||
|
||||
什么情况**不适用**索引:
|
||||
|
||||
- 表经常进行 `INSERT`/`UPDATE`/`DELETE` 操作;
|
||||
- 表的数据量比较小;
|
||||
- 列名不经常出现在 `WHERE` 或连接条件中
|
||||
索引是对数据库表中一或多个列的值进行排序的结构,是帮助数据库高效查询数据的数据结构。
|
||||
|
||||
### 索引的优缺点
|
||||
|
||||
索引的优点:
|
||||
✔ 索引的优点:
|
||||
|
||||
- 索引大大减少了服务器需要扫描的数据量,从而加快检索速度。
|
||||
- 支持行级锁的数据库,如 InnoDB 会在访问行的时候加锁。使用索引可以减少访问的行数,从而减少锁的竞争,提高并发。
|
||||
|
@ -28,22 +16,44 @@
|
|||
- 索引可以将随机 I/O 变为顺序 I/O。
|
||||
- 唯一索引可以确保每一行数据的唯一性,通过使用索引,可以在查询的过程中使用优化隐藏器,提高系统的性能。
|
||||
|
||||
索引的缺点:
|
||||
❌ 索引的缺点:
|
||||
|
||||
- 创建和维护索引要耗费时间,这会随着数据量的增加而增加。
|
||||
- **索引需要占用额外的物理空间**,除了数据表占数据空间之外,每一个索引还要占一定的物理空间,如果要建立组合索引那么需要的空间就会更大。
|
||||
- 写操作(`INSERT`/`UPDATE`/`DELETE`)时很可能需要更新索引,导致数据库的写操作性能降低。
|
||||
|
||||
### 何时使用索引
|
||||
|
||||
索引能够轻易将查询性能提升几个数量级。
|
||||
|
||||
✔ 什么情况**适用**索引:
|
||||
|
||||
- 表经常进行 `SELECT` 操作;
|
||||
- 表的数据量比较大;
|
||||
- 列名经常出现在 `WHERE` 或连接(`JOIN`)条件中
|
||||
|
||||
❌ 什么情况**不适用**索引:
|
||||
|
||||
- **频繁写操作**( `INSERT`/`UPDATE`/`DELETE` )- 需要更新索引空间;
|
||||
- **非常小的表** - 对于非常小的表,大部分情况下简单的全表扫描更高效。
|
||||
- 列名不经常出现在 `WHERE` 或连接(`JOIN`)条件中 - 索引就会经常不命中,没有意义,还增加空间开销。
|
||||
- 对于特大型表,建立和使用索引的代价将随之增长。可以考虑使用分区技术或 Nosql。
|
||||
|
||||
### 索引的类型
|
||||
|
||||
主流的关系型数据库一般都支持以下索引类型:
|
||||
|
||||
- 唯一索引(UNIQUE):索引列的值必须唯一,但允许有空值。如果是组合索引,则列值的组合必须唯一。
|
||||
- 主键索引(PRIMARY):一种特殊的唯一索引,一个表只能有一个主键,不允许有空值。一般是在建表的时候同时创建主键索引。
|
||||
- 普通索引(INDEX):最基本的索引,没有任何限制。
|
||||
从逻辑类型上划分(即一般创建表时设置的索引类型):
|
||||
|
||||
- 唯一索引(`UNIQUE`):索引列的值必须唯一,但允许有空值。如果是组合索引,则列值的组合必须唯一。
|
||||
- 主键索引(`PRIMARY`):一种特殊的唯一索引,一个表只能有一个主键,不允许有空值。一般是在建表的时候同时创建主键索引。
|
||||
- 普通索引(`INDEX`):最基本的索引,没有任何限制。
|
||||
- 组合索引:多个字段上创建的索引,只有在查询条件中使用了创建索引时的第一个字段,索引才会被使用。使用组合索引时遵循最左前缀集合。
|
||||
- **聚集索引**(Clustered):表中各行的物理顺序与键值的逻辑(索引)顺序相同,每个表只能有一个
|
||||
- **非聚集索引**(Non-clustered):非聚集索引指定表的逻辑顺序,也可以视为二级索引。数据存储在一个位置,索引存储在另一个位置,索引中包含指向数据存储位置的指针。可以有多个,小于 249 个
|
||||
|
||||
从物理存储上划分:
|
||||
|
||||
- **聚集索引**(`Clustered`):表中各行的物理顺序与键值的逻辑(索引)顺序相同,每个表只能有一个。
|
||||
- **非聚集索引**(`Non-clustered`):非聚集索引指定表的逻辑顺序,也可以视为二级索引。数据存储在一个位置,索引存储在另一个位置,索引中包含指向数据存储位置的指针。可以有多个,小于 249 个。
|
||||
|
||||
### 索引的数据结构
|
||||
|
||||
|
@ -229,7 +239,91 @@ MySQL 会一直向右匹配直到遇到范围查询 `(>,<,BETWEEN,LIKE)` 就停
|
|||
- `FOREIGN KEY` - 在一个表中存在的另一个表的主键称此表的外键。用于预防破坏表之间连接的动作,也能防止非法数据插入外键列,因为它必须是它指向的那个表中的值之一。
|
||||
- `CHECK` - 用于控制字段的值范围。
|
||||
|
||||
## 二、事务
|
||||
## 二、并发控制
|
||||
|
||||
### 乐观锁和悲观锁
|
||||
|
||||
> - 数据库的乐观锁和悲观锁是什么?
|
||||
> - 数据库的乐观锁和悲观锁如何实现?
|
||||
|
||||
确保在多个事务同时存取数据库中同一数据时不破坏事务的隔离性和统一性以及数据库的统一性,**乐观锁和悲观锁是并发控制主要采用的技术手段。**
|
||||
|
||||
- **`悲观锁`** - 假定会发生并发冲突,屏蔽一切可能违反数据完整性的操作
|
||||
- **在查询完数据的时候就把事务锁起来,直到提交事务(COMMIT)**
|
||||
- 实现方式:使用数据库中的锁机制
|
||||
- **`乐观锁`** - 假设不会发生并发冲突,只在提交操作时检查是否违反数据完整性。
|
||||
- **在修改数据的时候把事务锁起来,通过 version 的方式来进行锁定**
|
||||
- 实现方式:使用 version 版本或者时间戳
|
||||
|
||||
### 行级锁和表级锁
|
||||
|
||||
> - 什么是行级锁和表级锁?
|
||||
> - 什么时候用行级锁?什么时候用表级锁?
|
||||
|
||||
从数据库的锁粒度来看,MySQL 中提供了两种封锁粒度:行级锁和表级锁。
|
||||
|
||||
- **表级锁(table lock)** - 锁定整张表。用户对表进行写操作前,需要先获得写锁,这会阻塞其他用户对该表的所有读写操作。只有没有写锁时,其他用户才能获得读锁,读锁之间不会相互阻塞。
|
||||
- **行级锁(row lock)** - 仅对指定的行记录进行加锁,这样其它进程还是可以对同一个表中的其它记录进行操作。
|
||||
|
||||
二者需要权衡:
|
||||
|
||||
- **锁定的数据量越少,锁竞争的发生频率就越小,系统的并发程度就越高**。
|
||||
- **锁粒度越小,系统开销就越大**。
|
||||
|
||||
在 `InnoDB` 中,行锁是通过给索引上的索引项加锁来实现的。**如果没有索引,`InnoDB` 将会通过隐藏的聚簇索引来对记录加锁**。
|
||||
|
||||
### 读写锁
|
||||
|
||||
> - 什么是读写锁?
|
||||
|
||||
- 独享锁(Exclusive),简写为 X 锁,又称写锁。使用方式:`SELECT ... FOR UPDATE;`
|
||||
- 共享锁(Shared),简写为 S 锁,又称读锁。使用方式:`SELECT ... LOCK IN SHARE MODE;`
|
||||
|
||||
写锁和读锁的关系,简言之:**独享锁存在,其他事务就不能做任何操作**。
|
||||
|
||||
**`InnoDB` 下的行锁、间隙锁、next-key 锁统统属于独享锁**。
|
||||
|
||||
### 意向锁
|
||||
|
||||
> - 什么是意向锁?
|
||||
> - 意向锁有什么用?
|
||||
|
||||
意向锁的作用是:**当存在表级锁和行级锁的情况下,必须先申请意向锁(表级锁,但不是真的加锁),再获取行级锁**。使用意向锁(Intention Locks)可以更容易地支持多粒度封锁。
|
||||
|
||||
**意向锁是 `InnoDB` 自动加的,不需要用户干预**。
|
||||
|
||||
### MVCC
|
||||
|
||||
> 什么是 MVCC?
|
||||
>
|
||||
> MVCC 有什么用?解决了什么问题?
|
||||
>
|
||||
> MVCC 的原理是什么?
|
||||
|
||||
多版本并发控制(Multi-Version Concurrency Control, MVCC)是 `InnoDB` 存储引擎实现隔离级别的一种具体方式,**用于实现提交读和可重复读这两种隔离级别**。而未提交读隔离级别总是读取最新的数据行,要求很低,无需使用 MVCC。可串行化隔离级别需要对所有读取的行都加锁,单纯使用 MVCC 无法实现。
|
||||
|
||||
MVCC 的思想是:
|
||||
|
||||
- 保存数据在某个时间点的快照。**写操作(DELETE、INSERT、UPDATE)更新最新的版本快照,而读操作去读旧版本快照,没有互斥关系**,这一点和 `CopyOnWrite` 类似。
|
||||
- 脏读和不可重复读最根本的原因是**事务读取到其它事务未提交的修改**。在事务进行读取操作时,为了解决脏读和不可重复读问题,**MVCC 规定只能读取已经提交的快照**。当然一个事务可以读取自身未提交的快照,这不算是脏读。
|
||||
|
||||
### Next-key 锁
|
||||
|
||||
Next-Key 锁是 MySQL 的 `InnoDB` 存储引擎的一种锁实现。
|
||||
|
||||
MVCC 不能解决幻读问题,**Next-Key 锁就是为了解决幻读问题**。在可重复读(`REPEATABLE READ`)隔离级别下,使用 **MVCC + Next-Key 锁** 可以解决幻读问题。
|
||||
|
||||
另外,根据针对 SQL 语句检索条件的不同,加锁又有以下三种情形需要我们掌握。
|
||||
|
||||
- `Record Lock` - **行锁对索引项加锁,若没有索引则使用表锁**。
|
||||
- `Gap Lock` - 对索引项之间的间隙加锁。锁定索引之间的间隙,但是不包含索引本身。例如当一个事务执行以下语句,其它事务就不能在 t.c 中插入 15。`SELECT c FROM t WHERE c BETWEEN 10 and 20 FOR UPDATE;`
|
||||
- `Next-key lock` -它是 `Record Lock` 和 `Gap Lock` 的结合,不仅锁定一个记录上的索引,也锁定索引之间的间隙。它锁定一个前开后闭区间。
|
||||
|
||||
索引分为主键索引和非主键索引两种,如果一条 SQL 语句操作了主键索引,MySQL 就会锁定这条主键索引;如果一条语句操作了非主键索引,MySQL 会先锁定该非主键索引,再锁定相关的主键索引。在 `UPDATE`、`DELETE` 操作时,MySQL 不仅锁定 `WHERE` 条件扫描过的所有索引记录,而且会锁定相邻的键值,即所谓的 `next-key lock`。
|
||||
|
||||
当两个事务同时执行,一个锁住了主键索引,在等待其他相关索引。另一个锁定了非主键索引,在等待主键索引。这样就会发生死锁。发生死锁后,`InnoDB` 一般都可以检测到,并使一个事务释放锁回退,另一个获取锁完成事务。
|
||||
|
||||
## 三、事务
|
||||
|
||||
> 事务简单来说:**一个 Session 中所进行所有的操作,要么同时成功,要么同时失败**。具体来说,事务指的是满足 ACID 特性的一组操作,可以通过 `Commit` 提交一个事务,也可以使用 `Rollback` 进行回滚。
|
||||
|
||||
|
@ -237,31 +331,17 @@ MySQL 会一直向右匹配直到遇到范围查询 `(>,<,BETWEEN,LIKE)` 就停
|
|||
|
||||
### ACID
|
||||
|
||||
ACID — 数据库事务正确执行的四个基本要素\*\*
|
||||
ACID — 数据库事务正确执行的四个基本要素:
|
||||
|
||||
- **原子性(Atomicity)**
|
||||
- 事务被视为不可分割的最小单元,事务中的所有操作要么全部提交成功,要么全部失败回滚。
|
||||
- 回滚可以用日志来实现,日志记录着事务所执行的修改操作,在回滚时反向执行这些修改操作即可。
|
||||
- **一致性(Consistency)**
|
||||
- 数据库在事务执行前后都保持一致性状态。
|
||||
- 在一致性状态下,所有事务对一个数据的读取结果都是相同的。
|
||||
- **隔离性(Isolation)**
|
||||
- 一个事务所做的修改在最终提交以前,对其它事务是不可见的。
|
||||
- **持久性(Durability)**
|
||||
- 一旦事务提交,则其所做的修改将会永远保存到数据库中。即使系统发生崩溃,事务执行的结果也不能丢失。
|
||||
- 可以通过数据库备份和恢复来实现,在系统发生奔溃时,使用备份的数据库进行数据恢复。
|
||||
|
||||
**一个支持事务(Transaction)中的数据库系统,必需要具有这四种特性,否则在事务过程(Transaction processing)当中无法保证数据的正确性,交易过程极可能达不到交易。**
|
||||
|
||||
- 只有满足一致性,事务的执行结果才是正确的。
|
||||
- 在无并发的情况下,事务串行执行,隔离性一定能够满足。此时只要能满足原子性,就一定能满足一致性。
|
||||
- 在并发的情况下,多个事务并行执行,事务不仅要满足原子性,还需要满足隔离性,才能满足一致性。
|
||||
- 事务满足持久化是为了能应对系统崩溃的情况。
|
||||
|
||||
![img](http://dunwu.test.upcdn.net/cs/database/RDB/数据库ACID.png)
|
||||
|
||||
> MySQL 默认采用自动提交模式(AUTO COMMIT)。也就是说,如果不显式使用`START TRANSACTION`语句来开始一个事务,那么每个查询操作都会被当做一个事务并自动提交。
|
||||
|
||||
### 并发一致性问题
|
||||
|
||||
在并发环境下,事务的隔离性很难保证,因此会出现很多并发一致性问题。
|
||||
|
@ -348,191 +428,10 @@ T<sub>1</sub> 读取某个范围的数据,T<sub>2</sub> 在这个范围内插
|
|||
| 并发性能 | 无影响 | 严重衰退 | 略微衰退 |
|
||||
| 适合场景 | 业务方处理不一致 | 短事务 & 低并发 | 长事务 & 高并发 |
|
||||
|
||||
## 三、并发控制
|
||||
|
||||
### 乐观锁和悲观锁
|
||||
|
||||
> :question: 问题:
|
||||
>
|
||||
> - 数据库的乐观锁和悲观锁是什么?
|
||||
> - 数据库的乐观锁和悲观锁如何实现?
|
||||
|
||||
确保在多个事务同时存取数据库中同一数据时不破坏事务的隔离性和统一性以及数据库的统一性,**乐观锁和悲观锁是并发控制主要采用的技术手段。**
|
||||
|
||||
- **`悲观锁`** - 假定会发生并发冲突,屏蔽一切可能违反数据完整性的操作
|
||||
- **在查询完数据的时候就把事务锁起来,直到提交事务(COMMIT)**
|
||||
- 实现方式:使用数据库中的锁机制
|
||||
- **`乐观锁`** - 假设不会发生并发冲突,只在提交操作时检查是否违反数据完整性。
|
||||
- **在修改数据的时候把事务锁起来,通过 version 的方式来进行锁定**
|
||||
- 实现方式:使用 version 版本或者时间戳
|
||||
|
||||
### 行级锁和表级锁
|
||||
|
||||
> ❓ 问题:
|
||||
>
|
||||
> - 什么是行级锁和表级锁?
|
||||
> - 什么时候用行级锁?什么时候用表级锁?
|
||||
|
||||
从数据库的锁粒度来看,MySQL 中提供了两种封锁粒度:行级锁和表级锁。
|
||||
|
||||
- **表级锁(table lock)** - 锁定整张表。用户对表进行写操作前,需要先获得写锁,这会阻塞其他用户对该表的所有读写操作。只有没有写锁时,其他用户才能获得读锁,读锁之间不会相互阻塞。
|
||||
- **行级锁(row lock)** - 仅对指定的行记录进行加锁,这样其它进程还是可以对同一个表中的其它记录进行操作。
|
||||
|
||||
应该尽量只锁定需要修改的那部分数据,而不是所有的资源。锁定的数据量越少,发生锁争用的可能就越小,系统的并发程度就越高。但是加锁需要消耗资源,锁的各种操作(包括获取锁、释放锁、以及检查锁状态)都会增加系统开销。因此封锁粒度越小,系统开销就越大。
|
||||
|
||||
在选择封锁粒度时,需要在锁开销和并发程度之间做一个权衡。
|
||||
|
||||
在 `InnoDB` 中,行锁是通过给索引上的索引项加锁来实现的。**如果没有索引,`InnoDB` 将会通过隐藏的聚簇索引来对记录加锁**。
|
||||
|
||||
### 读写锁
|
||||
|
||||
> ❓ 问题:
|
||||
>
|
||||
> - 什么是读写锁?
|
||||
|
||||
- 独享锁(Exclusive),简写为 X 锁,又称写锁。
|
||||
- 共享锁(Shared),简写为 S 锁,又称读锁
|
||||
|
||||
写锁和读锁的关系,简言之:同一时刻,针对同一数据,只要有一个事务在进行写操作,其他事务就不能做任何操作。
|
||||
|
||||
使用方式:
|
||||
|
||||
- 独享锁:`SELECT ... FOR UPDATE;`
|
||||
- 共享锁:`SELECT ... LOCK IN SHARE MODE;`
|
||||
|
||||
`InnoDB` 下的行锁、间隙锁、next-key 锁统统属于独享锁。
|
||||
|
||||
### 意向锁
|
||||
|
||||
使用意向锁(Intention Locks)可以更容易地支持多粒度封锁。
|
||||
|
||||
在存在行级锁和表级锁的情况下,事务 T 想要对表 A 加 X 锁,就需要先检测是否有其它事务对表 A 或者表 A 中的任意一行加了锁,那么就需要对表 A 的每一行都检测一次,这是非常耗时的。
|
||||
|
||||
意向锁在原来的 X/S 锁之上引入了 IX/IS,IX/IS 都是表锁,用来表示一个事务想要在表中的某个数据行上加 X 锁或 S 锁。有以下两个规定:
|
||||
|
||||
- 一个事务在获得某个数据行对象的 S 锁之前,必须先获得表的 IS 锁或者更强的锁;
|
||||
- 一个事务在获得某个数据行对象的 X 锁之前,必须先获得表的 IX 锁。
|
||||
|
||||
通过引入意向锁,事务 T 想要对表 A 加 X 锁,只需要先检测是否有其它事务对表 A 加了 X/IX/S/IS 锁,如果加了就表示有其它事务正在使用这个表或者表中某一行的锁,因此事务 T 加 X 锁失败。
|
||||
|
||||
各种锁的兼容关系如下:
|
||||
|
||||
| - | X | IX | S | IS |
|
||||
| :-: | :-: | :-: | :-: | :-: |
|
||||
| X | ❌ | ❌ | ❌ | ❌ |
|
||||
| IX | ❌ | ✔️ | ❌ | ✔️ |
|
||||
| S | ❌ | ❌ | ✔️ | ✔️ |
|
||||
| IS | ❌ | ✔️ | ✔️ | ✔️ |
|
||||
|
||||
解释如下:
|
||||
|
||||
- 任意 IS/IX 锁之间都是兼容的,因为它们只表示想要对表加锁,而不是真正加锁;
|
||||
- 这里兼容关系针对的是表级锁,而表级的 IX 锁和行级的 X 锁兼容,两个事务可以对两个数据行加 X 锁。(事务 T1 想要对数据行 R1 加 X 锁,事务 T2 想要对同一个表的数据行 R2 加 X 锁,两个事务都需要对该表加 IX 锁,但是 IX 锁是兼容的,并且 IX 锁与行级的 X 锁也是兼容的,因此两个事务都能加锁成功,对同一个表中的两个数据行做修改。)
|
||||
|
||||
意向锁是 `InnoDB` 自动加的,不需要用户干预。
|
||||
|
||||
### MVCC
|
||||
|
||||
> ❓ 常见问题:
|
||||
>
|
||||
> 什么是 MVCC?
|
||||
>
|
||||
> MVCC 有什么用?
|
||||
|
||||
多版本并发控制(Multi-Version Concurrency Control, MVCC)是 `InnoDB` 存储引擎实现隔离级别的一种具体方式,**用于实现提交读和可重复读这两种隔离级别**。而未提交读隔离级别总是读取最新的数据行,要求很低,无需使用 MVCC。可串行化隔离级别需要对所有读取的行都加锁,单纯使用 MVCC 无法实现。
|
||||
|
||||
#### 基本思想
|
||||
|
||||
在数据库锁一节中提到,加锁能解决多个事务同时执行时出现的并发一致性问题。在实际场景中读操作往往多于写操作,因此又引入了读写锁来避免不必要的加锁操作,例如读和读没有互斥关系。读写锁中读和写操作仍然是互斥的,而 MVCC 利用了多版本的思想,**写操作更新最新的版本快照,而读操作去读旧版本快照,没有互斥关系**,这一点和 CopyOnWrite 类似。
|
||||
|
||||
**在 MVCC 中事务的写操作(DELETE、INSERT、UPDATE)会为数据行新增一个版本快照**。
|
||||
|
||||
脏读和不可重复读最根本的原因是事务读取到其它事务未提交的修改。在事务进行读取操作时,为了解决脏读和不可重复读问题,MVCC 规定只能读取已经提交的快照。当然一个事务可以读取自身未提交的快照,这不算是脏读。
|
||||
|
||||
#### 版本号
|
||||
|
||||
- 系统版本号 SYS_ID:是一个递增的数字,每开始一个新的事务,系统版本号就会自动递增。
|
||||
- 事务版本号 TRX_ID :事务开始时的系统版本号。
|
||||
|
||||
#### Undo 日志
|
||||
|
||||
MVCC 的多版本指的是多个版本的快照,快照存储在 Undo 日志中,该日志通过回滚指针 ROLL_PTR 把一个数据行的所有快照连接起来。
|
||||
|
||||
例如在 MySQL 创建一个表 t,包含主键 id 和一个字段 x。我们先插入一个数据行,然后对该数据行执行两次更新操作。
|
||||
|
||||
```sql
|
||||
INSERT INTO t(id, x) VALUES(1, "a");
|
||||
UPDATE t SET x="b" WHERE id=1;
|
||||
UPDATE t SET x="c" WHERE id=1;
|
||||
```
|
||||
|
||||
因为没有使用 `START TRANSACTION` 将上面的操作当成一个事务来执行,根据 MySQL 的 `AUTOCOMMIT` 机制,每个操作都会被当成一个事务来执行,所以上面的操作总共涉及到三个事务。快照中除了记录事务版本号 TRX_ID 和操作之外,还记录了一个 bit 的 DEL 字段,用于标记是否被删除。
|
||||
|
||||
`INSERT`、`UPDATE`、`DELETE` 操作会创建一个日志,并将事务版本号 `TRX_ID` 写入。`DELETE` 可以看成是一个特殊的 `UPDATE`,还会额外将 DEL 字段设置为 1。
|
||||
|
||||
#### ReadView
|
||||
|
||||
MVCC 维护了一个 `ReadView` 结构,主要包含了当前系统未提交的事务列表 TRX_IDs {TRX_ID_1, TRX_ID_2, ...},还有该列表的最小值 `TRX_ID_MIN` 和 `TRX_ID_MAX`。
|
||||
|
||||
在进行 `SELECT` 操作时,根据数据行快照的 `TRX_ID` 与 `TRX_ID_MIN` 和 `TRX_ID_MAX` 之间的关系,从而判断数据行快照是否可以使用:
|
||||
|
||||
- `TRX_ID` < `TRX_ID_MIN`,表示该数据行快照时在当前所有未提交事务之前进行更改的,因此可以使用。
|
||||
- `TRX_ID` > `TRX_ID_MAX`,表示该数据行快照是在事务启动之后被更改的,因此不可使用。
|
||||
- `TRX_ID_MIN` <= `TRX_ID` <= `TRX_ID_MAX`,需要根据隔离级别再进行判断:
|
||||
- 提交读:如果 `TRX_ID` 在 `TRX_IDs` 列表中,表示该数据行快照对应的事务还未提交,则该快照不可使用。否则表示已经提交,可以使用。
|
||||
- 可重复读:都不可以使用。因为如果可以使用的话,那么其它事务也可以读到这个数据行快照并进行修改,那么当前事务再去读这个数据行得到的值就会发生改变,也就是出现了不可重复读问题。
|
||||
|
||||
在数据行快照不可使用的情况下,需要沿着 Undo Log 的回滚指针 ROLL_PTR 找到下一个快照,再进行上面的判断。
|
||||
|
||||
#### 快照读与当前读
|
||||
|
||||
##### 快照读
|
||||
|
||||
MVCC 的 SELECT 操作是快照中的数据,不需要进行加锁操作。
|
||||
|
||||
```sql
|
||||
SELECT * FROM table ...;
|
||||
```
|
||||
|
||||
##### 当前读
|
||||
|
||||
MVCC 其它会对数据库进行修改的操作(INSERT、UPDATE、DELETE)需要进行加锁操作,从而读取最新的数据。可以看到 MVCC 并不是完全不用加锁,而只是避免了 SELECT 的加锁操作。
|
||||
|
||||
```sql
|
||||
INSERT;
|
||||
UPDATE;
|
||||
DELETE;
|
||||
```
|
||||
|
||||
在进行 SELECT 操作时,可以强制指定进行加锁操作。以下第一个语句需要加 S 锁,第二个需要加 X 锁。
|
||||
|
||||
```sql
|
||||
SELECT * FROM table WHERE ? lock in share mode;
|
||||
SELECT * FROM table WHERE ? for update;
|
||||
```
|
||||
|
||||
### Next-key 锁
|
||||
|
||||
Next-Key 锁是 MySQL 的 `InnoDB` 存储引擎的一种锁实现。
|
||||
|
||||
MVCC 不能解决幻影读问题,Next-Key 锁就是为了解决这个问题而存在的。在可重复读(REPEATABLE READ)隔离级别下,使用 **MVCC + Next-Key 锁** 可以解决幻读问题。
|
||||
|
||||
另外,根据针对 SQL 语句检索条件的不同,加锁又有以下三种情形需要我们掌握。
|
||||
|
||||
- `Record Lock` - **行锁对索引项加锁,若没有索引则使用表锁**。
|
||||
- `Gap Lock` - 对索引项之间的间隙加锁。锁定索引之间的间隙,但是不包含索引本身。例如当一个事务执行以下语句,其它事务就不能在 t.c 中插入 15。`SELECT c FROM t WHERE c BETWEEN 10 and 20 FOR UPDATE;`
|
||||
- `Next-key lock` -它是 `Record Lock` 和 `Gap Lock` 的结合,不仅锁定一个记录上的索引,也锁定索引之间的间隙。它锁定一个前开后闭区间。
|
||||
|
||||
索引分为主键索引和非主键索引两种,如果一条 SQL 语句操作了主键索引,MySQL 就会锁定这条主键索引;如果一条语句操作了非主键索引,MySQL 会先锁定该非主键索引,再锁定相关的主键索引。在 `UPDATE`、`DELETE` 操作时,MySQL 不仅锁定 `WHERE` 条件扫描过的所有索引记录,而且会锁定相邻的键值,即所谓的 `next-key lock`。
|
||||
|
||||
当两个事务同时执行,一个锁住了主键索引,在等待其他相关索引。另一个锁定了非主键索引,在等待主键索引。这样就会发生死锁。发生死锁后,`InnoDB` 一般都可以检测到,并使一个事务释放锁回退,另一个获取锁完成事务。
|
||||
|
||||
## 四、分库分表
|
||||
|
||||
### 什么是分库分表
|
||||
|
||||
> ❓ 常见问题:
|
||||
>
|
||||
> 什么是分库分表?什么是垂直拆分?什么是水平拆分?什么是 Sharding?
|
||||
>
|
||||
> 分库分表是为了解决什么问题?
|
||||
|
@ -576,7 +475,7 @@ MVCC 不能解决幻影读问题,Next-Key 锁就是为了解决这个问题而
|
|||
|
||||
#### 分库分表策略
|
||||
|
||||
- 哈希取模:`hash(key) % N`。
|
||||
- 哈希取模:`hash(key) % N` 或 `id % N`
|
||||
- 优点:可以平均分配每个库的数据量和请求压力(负载均衡)。
|
||||
- 缺点:扩容麻烦,需要数据迁移。
|
||||
- 范围:可以按照 ID 或时间划分范围。
|
||||
|
@ -616,8 +515,6 @@ MVCC 不能解决幻影读问题,Next-Key 锁就是为了解决这个问题而
|
|||
|
||||
### 分库分表的问题
|
||||
|
||||
> ❓ 常见问题:
|
||||
>
|
||||
> - 分库分表的常见问题有哪些?
|
||||
>
|
||||
> - 你是如何解决分库分表的问题的?
|
||||
|
@ -969,9 +866,10 @@ SQL 关键字尽量大写,如:Oracle 默认会将 SQL 语句中的关键字
|
|||
高级别范式的依赖于低级别的范式,1NF 是最低级别的范式。
|
||||
|
||||
<div align="center">
|
||||
<img src="http://dunwu.test.upcdn.net/cs/database/RDB/数据库范式.png!zp"/>
|
||||
<img src="http://dunwu.test.upcdn.net/cs/database/RDB/数据库范式.png"/>
|
||||
</div>
|
||||
|
||||
|
||||
#### 第一范式 (1NF)
|
||||
|
||||
属性不可分。
|
||||
|
@ -1050,9 +948,7 @@ Sname, Sdept 和 Mname 都部分依赖于键码,当一个学生选修了多门
|
|||
| 学院-1 | 院长-1 |
|
||||
| 学院-2 | 院长-2 |
|
||||
|
||||
## 八、Mysql 特性
|
||||
|
||||
### 存储引擎
|
||||
## 八、Mysql 存储引擎
|
||||
|
||||
Mysql 有多种存储引擎,**不同的存储引擎保存数据和索引的方式是不同的,但表的定义则是在 Mysql 服务层统一处理的**。
|
||||
|
||||
|
@ -1063,7 +959,7 @@ Mysql 有多种存储引擎,**不同的存储引擎保存数据和索引的方
|
|||
- **CSV** - 可以将 CSV 文件作为 Mysql 的表来处理,但这种表不支持索引。
|
||||
- **MEMORY** 。所有的数据都在内存中,数据的处理速度快,但是安全性不高。
|
||||
|
||||
#### InnoDB vs. MyISAM
|
||||
### InnoDB vs. MyISAM
|
||||
|
||||
InnoDB 和 MyISAM 是目前使用的最多的两种 Mysql 存储引擎。
|
||||
|
||||
|
|
8151
docs/yarn.lock
8151
docs/yarn.lock
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue