1.事务概述
事务是一种机制,也可称为是一个执行单元,执行单元里面包含多条SQL语句,这些SQL语句要么全部执行成功,要么都不成功,不可分割。在mysql数据库中只有用Innodb引擎的表才能使用事务,而且默认自动提交事务。
2.事务特性(ACID)
- 原子性:事务不可在分割的原则称为原子性,SQL语句要么全部执行成功,要么全部失败。
- 一致性:在事务前——事务后,数据必须按照原则保证完整性。
- 隔离性:防止并发情况下多个事务操作数据而导致数据的实际结果不一样。
- 持久性:当事务结束后,对数据的修改就是永久的,因为数据保存在磁盘
3.多个事务并发可能会产生的问题
3.1脏读
对于事务T1和T2,如果T1读取了T2事务中还未提交的数据,那么这个数据就是脏数据。比如A账户有100元,此时T1和T2同时开启事务,T2事务将A账户充值50元,那么此时T1读取到的A账户为150元,但是此时T2出现问题了,发生了回滚,A账户实际还是只有100元,那么这个T1读到的150就是一条脏数据。
3.2不可重复读
对于事务T1和T2,如果在事务T1中多次读到的同一数据不一致,那么就是不可重复读。比如A账户有100元,此时T1和T2同时开启事务,T1第一次读A账户为100元,此时T2在A账户充值50元,并提交了T2事务(注意T2已经提交了事务,所以不会发生脏读)。然后T1再读取A账户,发现为150元,那么在T1这个事务中,还未提交事务T1前读取两次A账户会有两个不同的结果。
3.3幻读
此时用户表中总共有5条数据,此时T1和T2同时开启事务,T1第一次读到表中数据有5条,此时T2在用户表中新增了一条数据,并提交了T2事务,此时T1再次读取用户表发现数据有6条,那么在T1这个事务中,还未提交事务T1前读取两次用户表中的数据条数不一样,这就是幻读。
不可重复侧重于数据的修改,而幻读侧重与数据的添加或者删除对数据条数发生的变化。
4.事务的隔离级别
事务的隔离级别来解决并发事务时出现的问题,不同的隔离级别可以解决不同的问题,隔离级别越高,一致性就越强。mysql事务的隔离级别有读未提交、读已提交、可重复读、串行化。mysql默认使用的是可重复读。
事务隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
读未提交 | 会出现 | 会出现 | 会出现 |
读已提交 | 不会出现 | 会出现 | 会出现 |
可重复读 | 不会出现 | 不会出现 | 会出现 |
串行化(通过锁表实现,效率太低) | 不会出现 | 不会出现 | 不会出现 |
mysql默认不使用串行化的原因是会严重影响效率,因为要锁表,相当于是单线程操作。
5.事务内部的实现原理 (重点)
5.1需要实现的核心问题
事务无非就是要保证可靠性和并发处理,可靠性就是我们必须记录数据修改前和修改后的状态,为回滚做保证。并发处理就是多个事务同时操作数据时,各个事务必须隔离起来操作,需要使用事务的隔离级别。
BUffer Poll:mysql的表数据都是存放在磁盘上的,为了提升读写性能Innodb提供了缓冲池(Buffer Poll),Buffer Poll中存储了磁盘数据页的映射,读取时先从缓存中读取,如果没有,则从磁盘中读取,之后放入缓冲池。
5.2持久性的实现
redolog是为了保证数据的持久性,分为内存日志缓冲和磁盘日志,属于物理日志,循环写入(类似循环队列),有一个LSN的标志位,记录下一次写入的位置。mysql为了提升性能对于每次修改并不会立即的同步到磁盘中,而是先存在Buffer Poll缓冲池中并将修改记录存在内存的redo log当中,后面再保存到磁盘的redo log中,然后后台的线程才会去做缓冲池和磁盘的数据同步,如果此时宕机了,那么在重启后会读取redolog来恢复数据,来保证数据的一致性。
redolog刷盘的时机:
- 每秒刷新一次
- 事务提交时
- redolog buffer剩余空间小于 1/2时
5.3原子性的实现
undolog是为了保证数据的原子性,undolog也叫回滚日志,记录的是数据修改前的信息,新增的信息和删除的信息,为的是在发生错误的时候可以回滚到没被修改之前的状态。
5.4隔离性的实现
5.4.2mysql行锁技术
mysql在并发事务读取数据和修改数据下,可以使用行锁来进行控制,不然很难保证事务的隔离性。这两种锁为共享锁和排他锁,属于行锁。其使用关系如下:
共享锁 | 排他锁 | |
---|---|---|
共享锁 | 可并行使用 | 不可并行使用 |
排他锁 | 不可并行使用 | 不可并行使用 |
在对同一行数据时,当在加了共享锁,再次加共享锁可以,但是如果要加排他锁,必须等到共享锁释放。
在对同一行数据时,当加了排他锁,无论加读写锁都需要阻塞,必须等到排他锁释放。
在Innodb引擎中,insert、delete、update语句自动会加上排他锁,而普通select语句默认不加任何锁,如果查询要加排他锁,语句为select ... for update。
在读已提交(RC)的隔离级别中,是使用排他锁来解决脏读问题,比如:当一个事务要修改数据时,执行update语句会给数据加上排他锁,这意味着其他的事务无法同时读取或修改此数据,直到当前的事务提交。
5.4.3MVCC(内含当前读和快照读和ReadView知识点讲解)
MVCC叫做多版本并发控制,实现思想是通过数据多版本来做到读写分离,从而做到不加锁来实现读写并行,MVCC的实现依靠的是undo log日志和read view。Innodb中的MVCC是通过在每行记录后面保存两个隐藏的列来实现的,一个保存了行的事务ID,另一个保存了行的回滚版本编号。
-
行的事务ID:每次有事务对此行数据改动时,都会将此事务ID赋值。
-
行的回滚版本编号:每次对此记录进行改动时,都会把旧版本写入到undo log日志中,记录回滚编号,根据回滚版本编号就可以找到修改前的信息,随着版本的增多,所有版本会链接为链表,称为版本链。如下图:
在MySQL中读取数据有两种方式,一种是当前读,另一种是快照读。
- 当前读顾名思义就是读取当前最新的数据,并对数据进行加锁,防止其他事务进行修改,insert、update、delete语句和像select ...for update这种也属于当前读。
- 快照读起到在MVCC中保存数据的快照的作用。
ReadView是一个用于MVCC的数据快照,用于保存某个时刻的表数据信息,所以在MVCC机制中,快照读和ReadView肯定是有关联的。
- 在读已提交(RC)隔离级别下每次读取(select)都会生成一个新的ReadView快照,所以就会出现不可重复读。
- 在可重复读(RR)的隔离级别下当一个事务第一次读取时,就会生成ReadView,第二次查询时仍然会从刚才的ReadView中读数据,所以解决了不可重复读。
5.4.3RR与幻读之间的破事
RR不能根除幻读的问题,只是利用MVCC可以解决普通查询的幻读问题以及利用临建锁解决当前读的幻读问题。
5.4.3.1利用MVCC解决普通查询的幻读问题
因为普通的select语句都是快照读,在一个事务中,第一次读取,生成ReadView,后面的普通读取或范围查询都会使用此数据快照,其他的事务的操作不会影响我的快照数据,所以也不存在幻读的问题。
5.4.3.2利用间隙锁和临建锁采取阻塞的方式解决本事务的幻读问题
-
行锁:行锁是指锁定表中的某一行记录,以防其他的事务对此行数据进行操作,行锁分为共享锁和排他锁。
-
间隙锁:间隙锁锁定的是一个数据范围,为了防止其他事务在此范围内插入新的数据。
临建锁就是行锁和间隙锁的总和,如下情况就是加了临建锁,更多临建锁和行锁的知识请看锁:
5.4.3.3可重复读解决不了幻读的情况 (举例)
在同一个事务中第二次查询前对快照数据进行了修改操作,比如第一次快照读查询的数据为5条,然后生成了一张数据快照,但是下面进行了新增(insert),新增了一条数据,因为在自己事务中修改了数据,所以是可见的,然后接下来快照读时就出现了幻读。举例如下:
BEGIN
SELECT COUNT(*) FROM cum WHERE id>1;
INSERT INTO cum VALUES(14,'李四',200,14);
SELECT COUNT(*)FROM cum WHERE id > 1;
COMMIT
在同一个事务中第一次使用快照读,然后别的事务新增完并提交事务了,第二次使用当前读,这种情况就是自讨苦吃了,第一次快照读,生成了一张快照,然后其他事务修改数据并提交事务,这本来对你的快照没什么影响,可是第二次又使用了当前读,选择读取当前最新的数据,这就会出现幻读。举例如下:
BEGIN
SELECT COUNT(*) FROM cum WHERE id>=1;
//这里别的事务新增了一条数据并且提交了事务
SELECT COUNT(*) FROM cum WHERE id >=1 FOR UPDATE;
COMMIT
5.5一致性的实现
事务的一致性是靠原子性、持久性和隔离性来共同保证的!