Mysql 事务基本概念及MVCC

一、事务基本概念

  • 在Mysql中只有InnoDB支持事务,支持原子性
  • InnoDB引擎默认自动提交事务,也可以设置取消自动提交,如:设置AUTOCOMMIT为0;通过start transaction或begin开启一个事务,然后使用rollback或者commit 结束这个事务。
  • 因为InnoDB默认会自动产生事务并提交,所以每次执行一个语句都会commit,严重影响了性能。所以可以用begin开启事务。
1.事务id的分配时机

这里要注意在默认情况下,只有执行修改操作(如INSERT、DELETE、UPDATE语句)才会分配一个事务id,SELECT并不会。

如果通过start transaction使用事务时,并不会马上就分配事务id,也要第一次执行修改操作才会分配,如果开启事务后第一条语句时SELECT,系统会产生如下所示的随机id,等到执行了真正的修改操作时,才会分配一个正式的事务id

 trx_id
281479839177256
2.事务id的产生
  • 服务器会在内存中维护一个全局变量,每当需要为某个事务分配一个事务id时,就会把该变量的值当作事务id分配给该事务,并且把该变量自增1
  • 每当这个变量的值为256的倍数时,就会将该变量的值刷新到系统表空间的页号为5的页面中一个称之为Max Trx ID的属性处,这个属性占用8个字节的存储空间。当系统下一次重新启动时,会将上边提到的Max Trx ID属性加载到内存中,将该值加上256之后赋值给我们前边提到的全局变量
3.事务的四大特征
  • Atomicity(原子性):一个事务内的所有所有操作是一个原子,要么全部执行完成,要么全部不执行,不会执行到中间某个步骤。InnoDB的原子性由undo Log来实现,如果发生rollback,就去undo log中找过去的记录来回退。
  • Consistency(一致性):在事务开始之前和事务结束以后,数据库的完整性没有被破坏。
  • Isolation(隔离性):数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。在第二节中将详细的介绍事务的隔离级别。
  • Durability(持久性):事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。

二、事务隔离级别和MVCC机制介绍

2.1事务隔离级别
脏读 不可重复读 幻读
Read uncommitted
Read committed ×
Repeatable Read × ×
Serializable × × ×

隔离级别从上往下不断提升,但是并发行也在不断下降

  • Read uncommitted 这种隔离级别最低,这种级别一般是在理论上存在,数据库隔离级别一般都高于该级别
  • Read committed Oracle的默认隔离级别
  • Repeatable Read Mysql的默认隔离级别
  • Serializable 这种隔离级别很少使用,因为每次事务操作都会锁住整个表,不同事务之间都是串行的,不是并发,吞吐量太低。
2.2 不可重复读和幻读的区别

脏读比较好理解,对于不可重复读和幻读,可能会难以区分

Read committed

时间顺序 session A session B
1 Begin;
2 select name from people where id=1; (结果为“JAMES”)
3 update people set name=“Jorden” where id=1;(隐式提交)
4 select name from people where id=1; (结果为"Jorden")
5 update people set name=“Kobe” where id=1;(隐式提交)
6 select name from people where id=1; (结果为"Kobe")

在Session B中提交了几个隐式事务(就是Mysql的默认模式,自动commit),这些事务都修改了id为1的记录的列c的值,每次事务提交之后,Session A中的事务都可以查看到最新的值。这种现象也被称之为不可重复读。

Repeatable Read

时间顺序 session A session B
1 Begin;
2 select name from people where id=1; (结果为“JAMES”)
3 update people set name=“Jorden” where id=1;(隐式提交)
4 select name from people where id=1; (结果为"JAMES")
5 update people set name=“Kobe” where id=1;(隐式提交)
6 select name from people where id=1; (结果为"JAMES")

可以发现解决了不可重复读,但是如果执行下面的例子

时间顺序 session A session B
1 Begin;
2 select name from people; (结果为“JAMES”)
3 insert into people(id,name) values(2,“OVEN”);
4 select name from people ; (结果为"JAMES" 查询不到"OVEN")
5 insert into people(id,name) values(2,“LOVE”); (会提示主键冲突,这就是幻读,明明没有这个数据,却实际存在了)

两者的区别:

  • Read committed 主要是读取了其他事务更新(update)的操作,可以用设置行锁来解决不可重复读问题
  • Repeatable Read 主要是读取了其他事务插入和删除(insert和delete)的操作,可以用表锁来解决幻读问题。

三、事务实现的原理

  • 事务的原子性是由undo log实现的
  • 事务的持久性性是通过 redo log 来实现的
  • 事务的隔离性是通过 (读写锁+MVCC)来实现的
  • 事务的一致性是通过原子性,持久性,隔离性来实现的
3.1 undo log

每条数据修改(insert、update或delete)操作都伴随一条undo log的生成,并且回滚日志必须先于数据持久化到磁盘上

所谓的回滚就是根据回滚日志做逆向操作,比如delete的逆向操作为insert,insert的逆向操作为delete,update的逆向为update等。

3.2 redo log

先介绍一下MySQL的数据存储机制
MySQL的基本存储结构是,Mysql的表数据是存放在磁盘上的,因此想要存取的时候都要经历磁盘IO,然而即使是使用SSD磁盘IO也是非常消耗性能的。
为此,为了提升性能InnoDB提供了缓冲池(Buffer Pool),Buffer Pool中包含了磁盘数据页的映射,可以当做缓存来使用,缓冲池是放在内存中的:
**读数据:**会首先从缓冲池中读取,如果缓冲池中没有,则从磁盘读取在放入缓冲池;
**写数据:**会首先写入缓冲池,缓冲池中的数据会定期同步到磁盘中;

那么现在又一个问题,如果修改了数据放到缓冲池以后,缓冲池的数据还没IO到磁盘,数据库关了,数据是否会丢失呢?

这时就会用到redo log,每次对记录的修改写入缓冲池的时候,也会写一份redo log,记录着哪一页修改了什么数据。redo log也是放在磁盘中的,其实也会现在内从中有buffer,先写到buffer里,在写到redo log中,
Mysql 事务基本概念及MVCC_第1张图片
可以发现写入redo log也是磁盘IO,但是他是顺序IO,比从buffer pool中将数据页随机IO到磁盘快很多。所以即使这时数据库挂了,根据redo log中的数据也能进行恢复。

3.3 MVCC机制的介绍

建议阅读文章
《MySQL事务隔离级别和MVCC》
本文非常详细的介绍了MVCC中版本链和ReadView的操作过程。语言通俗易懂,还有很好的配图,绝对是新手小白的最佳读物!!!!

版本链: 对于InnoDB引擎,聚簇索引的叶子节点还包含了两个隐藏列

  • trx_id:每次对记录进行修改(包括insert/update/delete)时,会把当前事务的id传进去
  • roll_pointer:每次对记录进行修改时,会把旧的记录放到undo日志中然后用这个roll_pointer指针指向之前这个undo日志的记录。

Mysql 事务基本概念及MVCC_第2张图片
ReadView: 当前活跃的事务id的集合。因为READ COMMITTEDREPEATABLE READ两种事务隔离级别下,一个事务查询记录时需要判断一下该记录的版本链中的哪个版本是当前事务可见的。

注意:
delete: 我个人不确定是如何修改,如果仅仅修改trx_id,然后将这个记录放到undo日志的话。
举个例子:
id=1的记录有更早的事务插入得到的

  • 1.先开启一个事务id=200(可以通过更新除id为1以外的所有数据,或者插入新的数据来获取这个id)。
  • 2.然后开启一个事务id=201,删除id=1的记录,并且提交。按照先前的推断,这个记录的trx_id会被更新为201,并且放到undo 日志中。
  • 3.在id=200的事务中查询id=1的记录。ReadView为[200],当前记录的版本为201,所以按照《MySQL事务隔离级别和MVCC》中的介绍,是不会显示这项记录的,但是实际上仍然会显示id=1的记录。

所以我觉得trx_id只能用来存放产生记录的事务id(后面用产生id来代替),还需要有一个隐藏列来存放删除记录的事务id(后面用产删除id来代替)。

InnoDB会根据以下两个条件检查每行记录:
a.InnoDB只会查找版本早于当前事务版本的数据行(也就是,记录的产生id小于或等于事务的id),这样可以确保事务读取的行,要么是在事务开始前已经存在的,要么是事务自身插入或者修改过的.
b.记录的删除id本要么未定义,要么大于当前事务id,这可以确保事务读取到的行,在事务开始之前未被删除.
只有a,b同时满足的记录,才能返回作为查询结果.

参考:
Mysql事务
没想到MySQL还会问这些…
MySQL事务隔离级别和MVCC
Mysql 四种事务隔离级别
Mysql MVCC实现机制

你可能感兴趣的:(MySQL)