我们来考虑一个问题,如果我们要备份数据,我们一定要保证备份出的数据的可用性,如果备份后的数据在恢复以后,无法正常使用,那么备份出的数据也就失去了备份的意义,数据可用的前提就是数据的正确性、完整性、一致性。正确性和完整性比较容易理解,一致性只通过字面理解不太容易,我们来举个小例子理解一下。

不过在举例之前,我们还是先来看一下”一致性”的一些专业解释,虽然这些专业解释不容易理解,但是没有关系,我们先大概了解一下,在看完后面通俗的示例后你自然会理解。

数据的一致性:通常指关联数据之间的逻辑关系是否正确和完整。

数据库的一致性:是指数据库从一个一致性状态变到另一个一致性状态。

好吧,专业的术语说的很模糊,我们来通俗的、不严谨的举个例子,可能有不当之处,但是方便理解。

数据的一致性

先说说什么是数据的一致性,举例如下。

假设,今天下午3点的时候,小朱的”某宝”账户中有200元钱,小朱想要在网上买一辆自行车,这辆自行车售价198,然后小朱把这辆自行车加入到了购物车中,但是并没有付款,那么某宝认为,小朱在3点的时候,是有能力购买这辆自行车的,于是,某宝就记录了一下,小朱在3点的时候有能力购买这辆自行车(这里只是假设),假设某宝3点开始备份数据库,那么,理论上来说,某宝备份出的数据应该是3点那一刻所有数据的状态,某宝把这次备份命名为”某宝在3点时的数据备份”,但是某宝的用户辣么多,数据量辣么大,备份总是需要时间的,假设小朱是某宝的第88888888888个用户,从某宝备份数据开始,到备份结束,需要2个小时,而备份到小朱的账户信息时需要一个半小时,换句话说,某宝从3点开始备份,备份完成,需要到5点,可能会在4点半备份到小朱的数据,那么如果,在3点之后到4点半之前这段时间内,小朱从”某宝”账户中转出了100元,还在账户中剩余了100元,那么,在4点半的时候,某宝备份到的小朱的账户数据将是”小朱的账户中有100元”,可是我们不要忘了,某宝是从3点开始备份的,某宝会认为这次备份中的数据都是3点时的数据,那么,如果出现上述的情况,这次备份的数据就变成了”3点时,小朱的账户中有100元”,这与实际情况不符 啊,这与实际情况”不一致”啊,3点那一刻,小朱的账户中明明有”200”元,可是备份中却显示”3点那一刻,小朱的账户中有100元”,如果在3点时,小朱的账户只有100元,是没有能力购买那辆价值198元的自行车的,也就是说,在3点那一刻,所有的”交易”逻辑都不成立了,这就是数据的不一致,在某一时间点,关联数据之间的逻辑关系不正确,或者不完整,这就是数据不一致,那么我们遇到过这种情况吗?貌似没有,某宝很可靠,某宝中的数据都是一致的,即使在3点开始备份,备份需要两个小时,备份期间即使各个用户的账户信息发生变化,某宝还是能够记录下备份开始时那一刻时,所有信息的样子,而且关联数据之间的逻辑关系都是正常的。这就是所谓的”数据的一致性”。那么,怎么样才能让数据”一致”呢?我们一会儿再聊。

这时,我们再来回顾一下数据的一致性的概念:

数据的一致性通常指关联数据之间的逻辑关系是否正确和完整。

现在,你明白上述”专业术语”的意思了吗?

mysql/mariadb知识点总结(27):一致性读,快照读

可能还是不理解,但是起码比刚才好多了吧。

数据库的一致性

你在网上搜索”什么是数据库的一致性”,往往会看到如下通俗的举例。

银行的数据库中有2个用户,A用户和B用户,上午10点时,A用户的账户中有5万元,B用户的账户中有10万元,目前,数据是处在一个一致状态的,因为,A用户发现自己的钱没少没多,B用户发现自己的钱没少没多,一切都很正常,上午11点时,B用户要向A用户转账,B用户要向A用户转账2万元,那么,转账完成后,B用户的账户中有8万元,A用户的账户中有7万元,此刻,又处于一个一致状态,虽然B账户的钱少了2万,A账户多了2万,但是这是转账后的结果,从逻辑上讲,也是对的,上午12点,A用户准备退出此银行,想把钱转向其他银行,于是,A用户把自己账户中的7万元取走了,此时,银行只剩下了以个B用户,所有钱都是B用户的钱,一共8万,此时,银行的数据库又处于一个一致性状态。这就是数据库的一致性。

这时,我们再来回顾一下数据库的一致性的概念:

数据库的一致性:是指数据库从一个一致性状态变到另一个一致性状态。

其实我感觉,数据库的一致性想要表达的意思就是,数据库中的数据保持在一致状态,虽然数据有可能会变化,但是状态一直是一致的。

怎样保持数据一致性

还是以备份时的场景为例,如果我们3点开始备份,则必须保证备份中的所有数据都是3点那一刻的样子,即使备份需要持续一段时间,我们仍然要确保备份完成后,数据就是3点那一刻的样子,即保证备份对于备份开始那一刻来说,数据是一致的,那么我们怎么做到呢?

一般我们有两种做法

第一种方法:备份开始时对所有表加锁,备份结束之前不能修改数据库,这样,不管备份持续多长时间,都能保证对于备份开始那一刻来说,数据库中的数据是一致的,因为数据压根没变,它一直处于同一个一致状态中。但是这样做的缺点就是,一旦数据库开始备份,则无法对数据库进行写操作,最多只能进行读操作。

第二种方法:在备份开始时对所有数据进行一个”快照”,由于”快照”记录了开始备份时那一刻所有数据的样子,所以,在这个”快照”范围内读取出的所有数据,也具有一致性,在mysql中,我们可以利用事务实现类似”快照”的功能,如果表使用的存储引擎为innodb,那么我们则可以利用事务的”隔离性”,保证数据的”一致性”,之前的文章已经总结了innodb的事务的”隔离级别”(如果你不知道我在说什么,请参考之前对事务的总结),不同的隔离级别下,事务之间拥有不同的隔离性,所以,并不是所有的隔离级别都能在备份的场景中保证数据的一致性。如果你读过之前的文章,那你一定想到了,通过”可重读”隔离级别,即可在备份的场景中,保证数据的一致性,这时候,有的人会说,事务在”可重读”的隔离界别下,不是会出现”幻读”的情况吗?如果我们在备份时,出现幻读,不就会备份出不一致的数据吗?但是,回顾之前的总结,我们可以发现,如果当前事务处于”可重读”隔离级别下,如果在当前事务中不进行更新操作,是不会出现幻读的情况的,而恰巧,在备份时,相当于对数据进行读操作,我们只要能够保证,将所有备份操作放在一个单独的事务中,并且将这个事务的隔离级别设置为”可重读”即可,因为这个事务中只进行备份操作,并不进行其他操作,所以,并不会出现”幻读”的情况,从而就保证了整个备份过程中,所有数据对于某个时间点来说,是一致的,从而保证了数据的”一致性读”,”一致性读”也被称为”快照读”。

可重读隔离级别下的一致性读

君子动口不动手,该出手时就出手。

我们一起来动手实践一下,即可更加明白,怎样通过”可重读”隔离级别保证完成一致性读。

首先,先来回顾一下”可重读”隔离级别下的”幻读”,我们将两个数据库会话中的事务隔离级别都设置成可重读,然后在两个会话中同时开启两个事务,如下图所示,红线以下的操作请严格按照序号的顺序进行操作。

mysql/mariadb知识点总结(27):一致性读,快照读

如上图所示,刚开始时,事务A和事务B中查询出的数据均为2条数据,即使事务B中插入了新数据,并且提交了事务B,然后在事务A中查询时,仍然只能查询出两条数据,只有在事务A中更新执行了更新操作以后,才能够在事务A中看到事务B中新增的数据,这就是”可重读”级别下的”幻读”。

注意:上例中第8步执行的update语句并没有指定任何条件,相当于更新表中的所有行的对应字段,如果你指定了条件,并且没有更新到>注意:上例中第8步执行的update语句并没有指定任何条件,相当于更新表中的所有行的对应字段,如果你指定了条件,并且没有更新到”隐藏”的行,那么可能无法看到幻读现象内容

但是,在备份数据时,所有备份操作都是”读操作”,并不包含更新操作,所以,如果我们把所有备份操作都放在一个单独的”事务”中,并且这个事务的隔离级别为”可重读”时,是不会出现”幻读”的问题的,就实现了针对某一时间点内所有数据的一致性读,保证了读取数据时,数据的一致性。

但是,真的如我们想象的这样简单吗?我们来继续做实验。

假设,我们使用下图中会话A中的事务进行数据备份,会话A中首先要开启一个事务A,而且这个事务的隔离级别为”可重读”,我们假设此事务中的所有读操作都是数据备份的操作,如下图所示

mysql/mariadb知识点总结(27):一致性读,快照读

如果说,上图中的第1步操作表示备份开始,备份的时间为下午3点,备份完成需要两个小时,那么,我们必须保证,只要这个事务一开始,在这个事务中读到的所有数据,都是下午3点时的样子(就好像事务开始时的那一刻的所有数据做了”快照”一样),那么按照上图所示,似乎与我们想象的一样,因为即使在备份期间,对数据库进行写操作(操作3与操作4),似乎也没有影响到数据的一致性,在事务A中无论何时读到的数据都是事务开始时的样子(下午3点时的样子),事务A之外的操作并不能影响到事务A的读取。这样真的万无一失吗?我们来看下面的例子。

下图中的会话A中的事务仍然表示我们用来备份的”事务”,这是一个独立的、隔离级别为”可重读”的事务,假设,我们下午3点开始备份,我们启动了一个”可重读”的独立事务,下午3点时,t1表中只有两条数据,虽然3点时,我们开启了事务,但是,此刻我们并没有开始备份操作(读操作),我们想要先准备一些数据以后,再开始实际的备份工作。可是,就在这个时间段内,有人对t1表进行了写操作,在这之后,我们开始进行备份操作,结果发现,备份出的数据并不是下午3点时的两条数据,而是3条数据,如下图所示。

mysql/mariadb知识点总结(27):一致性读,快照读

怎么会这样???????????

我一直认为,如果一个事务处在”可重读”级别下,只要事务开始,就代表当前事务对数据进行了”快照”,看来我错了。

换句话说就是,我一直以为,当事务处于”可重读”隔离级别时,只要start transaction这个语句一开始,就代表当前事务对这一刻的数据进行了快照,我一直都理解错了,看来,我不应该以start transaction语句开始的时间点作为”快照”建立的时间点。那么,我应该以哪个时间点作为”快照”建立的时间点呢?官方的解释如下。

https://dev.mysql.com/doc/refman/5.5/en/innodb-consistent-read.html

If the transaction isolation level is REPEATABLE READ (the default level), all consistent reads within the same transaction read the snapshot established by the first such read in that transaction. You can get a fresher snapshot for your queries by committing the current transaction and after that issuing new queries.

也就是说,当事务处于”可重读”隔离级别时,并不是事务开始时就代表快照建立,而是事务中的第一个查询语句执行时,快照点才会被建立。

这也解释了为什么上述两例中,两个事务的隔离级别都为可重读,但是第一个示例在事务开始后就实现了”一致性读”,而第二个示例在事务开始的时间点,没有实现”一致性读”的原因,因为,第一个示例中,事务开始以后,立马执行了一个select语句,而第二个示例中,事务开始以后,并没有马上执行select语句,在这段时间里,数据已经发生了改变,等到执行select语句时,快照才会建立,所以,对于执行select语句时的时间点,数据是一致的,但是对于事务开始的时间点来说,数据是不一致的。

难道,如果我们想要在事务开始时就建立快照,就必须在事务开始后立马手动执行一条select语句吗?不是的,mysql为我们提供了另一种选择,就是使用如下语句启动一个”可重读”的事务。

START TRANSACTION WITH consistent snapshot

执行上述语句等效于执行start transaction 之后,马上执行一条 select 语句(即 start transaction 语句执行的同时建立”快照”)

那么我们来实验一下,看看到底行不行,仍然使用事务A模拟备份的事务,事务A的隔离级别是”可重读”,示例如下

mysql/mariadb知识点总结(27):一致性读,快照读

从上图可以看出,使用start transaction with consistent snapshot;命令启动事务,就表示启动事务的同时就建立快照,也就是说,只要事务开始,就能保证”一致性读”。

好了,mysql中的一致性读(快照读)就总结到这里吧。

参考:http://www.zsythink.net/archives/1436

分类: web

标签:   mysql