mysql:事务
mysql:事务
小吴顶呱呱1.什么是事务
数据库中的的事务是指对数据库执行一批操作,在同一个事务中,这些操作要么全部执行成功,要么全部失败,不存在部分成功的情况。
- 事务是一个源自操作,是一个最小执行单元,可以由一个或者多个sql语句组成。
- 在同一个事务中,所有sql语句都成功执行时,整个事务成功,有一个SQL语句执行失败,整个事务就会执行失败。
2.事务的四大特性(ACID)
-
原子性(Atomicity)
事务的整个操作如原子操作一样,要么全部成功,或者全部失败,这个原子性是从最终结果来看的,从最终结果来看这个过程是不可分割的。
-
一致性(Consistency)
一个事务完成时,必须是所有的数据都保持一直状态
-
隔离性(Isolation)
数据库系统提供的隔离机制,保证事务不受外部并发操作影响的独立环境下运行
-
持久性(Durability)
事务一旦提交成功或回滚,它对数据库中的数据的改变是永久的
3.隔离级别
并发事务问题:脏读,不可重复读,幻读
-
脏读:一个事务读到另外一个事务还没有提交的数据
如下图:事务A修改了数据库中的一条记录,还未提交,事务B已经读到了修改之后的数据。
-
不可重复读:一个事务先后读取到同一条记录,但俩次读取的数据不同,称之为不可重复读。
如图所示:事务A先查询了数据库中id为1的数据,事务还未结束,此时事务B修改了id为1的数据并提交了事务,之后事务A又查询id为1的数据,就出现了在同一个事务内,查询同一条数据结果不一致。
-
幻读:一个事务按照条件查询数据时,没有对应的数据行,但是在插入数据时,又发现这行数据以及存在,好像出现了幻影。
如下图:事务A先从数据库中查询id为1的数据,发现未查询到,当事务A还未执行插入操作时候,事务B执行插入id为1的数据并提交事务B,此时事务开始执行插入操作,发现插入失败,于是又去查询id为1 的数据,由于事务A还未结束,所以查询id为1的数据还是查询不到。(幻读是在解决了不可重复读的基础上所产生的,所以在同一个事务内,查询的数据都是一致的)
要解决并发事务问题,所以需要对事务进行隔离,一共有四种隔离级别:
- 读未提交
- 读已提交
- 可重复读
- 串行化
注意:事务隔离级别越高,数据越安全,但性能越低,mysql中默认的隔离级别是可重复读,可以解决脏读和不可重复读,不能解决幻读问题。
4. redo log和undo log
4.1 redo log(重做日志)
redo log 是属于引擎层(innodb)的日志,它的设计目标是支持innodb的“事务”的特性,记录的是事务提交时数据页的物理修改,是用来实现事务的持久性。
redo log 能保证对于已经COMMIT的事务产生的数据变更,即使是系统宕机崩溃也可以通过它来进行数据重做,达到数据的一致性,这也就是事务持久性的特征,一旦事务成功提交后,只要修改的数据都会进行持久化,不会因为异常、宕机而造成数据错误或丢失,所以解决异常、宕机而可能造成数据错误或丢是redo log的核心职责。
redo log记录的是操作数据变更的日志,听起来好像和binlog有类似的地方,但是最核心的一点就是redo log记录的数据变更粒度和binlog的数据变更粒度是不一样的,也正因为这个binlog是没有进行崩溃恢复事务数据的能力的。
4.2 Undo log(回滚日志)
用于记录数据被修改之前的信息,作用包含俩个:提供回滚和MVCC(多版本并发控制)。undo log和redo log记录物理日志不一样,它是逻辑日志。
- 可以认为当delete一条日志时候吗,undo log中会记录一条对应的insert记录,反之亦然。
- 当update一条记录时,它记录一条对应相反的update记录,当执行rollback时,就可以从undo log 中的逻辑记录中读取到相应的内容并进行回滚。
- undo log可以实现事务的一致性和原子性
4.3 区别
Undo Log(回滚日志) 和 Redo Log(重做日志)之间的区别,没那么高深,我们只要按字面意思理解就行了。
Redo Log(重做日志)是为了系统崩溃之后恢复数据用的,让数据库照着日志,把没做好的事情重做一遍。 有了Redo Log,就可以保证即使数据库发崩溃重启后,之前提交的记录都不会丢失,这个能力称为 crash-safe。
Undo Log(回滚日志)是为了回滚用的。 在事务提交之前就开始写数据,万一事务到最后又打算不提交了,要回滚,或者系统崩溃了,这些提前写入的数据就变成了脏数据,这时候就必须用Undo Log恢复了。
5.保证事务的隔离性
redo log保证事务的持久性,undo log保证事务的一致性和原子性,那么数据库如何保证事务的隔离性?
数据库是通过加锁,来实现事务的隔离性的。insert,update,delete都会自动添加排它锁,防止其他事物修改这行数据。
加锁确实好使,可以保证隔离性。比如串行化隔离级别就是加锁实现的。但是频繁的加锁,导致读数据时,没办法修改,修改数据时,没办法读取,大大降低了数据库性能。
那么,如何解决加锁后的性能问题的?
答案就是,MVCC多版本并发控制!它实现读取数据不用加锁,可以让读取数据同时修改。修改数据时同时可读取。
6. MVCC
MVCC,即Multi-Version Concurrency Control (多版本并发控制)。它是一种并发控制的方法,一般在数据库管理系统中,实现对数据库的并发访问,在编程语言中实现事务内存。
通俗的讲,数据库中同时存在多个版本的数据,并不是整个数据库的多个版本,而是某一条记录的多个版本同时存在,在某个事务对其进行操作的时候,需要查看这一条记录的隐藏列事务版本id,比对事务id并根据事物隔离级别去判断读取哪个版本的数据。
数据库隔离级别读已提交、可重复读 都是基于MVCC实现的,相对于加锁简单粗暴的方式,它用更好的方式去处理读写冲突,能有效提高数据库并发性能。
7.MVCC实现原理
7.1 事务版本号
事务每次开启前,都会从数据库获得一个自增长的事务ID,可以从事务ID判断事务的执行先后顺序。这就是事务版本号。
7.2 隐式字段
对于InnoDB存储引擎,每一行记录都有两个隐藏列trx_id、roll_pointer,如果表中没有主键和非NULL唯一键时,则还会有第三个隐藏的主键列row_id。
列名 | 是否必须 | 描述 |
---|---|---|
row_id | 否 | 单调递增的行ID,不是必需的,占用6个字节。 |
trx_id | 是 | 记录操作该数据事务的事务ID |
roll_pointer | 是 | 这个隐藏列就相当于一个指针,指向回滚段的undo日志 |
7.3 undo log
undo log,回滚日志,用于记录数据被修改前的信息。在表记录修改之前,会先把数据拷贝到undo log里,如果事务回滚,即可以通过undo log来还原数据。
可以这样认为,当delete一条记录时,undo log 中会记录一条对应的insert记录,当update一条记录时,它记录一条对应相反的update记录。
undo log有什么用途呢?
- 事务回滚时,保证原子性和一致性。
- 用于MVCC快照读。
7.4 undo log 版本链
多个事务并行操作某一行数据时,不同事务对该行数据的修改会产生多个版本,然后通过回滚指针(roll_pointer),连成一个链表,这个链表就称为版本链。如下:
其实,通过版本链,我们就可以看出事务版本号、表格隐藏的列和undo log它们之间的关系。不同事务或相同事务对统一记录进行修改,会导致该记录的undo log生成一条记录版本链表,链表的头部是最新的旧记录,链表尾部是最早的旧记录。
7.5 快照读和当前读
快照读: 读取的是记录数据的可见版本(有旧的版本)。不加锁,普通的select语句都是快照读,如:
1 | select * from core_user where id > 2; |
当前读:读取的是记录数据的最新版本,读取时还要保证其他并发事务不能修改当前记录,会对读取的记录进行加锁,显式加锁的都是当前读。如:select…lock in share mode(共享锁),select…for update、update、insert、delete(排它锁)都是一种当前读。
1 | select * from core_user where id > 2 for update; |
7.6 Read View
- Read View是什么呢? 它就是事务执行SQL语句时,产生的读视图。实际上在innodb中,每个SQL语句执行前都会得到一个Read View。
- Read View有什么用呢? 它主要是用来做可见性判断的,即判断当前事务可见哪个版本的数据
Read View是如何保证可见性判断的呢?我们先看看Read view 的几个重要属性
- m_ids:当前系统中那些活跃(未提交)的读写事务ID, 它数据结构为一个List。
- min_limit_id:表示在生成ReadView时,当前系统中活跃的读写事务中最小的事务id,即m_ids中的最小值。
- max_limit_id:表示生成ReadView时,系统中应该分配给下一个事务的id值。
- creator_trx_id: 创建当前read view的事务ID
Read view 匹配条件规则如下:
- 如果数据事务ID
trx_id < min_limit_id
,表明生成该版本的事务在生成Read View前,已经提交(因为事务ID是递增的),所以该版本可以被当前事务访问。 - 如果
trx_id>= max_limit_id
,表明生成该版本的事务在生成ReadView后才生成,所以该版本不可以被当前事务访问。 - 如果
min_limit_id =<trx_id< max_limit_id
,需腰分3种情况讨论
- (1).如果
m_ids
包含trx_id
,则代表Read View生成时刻,这个事务还未提交,但是如果数据的trx_id
等于creator_trx_id
的话,表明数据是自己生成的,因此是可见的。- (2)如果
m_ids
包含trx_id
,并且trx_id
不等于creator_trx_id
,则Read View生成时,事务未提交,并且不是自己生产的,所以当前事务也是看不见的;- (3).如果
m_ids
不包含trx_id
,则说明你这个事务在Read View生成之前就已经提交了,修改的结果,当前事务是能看见的。