静态企业网站源码,天津城市基础设施建设投资集团有限公司网站,.net网站 作品,广州网站建设教程一致性非锁定读(consistent nonlocking read)是指InnoDB存储引擎通过多版本控制(MVVC)读取当前数据库中行数据的方式。如果读取的行正在执行DELETE或UPDATE操作#xff0c;这时读取操作不会因此去等待行上锁的释放。相反地#xff0c;InnoDB会去读取行的一个快照。上图直观地…一致性非锁定读(consistent nonlocking read)是指InnoDB存储引擎通过多版本控制(MVVC)读取当前数据库中行数据的方式。如果读取的行正在执行DELETE或UPDATE操作这时读取操作不会因此去等待行上锁的释放。相反地InnoDB会去读取行的一个快照。上图直观地展现了InnoDB一致性非锁定读的机制。之所以称其为非锁定读是因为不需要等待行上排他锁的释放。快照数据是指该行的之前版本的数据每行记录可能有多个版本一般称这种技术为行多版本技术。由此带来的并发控制称之为多版本并发控制(Multi Version Concurrency Control, MVVC)。InnoDB是通过undo log来实现MVVC。undo log本身用来在事务中回滚数据因此快照数据本身是没有额外开销。此外读取快照数据是不需要上锁的因为没有事务需要对历史的数据进行修改操作。一致性非锁定读是InnoDB默认的读取方式即读取不会占用和等待行上的锁。但是并不是在每个事务隔离级别下都是采用此种方式。此外即使都是使用一致性非锁定读但是对于快照数据的定义也各不相同。在事务隔离级别READ COMMITTED和REPEATABLE READ下InnoDB使用一致性非锁定读。然而对于快照数据的定义却不同。在READ COMMITTED事务隔离级别下一致性非锁定读总是读取被锁定行的最新一份快照数据。而在REPEATABLE READ事务隔离级别下则读取事务开始时的行数据版本。我们下面举个例子来详细说明一下上述的情况。# session Amysql BEGIN;mysql SELECT * FROM test WHERE id 1;我们首先在会话A中显示地开启一个事务然后读取test表中的id为1的数据但是事务并没有结束。于此同时用户在开启另一个会话B这样可以模拟并发的操作然后对会话B做出如下的操作# session Bmysql BEGIN;mysql UPDATE test SET id 3 WHERE id 1;在会话B的事务中将test表中id为1的记录修改为id3但是事务同样也没有提交这样id1的行其实加了一个排他锁。由于InnoDB在READ COMMITTED和REPEATABLE READ事务隔离级别下使用一致性非锁定读这时如果会话A再次读取id为1的记录仍然能够读取到相同的数据。此时READ COMMITTED和REPEATABLE READ事务隔离级别没有任何区别。如上图所示当会话B提交事务后会话A再次运行SELECT * FROM test WHERE id 1的SQL语句时两个事务隔离级别下得到的结果就不一样了。对于READ COMMITTED的事务隔离级别它总是读取行的最新版本如果行被锁定了则读取该行版本的最新一个快照。因为会话B的事务已经提交所以在该隔离级别下上述SQL语句的结果集是空的。对于REPEATABLE READ的事务隔离级别总是读取事务开始时的行数据因此在该隔离级别下上述SQL语句仍然会获得相同的数据。MVVC我们首先来看一下wiki上对MVVC的定义Multiversion concurrency control (MCC or MVCC), is a concurrency controlmethod commonly used by database management systems to provideconcurrent access to the database and in programming languages toimplement transactional memory.由定义可知MVVC是用于数据库提供并发访问控制的并发控制技术。数据库的并发控制机制有很多最为常见的就是锁机制。锁机制一般会给竞争资源加锁阻塞读或者写操作来解决事务之间的竞争条件最终保证事务的可串行化。而MVVC则引入了另外一种并发控制它让读写操作互不阻塞每一个写操作都会创建一个新版本的数据读操作会从有限多个版本的数据中挑选一个最合适的结果直接返回由此解决了事务的竞争条件。考虑一个现实场景。管理者要查询所有用户的存款总额假设除了用户A和用户B之外其他用户的存款总额都为0A、B用户各有存款1000所以所有用户的存款总额为2000。但是在查询过程中用户A会向用户B进行转账操作。转账操作和查询总额操作的时序图如下图所示。如果没有任何的并发控制机制查询总额事务先读取了用户A的账户存款然后转账事务改变了用户A和用户B的账户存款最后查询总额事务继续读取了转账后的用户B的账号存款导致最终统计的存款总额多了100元发生错误。使用锁机制可以解决上述的问题。查询总额事务会对读取的行加锁等到操作结束后再释放所有行上的锁。因为用户A的存款被锁导致转账操作被阻塞直到查询总额事务提交并将所有锁都释放。但是这时可能会引入新的问题当转账操作是从用户B向用户A进行转账时会导致死锁。转账事务会先锁住用户B的数据等待用户A数据上的锁但是查询总额的事务却先锁住了用户A数据等待用户B的数据上的锁。使用MVVC机制也可以解决这个问题。查询总额事务先读取了用户A的账户存款然后转账事务会修改用户A和用户B账户存款查询总额事务读取用户B存款时不会读取转账事务修改后的数据而是读取本事务开始时的数据副本(在REPEATABLE READ隔离等级下)。MVCC使得数据库读不会对数据加锁普通的SELECT请求不会加锁提高了数据库的并发处理能力。借助MVCC数据库可以实现READ COMMITTEDREPEATABLE READ等隔离级别用户可以查看当前数据的前一个或者前几个历史版本保证了ACID中的I特性(隔离性)InnoDB的MVVC实现多版本并发控制仅仅是一种技术概念并没有统一的实现标准 其的核心理念就是数据快照不同的事务访问不同版本的数据快照从而实现不同的事务隔离级别。虽然字面上是说具有多个版本的数据快照但这并不意味着数据库必须拷贝数据保存多份数据文件这样会浪费大量的存储空间。InnoDB通过事务的undo日志巧妙地实现了多版本的数据快照。数据库的事务有时需要进行回滚操作这时就需要对之前的操作进行undo。因此在对数据进行修改时InnoDB会产生undo log。当事务需要进行回滚时InnoDB可以利用这些undo log将数据回滚到修改之前的样子。根据行为的不同 undo log 分为两种 insert undo log和update undo log。insert undo log 是在 insert 操作中产生的 undo log。因为 insert 操作的记录只对事务本身可见对于其它事务此记录是不可见的所以 insert undo log 可以在事务提交后直接删除而不需要进行 purge 操作。update undo log 是 update 或 delete 操作中产生的 undo log因为会对已经存在的记录产生影响为了提供 MVCC机制因此 update undo log 不能在事务提交时就进行删除而是将事务提交时放到入 history list 上等待 purge 线程进行最后的删除操作。为了保证事务并发操作时在写各自的undo log时不产生冲突InnoDB采用回滚段的方式来维护undo log的并发写入和持久化。回滚段实际上是一种 Undo 文件组织方式。InnoDB行记录有三个隐藏字段分别对应该行的rowid、事务号db_trx_id和回滚指针db_roll_ptr其中db_trx_id表示最近修改的事务的iddb_roll_ptr指向回滚段中的undo log。如下图所示。当事务2使用UPDATE语句修改该行数据时会首先使用排他锁锁定改行将该行当前的值复制到undo log中然后再真正地修改当前行的值最后填写事务ID使用回滚指针指向undo log中修改前的行。如下图所示。当事务3进行修改与事务2的处理过程类似如下图所示。REPEATABLE READ隔离级别下事务开始后使用MVVC机制进行读取时会将当时活动的事务id记录下来记录到Read View中。READ COMMITTED隔离级别下则是每次读取时都创建一个新的Read View。Read View是InnoDB中用于判断记录可见性的数据结构记录了一些用于判断可见性的属性。low_limit_id某行记录的db_trx_id 该值则该行对于当前Read View是一定可见的up_limit_id某行记录的db_trx_id 该值则该行对于当前read view是一定不可见的low_limit_no用于purge操作的判断rw_trx_ids读写事务数组Read View创建后事务再次进行读操作时比较记录的db_trx_id和Read View中的low_limit_idup_limit_id和读写事务数组来判断可见性。如果该行中的db_trx_id等于当前事务id说明是事务内部发生的更改直接返回该行数据。否则的话如果db_trx_id小于up_limit_id说明是事务开始前的修改则该记录对当前Read View是可见的直接返回该行数据。如果db_trx_id大于或者等于low_limit_id则该记录对于该Read View一定是不可见的。如果db_trx_id位于[up_limit_id, low_limit_id)范围内需要在活跃读写事务数组(rw_trx_ids)中查找db_trx_id是否存在如果存在记录对于当前Read View是不可见的。如果记录对于Read View不可见需要通过记录的DB_ROLL_PTR指针遍历undo log构造对当前Read View可见版本数据。简单来说Read View记录读开始时及其之后所有的活动事务这些事务所做的修改对于Read View是不可见的。除此之外所有其他的小于创建Read View的事务号的所有记录均可见。后记我们后续还会学习InnoDB的锁的相关的知识请大家持续关注。参考文章