前言
将一条sql提交给mysql之后都发生了什么?
-
缓存:InnoDB作为MySQL的存储引擎,数据是存放在磁盘中的,但如果每次读写数据都需要磁盘IO,效率会很低。为此,InnoDB提供了缓存(Buffer Pool)。当从数据库读取数据时,会首先从Buffer Pool中读取,如果Buffer Pool中没有,则从磁盘读取后放入Buffer Pool;当向数据库写入数据时,会首先写入Buffer Pool,Buffer Pool中修改的数据会定期刷新到磁盘中(这一过程称为刷脏)。Buffer Pool的使用大大提高了读写数据的效率,但是也带了新的问题:如果MySQL宕机,而此时Buffer Pool中修改的数据还没有刷新到磁盘,就会导致数据的丢失,事务的持久性无法保证,所以我们需要使用mysql中的日志来保证数据持久性。
-
解析器:通过关键字将SQL语句进行解析,并生成对应的解析树。MySQL解析器将使用MySQL语法规则验证和解析查询。
-
解析树:判断sql语法是否正确
-
预处理器:根据一些MySQL规则进行进一步检查解析树是否合法,例如检查数据表和数据列是否存在,还会解析名字和别名,看看它们是否有歧义。
-
查询优化器:将解析树转化成执行计划。一条查询可以有多种执行方法,最后都是返回相同结果。优化器的作用就是找到这其中最好的执行计划。
一、什么是事务
事务是由一条或者多条对数据库操作的SQL语句所组成的一个不可分割的单元,只有当事务中的所有操作都正常执行完了,整个事务才会被提交给数据库;
二、为什么要有事务
当需要对数据表执行一系列多个操作的情况下,为了防止这些操作中的部分操作执行成功而另一些操作执行失败,从而导致数据不正确,我们就需要使用事务了。
三、事务的三大特性(ACID)
1. 原子性(atomicity):一个事务中的所有操作,要么全部完,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。
实现原理:undo log 日志 :
当事务对数据库进行修改时,InnoDB会生成对应的undo log;如果事务执行失败或调用了rollback,导致事务需要回滚,便可以利用undo log中的信息将数据回滚到修改之前的样子。undo log属于逻辑日志,它记录的是sql执行相关的信息。当发生回滚时,InnoDB会根据undo log的内容做与之前相反的工作:
对于每个insert,回滚时会执行delete;
对于每个delete,回滚时会执行insert;
对于每个update,回滚时会执行一个相反的update;
以update操作为例:当事务执行update时,其生成的undo log中会包含被修改行的主键(以便知道修改了哪些行)、修改了哪些列、这些列在修改前后的值等信息,回滚时便可以使用这些信息将数据还原到update之前的状态
2. 一致性(consistency):在事务开始之前和事务结束以后,数据库的完整性没有被破坏。
3. 隔离性(isolation):数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。
4. 持久性(durability):事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。
实现原理:redo log 日志 :
当数据修改时,除了修改Buffer Pool中的数据,还会在redo log记录这次操作;当事务提交时,会调用fsync接口对redo log进行刷盘。如果MySQL宕机,重启时可以读取redo log中的数据,对数据库进行恢复。所有修改先写入日志,再更新到Buffer Pool,保证了数据不会因MySQL宕机而丢失,从而满足了持久性要求。
四、事务操作
set @@autocommit = 0;
set session transaction isolation level [隔离级别]
Read committed / Repeatable read / Serializable
/* 已提交读 可重复读 序列化 */
begin;//开启事务,可以省略
(查询/修改 1);
savepoint p1;//设置回滚点
(查询/修改 2);
rollback to p1;//回滚到2之前(不是结束事务操作)
commit;//结束事务
rollback;//结束事务,并回滚到事务执行前
五、隔离性
当两个事务同时进行时,会引发下面三个问题:
1. 脏读(Dirty Read)
对于两个事务T1与T2,T1读取了已经被T2更新但是还没有提交的字段之后,若此时T2回滚,T1读取的内容就是临时并且无效的
例: B用户给A用户转账50RMB,但是转账过程中出错误:
A用户余额从0变为60。
那么在rollback前:
A: 开启事务,查看自己账户余额:select money from A;
此时money = 60,称之为脏数据。
B: 银行系统检测出交易错误,执行rollback。
A: 再次查看账户,余额为0。
2. 不可重复读(NonRepeatable Read)
对于两个事务T1和T2,T1读取了一个字段,然后T2更新了该字段并提交之后,T1再次提取同一个字段,前后便不一致了。
例: A拿着银行卡去消费50元,当他买单时(A事务开启),
收费系统事先检测到他的卡里有50元,
就在这个时候!B取出了50元,并提交。
当收费系统准备扣款时,再检测卡里的金额,发现已经没钱了
(第二次检测金额当然要等待B转出金额事务并提交完)。
即收费系统前后读到的数据不同
3.幻读
幻读,并不是说两次读取获取的结果不同,幻读侧重的方面是某一次的 select 操作得到的结果的数据状态无法支撑后续的业务操作。
例:select 某记录是否存在,不存在,准备插入此记录。
但执行 insert 时发现此记录已存在,无法插入,此时就发生了幻读。
针对以上问题,Mysql提出了不同隔离级别所解决的不同的事务并发问题。
隔离级别 | 脏读 | 不可重复读 | 虚/幻读 |
---|---|---|---|
未提交读TRANSACTION_READ_UNCOMMITTED | 可以 | 可以 | 可以 |
已提交读TRANSACTION_READ_COMMITTED | 不可以 | 可以 | 可以 |
可重复读TRANSACTION_REPEATABLE_READ | 不可以 | 不可以 | 可以 |
可序列化 / 串行化TRANSACTION_SERIALIZABLE | 不可以 | 不可以 | 不可以 |
要真正理解这四个级别是如何解决这三个问题的,则需要了解MySQL锁机制和MVCC机制。
锁机制:
行锁和表锁:
- 表级锁:开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低。
- 行级锁:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最 低,并发度也最高
记录锁(Record Locks):在索引记录上加锁。(行锁)
间隙锁(Gap Locks):在索引记录之间加锁,或者在第一个索引记录之前加锁,或者在最后一个索引记录之后加锁。
Next-Key Locks:在索引记录上加锁,并且在索引记录之前的间隙加锁。它相当于是Record Locks与Gap Locks的一个结合
共享读锁和排他写锁:
- 共享锁(S):允许一个事务去读一行,阻止其他事务获得相同数据集的排他锁。
- 排他锁(X):允许获得排他锁的事务更新数据,阻止其他事务取得相同数据集的。
set @@autocommit = 0;
begin;//开启事务
select * from user;//表锁
select * from user where id = 10;//行锁
select * from user where id > 10;//间隙锁
commit;//结束事务
死锁:是指两个或者两个以上线程在执行过程中,因争夺资源而产生互相等待的现象,若无外力作用,他们都将无法推进下去。
A窗口:
select * from table1 where id = 1 for update; 1锁
B窗口:
select * from table2 where id = 2 for update; 2锁
A窗口:
select * from table2 where id = 2 for update;
A在等B释放2锁
B窗口:
select * from table1 where id = 1 for update;
B在等A释放1锁
MVCC机制:
尽量的不去使用这些锁,一旦这些锁加上之后事务与事务之间就变成了完全的串行执行,在隔离级别中串行化其实就是这样做的。
MVCC的实现,是通过保存数据在某一个时间点的快照来实现的,每一个事务只操作自己版本下的数据。因此每一个事务无论执行多长时间看到的数据,都是一样的。
四种隔离级别的实现原理:
1. 未提交读(Read uncommitted):一般不使用
2. 已提交读(Read committed):
在RC级别中,数据的读取不加锁,但是数据的写入、修改和删除是需要加锁的(Record Locks)。RC级别通过MVCC机制解决了脏读问题。只要没有commit那么修改就是自己版本下的数据。但是commit之后 改变的就是所有数据,包括原数据和其他事务副本信息中的数据。
3.可重复读(Repeatable read):
数据的读取不加锁,但是数据的写入、修改和删除是需要加锁(Record Locks)的。通过Mvcc机制解决了不可重复读的问题,相比起已提交读,即使commit操作的也是自己版本数据。
4.序列化(Serializable):
所有锁都加了,读加共享锁,写加排他锁,读写互斥。没有mvcc机制,使用的悲观锁的理论,(加的锁就是Next-Key Locks)实现简单,数据更加安全,但是并发能力非常差,效率最低。