🚀 Database
1、数据库基础
1.1 事务的概念和特性
1.2 锁
1.3 锁协议
1.4 事务日志
1.5 MVCC实现原理
1.6 基础知识
1.6.1 三范式
1.6.2 多表连接方式
1.6.3 存储过程
1.6.4 TRUNCATE和DROP的区别
1.6.5 触发器
1.6.6 视图
2、MySQL
2.1 索引
2.2 索引组织表
2.3 InnoDB和MyISAM的区别
2.4 Checkpoint技术
2.5 宕机恢复原理
2.6 数据库优化
2.7 分库分表
2.8 一致性哈希算法
2.9 主从复制
3、Redis
3.1 概述
3.1.1 为什么Redis单线程还这么快
3.1.2 Redis数据类型
3.1.3 持久化机制
3.1.4 过期机制和内存淘汰策略
3.2 线程模型
3.3 分布式问题
3.3.1 Redis实现分布式锁
3.4 缓存异常
3.4.1 缓存击穿、缓存雪崩
3.5 高可用
3.5.1 主从复制
3.5.2 哨兵模式
3.5.3 集群模式
-
+
游客
注册
登录
事务的概念和特性
## 事务的概念 1. 数据库中的事务是一个**操作序列**,包含了一组**数据库操作命令**。 2. 事务把这一组命令**作为一个整体一起向系统提交或撤销操作请求**,即**这一组命令要么都执行,要么都不执行**,因此事务是一个**不可分割的工作逻辑单元**。 ## 事务的特性 > 注:如果没有特别说明,下面的事物的特性的原理指的都是**MySQL**的**InnoDB**引擎。 事务具有 4 个特性,即**原子性**(Atomicity)、**一致性**(Consistency)、**隔离性**(Isolation)、**持久性**(Durability),这 4 个特性通常简称为**ACID**。 ### 原子性 #### 含义 1. **事务开始后的所有操作,要么全部做完,要么全部不做,不可能停滞在中间环节**。 2. 事务执行过程中**出错**,会**回滚到事务开始前的状态**,所有的操作**就像没有发生一样**。 #### 实现原理 1. InnoDB 引擎使用**Undo Log**(回滚日志)来**保证原子性操作**,我们对数据库中的每一条数据的改动(Insert、Delete、Update)都会被记录到 Undo Log 中,比如以下这些操作: 1. **插入**一条记录时,至少要把这条记录的**主键**记下来,之后回滚的时候只需把这个主键对应的记录删掉就好了。 2. **删除**一条记录时,至少要把这条**记录中的内容**都记下来,这样之后回滚时再把由这些内容组成的记录插入到表中就好了。 3. **修改**一条记录时,至少要把修改这条记录前的**旧值**都记录下来,这样之后回滚时再把这条记录更新为旧值就好了。 2. 当**事务执行失败**或者**调用了 `rollback` 方法**时,就会触发**回滚**事件,**利用 Undo Log 中的记录将数据回滚到修改之前的样子**。 ### 一致性 #### 含义 1. **事务开始前和结束后**,数据库的**完整性约束没有被破坏**。 2. 比如**A 向 B 转账**,**不可能 A 扣了钱**,**B 却没收到**。 #### 如何保证一致性 这个主要包括两个方面,一个是**数据库层面**,一个是**应用层面**。 1. **数据库层面**: 1. 数据库**通过原子性、隔离性、持久性来保证一致性**。 2. 也就是说四大特性中,**一致性是目的**,**其他三种特性是手段**。 3. 数据库**必须要实现其它三大特性**,**才能实现一致性**,例如,原子性无法保证,显然一致性也无法保证。 2. **应用层面**: 1. **通过代码判断数据库数据是否有效**,**然后决定回滚还是提交数据**。 2. 如果我们**故意在事务里面写出违反约束的代码**,**一致性还是无法保证的**,例如,我们在转账中,故意不给 B 账户加钱,那一致性还是无法保证,因此,还必须从应用层考虑。 ### 隔离性 #### 含义 1. 不同的事务**并发操作相同的数据**的时候,每个事务都有各自**完整的数据空间**,即**一个事务内部的操作及使用的数据对其他并发事务是隔离的**,并发执行**各个事务之间不能相互干扰**。 #### 为什么要进行事务隔离 因为数据库中有多个事务并发执行的时候,可能会导致 **丢失修改**(Lost Modification)、**脏读**(Dirty Read)、**不可重复读**(Non-repeatable Read)、**幻读**(Phantom Read)。 ##### 丢失修改 ###### 含义 1. 丢失修改是指 **两个事务 $A$ 和 $B$ 读入同一数据并修改**,**$B$ 提交的结果破坏了 $A$ 提交的结果**,**导致 $A$ 的修改被丢失**。 ###### 示例 1. 考虑飞机订票系统中的一个活动序列: ![事务-隔离性-丢失修改](https://notebook.ricear.com/media/202207/2022-07-30_212547_7875530.8881465817784129.png) 1. $T_1$ 时 $A$ 售票点($A$ 事务)读出某航班的机票余额 $num = 16$。 2. $T_1$ 时 $B$ 售票点($B$ 事务)读出同一航班的机票余额 $num = 16$。 3. $T_2$ 时 $A$ 售票点卖出一张机票,修改余额 $num = 15$,并提交事务。 4. $T_3$ 时 $B$ 售票点也卖出一张机票,修改余额 $num = 15$,并提交事务。 5. 结果明明卖出两张机票,数据库中机票余额只减少 1. ##### 脏读 ###### 含义 1. 脏读是指**事务 B 读取事务 A 还未提交的数据,然后事务 A 进行了回滚,导致事务 B 再次读取数据时和第一次读到的数据不一致了**。 ###### 示例 ![](https://notebook.ricear.com/media/202105/2021-05-25_160846.png) 1. 事务 A 先写数据,把一行数据的值从`null` 改成了`test`,同时事务 A 还**没有提交**。 2. 然后事务 B 过来读取该数据,此时如果事务之间没有有效隔离,那么事务 B 读到的数据就为`test`。 3. 接着事务 A**回滚**了,回滚之后该数据对应的值从`test` 变为`null`。 4. 然后事务 B 再去读取该数据时读到的就是`null` 了,和第一次读取到的数据不一致,这就是**脏读**。 ##### 不可重复读 ###### 含义 1. 事务 A**多次读取同一数据**,事务 B 在事务 A 多次读取的过程中,**对数据作了更新并提交**,导致事务 A**多次读取同一数据时结果不一致**。 2. 不可重复读和脏读的区别在于**脏读**是由于别的事务**回滚**导致,而**不可重复读**读到的其实是已经**提交**的数据。 ###### 示例 ![](https://notebook.ricear.com/media/202105/2021-05-25_161956.png) 1. 事务 A 先去读取一行数据,读到的值是`null`。 2. 事务 B 去修改数据,改成了`test`,这里和前面不一样的地方就在于事务 B 还**提交了**,**不回滚了**。 3. 事务 A 第二次去读,读到的是`test`,和第一次读到的`null` 不一样。 ##### 幻读 ###### 含义 1. 事务 B 在事务 A**多次读取同一数据的过程中**,**添加了部分数据**,导致事务 A**多次读取到的数据不一致**,**仿佛出现了幻觉一样**。 ###### 示例 ![](https://notebook.ricear.com/media/202105/2021-05-25_162838.png) 1. 事务 A 里有一个条件查询的语句`select name from t where id > 10`,他进行了一次范围查询,查到了 10 行数据。 2. 然后事务 B 往数据库里面插入了一条数据。 3. 事务 A 再用刚才的查询条件查询时,发现查到了 15 条数据,其中 5 条是之前没见过的,这时事务 A 以为自己出现了幻觉,这就是幻读。 #### 事务的隔离级别有哪些 > 为了解决上面的那些问题,提出了**隔离级别**的概念。 MySQL 中主要包括四种隔离级别,分别是**读未提交**(Read Uncommitted)、**读提交**(Read Committed)、**可重复读**(Repetable Read)、**串行化**(Serializable)。 > - 标准情况下一般使用 **锁** 来实现隔离级别的控制,需要 **频繁的加锁解锁**,而且很容易导致 **读写阻塞**(例如在读提交级别下,事务 $A$ 更新了数据行 1,事务 $B$ 则在事务 $A$ 提交前读取数据行 1都要等待事务 $A$ 提交并释放锁)。 > - 所以 InnoDB 采用了 **[MVCC](https://notebook.ricear.com/project-37/doc-744)** 机制: > - 在 **读提交** 和 **可重复读** 两种隔离级别下的事务对于 `SELECT` 操作会访问 **版本链** 中的记录。 > - 这就使得别的事务可以修改这条记录,实现了读-写、写-读的 **并发执行**,提升了系统的性能。 ##### 读未提交 ###### 含义 1. 读未提交其实就是**可以读到其它事务未提交的数据**,但**没有办法保证我们读到的数据最终一定是提交后的数据**,如果中间发生**回滚**,那就会出现**脏数据**问题。 2. 读未提交主要用来解决 **丢失修改** 的问题。 > :thinking: 读未提交的实现原理是什么? > > 1. **事务对当前被读取的数据不加锁**(可能产生脏读问题)。 > > 2. **事务在更新某数据的瞬间**(就是发生更新的瞬间),**必须先对其加行级排他锁,**直到事务结束才释放**(保证了 事务 $A$ 修改数据时,其他事务不能修改数据)。 > :thinking: 读未提交为什么能解决丢失修改的问题? > > 1. 丢失修改是因为事务 $A$ 修改数据的时候,其他事务也可以修改。 > 2. 只要保证某事务修改数据过程中,其他事务不能修改就可以了,但是可以读取。 > 读未提交**没办法解决脏数据**问题,更不能解决**可重复读**和**幻读**的问题。 > > MySQL 事务隔离其实是依靠锁来实现的,加锁自然会带来性能的损失,而读未提交隔离级别读取数据时是**不加锁**的,所以他的**性能是最好的**,没有加锁、解锁带来的性能开销。 ###### 示例 1. 设置全局隔离级别为**读未提交**。 ```sql set global transaction isolation level read uncommitted; ``` 2. 启动两个事务,分别是事务 A 和事务 B,在事务 A 中使用 `update` 语句,修改 `age` 的值为 10,初始时是 1。 3. 在执行完 `update` 语句之后,在事务 B 中查询 `user` 表,会看到 `age` 的值已经是 10 了,这时候事务 A 还没有提交,而此时事务 B 有可能拿着已经修改过的 `age=10` 去进行其他操作了。 4. 在事务 B 进行其他操作的过程中,很有可能事务 A 由于某些原因,进行了事务回滚操作,那其实 B 得到的数据就是脏数据了,拿着脏数据去进行其它计算,那结果肯定也是有问题的。 ![](https://notebook.ricear.com/media/202105/2021-05-25_171034.png) ##### 读提交 ###### 含义 1. 读提交就是**一个事务只能读到其它事务已经提交过的数据**。 2. 读提交主要用来解决 **脏读** 的问题。 > :thinking: 读提交的实现原理是什么? > > 1. 事务对当前 **读取** 的数据加 **[行级共享锁](https://notebook.ricear.com/project-37/doc-716/#%E5%85%B1%E4%BA%AB%E9%94%81)**(当读到时才加锁),一旦**读完该行**,**立即释放**该行级共享锁。 > 2. 事务在 **更新** 某数据的瞬间(就是发生更新的瞬间),必须先对其加 **[行级排他锁](https://notebook.ricear.com/project-37/doc-716/#%E6%8E%92%E4%BB%96%E9%94%81)**,直到**事务结束才释放**。 > :warning: InnoDB 在读提交隔离级别下事务对当前读取的数据不加锁,通过 **[快照读](https://notebook.ricear.com/project-37/doc-744/#%E5%BF%AB%E7%85%A7%E8%AF%BB)** 来读取数据。 > :thinking: 读提交为什么能解决脏读的问题? > > 1. 脏读是因为事务 $A$ 读取到了别的事务修改但没有提交的数据。 > 2. 只要事务修改的数据,没有提交之前不被其他事务读到就可以避免脏读。 > :warning: 读提交可以解决脏读的问题,但是**不能解决可重复读和幻读的问题**。 > :warning: 读提交是**大多数流行数据库的默认事务隔离级别**,比如**Oracle**,但**不是 MySQL 的默认隔离级别**。 ###### 示例 1. 把事务隔离级别改为**读提交**。 ```sql set global transaction isolation level read committed; ``` 2. 同时开启事务 A 和事务 B 两个事务,在事务 A 中使用 `update` 语句将 `id=1` 的记录行 `age` 字段更改为 10。 3. 此时,在事务 B 中使用 `select` 语句进行查询,我们发现在事务 A 提交之前,事务 B 中查询到的记录 `age` 一直为 1,直到**事务 A 提交**,此时在事务 B 中使用 `select` 查询时,发现 `age` 的值已经是 10 了。 4. 这就出现了一个问题,在同一事务中(本例中的事务 B),事务的不同时刻,同样的查询条件,查询出来的记录内容是不一样的,事务 A 的提交影响了事务 B 的查询结果,这就是**不可重复读**,因此,虽然读提交隔离级别**解决了脏读问题**,但是**不能解决不可重复读和幻读问题**。 ![](https://notebook.ricear.com/media/202105/2021-05-26_100923.png) ##### 可重复读 ###### 含义 1. 可重复读是指**事务不会读到其它事务对已有数据的修改,即使其它事务已经提交**,也就是说,**事务开始读到的已有数据是什么,在本事务提交前的任意时刻,这些数据的值都是一样的**。 2. 可重复读主要用来解决 **不可重复读** 的问题。 > :thinking: 可重复读的原理是什么? > > 1. 事务在 **读取** 某数据的瞬间(就是开始读取的瞬间),必先对其加 **行级共享锁**,直到 **事务结束才释放**(保证了事务在读取数据过程中,别的事务不允许修改,但是对于其他事务新插入的数据是可以读到的,这就引发了 **幻读** 的问题)。 > 2. 事务在 **更新** 某数据的瞬间(就是发生更新的瞬间),必须先对其加 **行级排他锁**,直到 **事务结束才释放**。 > :warning: InnoDB 在可重复读隔离级别下事务对当前读取的数据不加锁,通过 **[快照读](https://notebook.ricear.com/project-37/doc-744/#%E5%BF%AB%E7%85%A7%E8%AF%BB)** 来读取数据。 > :thinking: 可重复读为什么能解决不可重复读的问题? > > 1. 不可重复读是因为事务在两次相同查询的中间过程,别的事务执行并提交修改操作,使得两次读取的结果内容不一致。 > 2. 只要事务不会读到其他事务对已有数据的修改,即使其他事务已经提交,即可解决不可重复读问题。 > :information_desk_person: MySQL 的默认隔离级别就是**可重复读**,但 MySQL 的可重复读隔离级别通过 **[间隙锁](https://notebook.ricear.com/project-37/doc-716/#%E9%97%B4%E9%9A%99%E9%94%81)** 解决了幻读问题。 ###### 示例 1. 修改全局隔离级别为**可重复读**。 2. 同时开启两个事务: 1. 测试**解决不可重复读问题:** 1. 事务 A 先修改了数据,并且**在事务 B 之前提交**,事务 B 在事务开始后和事务 A 提交之后读取的数据相同,说明可重复读隔离级别**解决了不可重复读问题**。 2. 测试**产生幻读问题:** 1. 事务 A 开始后,执行`update` 操作,将`age=1` 的记录的`name` 改为“风筝 2 号”。 2. 事务 B 开始后,在事务执行完`update` 后,执行`insert` 操作,插入记录`age=1, name=古时的风筝 `,这和事务 A 修改的那条记录值相同,然后提交。 3. 事务 B 提交后,事务 A 执行`select`,查询`page=1` 的数据,这时会发现多了一行,并且发现还有一条`age=1, name=古时的风筝 ` 的记录,这就是事务 B 刚刚插入的,这就是**幻读**,说明可重复读隔离级别**解决不了幻读的问题**。 ![](https://notebook.ricear.com/media/202105/2021-05-26_103448.png) ![](https://notebook.ricear.com/media/202105/2021-05-26_103458.png) ##### 串行化 1. 串行化相当于**单线程**,**后一个事务的执行必须等待前一个事务结束**。 2. 串行化主要用来解决 **幻读** 的问题。 > :thinking: 串行化的原理是什么? > > 1. 事务在 **读取** 数据时,必须先对其加 **表级共享锁**,直到 **事务结束才释放**。 > 2. 事务在 **更新** 数据时,必须先对其加 **表级排他锁**,直到 **事务结束才释放**。 > :thinking: 串行化为什么能解决幻读问题? > > 1. 幻读是因为事务在两次相同查询的中间过程,别的事务执行并提交了新增或者删除操作,使得两次读取的数据行数不一致。 > 2. 通过在一次操作中对整张表进行加锁,从而其他事务不能对整张表进行增、删、改操作,所以不会有行记录的增加或减少,从而保证了当前事务两次读之间数据的一致性,解决了幻读问题。 > :information_desk_person: 串行化是 4 种隔离级别中**隔离效果最好**的,**解决了脏读、不可重复读、幻读的问题**,但是串行化是这 4 种隔离级别中**性能最差**的。 ### 持久性 #### 含义 1. **事务一旦提交,它对数据库的改变就应该是永久性的**,**接下来的操作或故障不应该对其有任何影响**。 #### 实现原理 1. InnoDB 引擎使用**Redo Log**(归档日志)来实现持久性。 2. 当**有一条记录需要更新**的时候,InnoDB 引擎会**先把记录写到 Redo Log 日志**里面,并**更新内存**,这个时候更新就算完成了。 3. 当**数据库宕机重启**的时候,会**将 Redo Log 中的内容恢复到数据库**中,再**根据 Undo Log 和 Binlog 内容决定回滚数据还是提交数据**。 4. 相比直接刷磁盘,Redo Log 有以下两个优势: 1. Redo Log 体积小,毕竟**只记录了哪一页修改了啥**,因此**体积小**,**刷盘快**。 2. Redo Log 是**一直往末尾进行追加**,属于**顺序 IO**,**效率比随机 IO 要高**。 ## 参考文献 1. [事务的概念和特性?](https://github.com/wolverinn/Waking-Up/blob/master/Database.md#%E4%BA%8B%E5%8A%A1%E7%9A%84%E6%A6%82%E5%BF%B5%E5%92%8C%E7%89%B9%E6%80%A7) 2. [数据库事务的概念和特性](http://c.biancheng.net/view/7289.html)。 3. [你可能知道事务的四大特性,但是你不一定知道事务的实现原理](https://database.51cto.com/art/202001/608826.htm)。 4. [小胖问我:MySQL 事务与 MVCC 原理?](https://segmentfault.com/a/1190000039809030) 5. [【原创】Mysql 中事务 ACID 实现原理](https://www.cnblogs.com/rjzheng/p/10841031.html)。 6. [MySQL 事务隔离性与隔离级别原理](https://blog.csdn.net/qq_25448409/article/details/113129651)。 7. [图解脏写、脏读、不可重复读、幻读](https://zhuanlan.zhihu.com/p/164924094)。 8. [数据库常用的事务隔离级别都有哪些?都是什么原理?](http://blog.itpub.net/31098809/viewspace-2760548) 9. [MySQL 事务隔离级别和实现原理(看这一篇文章就够了!)](https://zhuanlan.zhihu.com/p/117476959)。 10. [深入理解MySQL中事务隔离级别的实现原理](https://segmentfault.com/a/1190000025156465)。 11. [隔离级别实现原理-锁机制分析](https://www.jianshu.com/p/8d75ac0406b4)。 12. [丢失的修改、不可重复读、读脏数据、幻影读](https://www.daimajiaoliu.com/daima/4edf86aeb9003f8)。
ricear
2022年8月14日 21:38
©
BY-NC-ND(4.0)
转发文档
收藏文档
上一篇
下一篇
手机扫码
复制链接
手机扫一扫转发分享
复制链接
Markdown文件
分享
链接
类型
密码
更新密码