Mysq是如何实现可重复读的呢?

为了弄明白整个问题,首先要清楚事务到底是什么?

事务是为了保证一组数据库的操作,要么全部成功,要么全部失败。事务有四大特性:(ACID)原子性,一致性,隔离性,持久性,这里有说一下隔离性的问题。

隔离性是为了解决操作数据的时候可能出现的脏读,不可重复读以及虚读的问题。

1.脏读,读到其他事务还未提交的数据;
2.不可重复读是指一个事务内,前后读取到的数据内容不一样,比如city字段,第一次读到的是郑州,第二次读到的是武汉,前后出现了不一致的情况,主要对应update
3.幻读,一个事务内前后读取的数据个数不一致,主要对应的insert,delete

SQL的规范总共有四种隔离级别,读未提交(Read- Uncommited),读已提交(Read-Commited),可重复读(Repeatable-Read),串行化(serializable),级别越高,效率越低。
读未提交,自己的事务可以读到其他事务未提交的数据,
读已提交,自己的事务能读到其他事务提交的数据,解决脏读
可重复读,自己的事务执行时,其他事务(自己事务开启之前未提交或之后创建的)不可见,解决脏读和不可重复读
串行化,自己的事务执行时,其他事务需要挂起等待(lock wait),解决脏读和不可重复读以及幻读。

可重复读是如何实现的呢?
分析之前,需要了解一个概念:
MVCC(Multi-Version-Concurrency-Controller)多版本控制
每个引擎都有自己的实现方式,InnoDB的实现方式是这样的,每条数据会有多个版本,每一次事务执行完成后,都会生成一个版本,这些版本不是物理存储的,通过row trx_id配合undo log(回滚日志),最新的数据可以通过逻辑处理得到上一版本的数据,依次类推,多个版本的数据都可以获取到,这就是多版本控制系统,帮助数据库实现‘秒建快照’的能力。

当前读 current read
查询当前最新的数据,如果操作该数据的事务未提交,等待commit后再获取

一致性读视图 consistent read view
sql执行时,数据库内进行的一些操作。每次开启一个事务,事务系统会分配一个transaction_id给这个事务,这个id是严格按照顺序递增的。一个行数据可能会有多个事务对它进行操作,这个行数据就会有多个row_trx_id(对应transaction_id)。

通过创建一致性视图来处理这么多的事务,视图数组内记录了“活跃”的事务,‘活跃’指开启后还未提交的事务。

数组里面事务 ID 的最小值记为低水位,当前系统里面已经创建过的事务 ID 的最大值加 1
记为高水位。
这个视图数组和高水位,就组成了当前事务的一致性视图(read-view)。

而数据版本的可见性规则,就是基于数据的 row trx_id 和这个一致性视图的对比结果得到
的。

1.未提交的事务,自己的事务不可见(在低水位以下的事务)
2.视图创建之前的事务,提交的事务都可见
3.视图创建之后的事务,事务都不可见(在高水位以上的事务)
4.自己事务内更新的数据都可见

隔离级别是可重复读时,是在开始事务的时候创建一致性读视图,再此之前提交的事务,对于该事务内的操作都是可见的,之后再提交的事务是不可见的。保证了事务内的数据读到的数据的一致性问题,其核心也是一致性读(consistent read)如果是更新数据,都是先读后写的,而这个读,只能读当前的值,称为“当前读”,要读取最新的数据,如果该行数据有其他事务T在执行更新操作,需要等待这个事务T提交后,再继续操作

既然保证了数据一致性,为什么没解决幻读的情况呢,insert或delete时,只保证了该行数据的可重复读的问题,对于符合搜索条件的新插入或删除的数据,是没有事务控制的。

隔离级别是读已提交时,是在每一次执行语句的时候创建一致性视图,保证每次查询的是最新的数据。不同的隔离级别,创建一致性事务的时机是不一样的,这样就保证了不同隔离级别,能看到不同的数据,也就是看到不同版本了的行数据。

扩展
在保存行数据的一个版本的时候,存储的并不是时间,而是事务的版本号,每开启一个新事务,事务的版本号就会递增。新的版本会记录上一个版本的信息, 在可重读Repeatable reads事务隔离级别下:

SELECT时,读取创建版本号<=当前事务版本号,删除版本号为空或>当前事务版本号。
INSERT时,保存当前事务版本号为行的创建版本号
DELETE时,保存当前事务版本号为行的删除版本号
UPDATE时,插入一条新纪录,保存当前事务版本号为行创建版本号,同时保存当前事务版本号到原来删除的行

这样的处理也是提高了数据库的查询效率,是乐观锁的一种体现,保证了数据在逻辑上的可回溯可追查,通过较为复杂的逻辑代替重复的进行磁盘IO。
新的版本记录了旧版本的信息,版本里有两个值,一个记录这行数据何时被创建,另外一个记录这行数据何时过期(或者被删除),这些版本的更新的操作数据存在于redo log日志中,会按照记录操作的时间顺序回写到磁盘上,保证数据的准确性。

事务如何启动

1. begin或sart transaction 配套commit,rollback
2.set autocommit=0, 自动开启事务,需要显示用commit提交事务,可能导致长事务,建议set autocommit=1,采用显示的方式开启事务

注意:事务不是在begin或start transaction时开启,而是在第一次执行语句是才正式开启。如果你想要马上启动一个事务,可以使用 start transaction with consistent snapshot 这个命令。