多版本并发控制 MVCC 实现可重复读

多版本并发控制 MVCC 实现可重复读

参考

高性能 MySQL 第3版 1.4 多版本并发控制

MVCC 是通过保存数据在某个时间点的快照实现的。不同存储引擎的 MVCC 的实现不同,典型的有乐观并发控制和悲观并发控制。

InnoDB 的 MVCC 实现

名词

系统版本号 事务版本号 记录创建时间 记录删除时间

InnoDB 的 MVCC 是通过在每行记录后面保存 2 个隐藏列实现的。一个保存行的创建时间,一个保存行的删除时间。当然存储的不是时间值,而是系统版本号。每开始一个新的事务,系统版本号都会自动递增。事务版本号是事务开始时刻的系统版本号。

可重复读级别下,MVCC 的执行过程

事务版本号是事务开始时刻的系统版本号 每开始一个新的事务,系统版本号都会自动递增

select

返回满足下面条件的记录

创建时间 <= 当前事务版本号 && ( 删除时间 == null || 删除时间 > 当前事务版本号 )

insert

插入记录的创建时间 = 当前系统版本号

新插入的记录的创建时间设置为当前系统版本号,删除时间为 null 。

delete

被删除记录的删除时间 = 当前系统版本号

被删除记录的删除时间被设置为当前系统版本号,逻辑删除,并不会真的删除数据。

update

被更新记录的原记录的删除时间 = 当前系统版本号 新插入记录的创建时间 = 当前系统版本号

将被更新的原记录的删除时间设置为当前系统版本号,即逻辑删除原记录。插入一条新记录,其创建时间为当前系统版本号。

MVCC 只在 read committed 和 repeatable read 2 个隔离级别工作,其他隔离级别并没有使用这种机制。read uncommitted 总是读取最新的数据行,而不是符合当前事务版本的数据行,seriablizable 会对所有读取的行都加锁。

MVCC 源码解析

http://www.ningoo.net/html/tag/mysql

http://blog.sina.com.cn/s/blog_4673e603010111ty.html

https://blog.csdn.net/joy0921/article/details/80128857

https://blog.csdn.net/u012919352/article/details/87984786

https://mp.weixin.qq.com/s?__biz=MzIxNTQ3NDMzMw==&mid=2247483670&idx=1&sn=751d84d0ce50d64934d636014abe2023&chksm=979688e4a0e101f2a51d1f06ec75e25c56f8936321ae43badc2fe9fc1257b4dc1c24223699de&scene=21#wechat_redirect

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

https://blog.csdn.net/joy0921/article/details/80128857

记录隐藏的3个字段

row_id db_trx_id db_roll_pt 记录 ID 事务 ID 回滚指针

  • 首先InnoDB每一行数据还有一个DB_ROLL_PT的回滚指针,用于指向该行修改前的上一个历史版本

  • 当插入的是一条新数据时,记录上对应的回滚段指针为NULL。

  • 如果当前记录有主键,则不会产生 row_id 记录 ID

  • read view undo log

通过read view判断行记录是否可见

具体的判断流程如下:

RR隔离级别下,在每个事务开始的时候,会将当前系统中的所有的活跃事务拷贝到一个列表中(read view)
RC隔离级别下,在每个语句开始的时候,会将当前系统中的所有的活跃事务拷贝到一个列表中(read view)

  • 《唐成-2016PG大会-数据库多版本实现内幕.pdf》

https://myslide.cn/slides/3542

MySQL · 源码分析 · InnoDB的read view,回滚段和purge过程简介 https://yq.aliyun.com/articles/560506

mysql> show engine innodb statusG;
------------
TRANSACTIONS
------------
Trx id counter AC16
Purge done for trx's n:o < AC14 undo n:o < 0
History list length 1079
LIST OF TRANSACTIONS FOR EACH SESSION:
---TRANSACTION AC15, not started
MySQL thread id 4, OS thread handle 0x43c, query id 133 localhost 127.0.0.1 root
show engine innodb status
mysql> SELECT * FROM INFORMATION_SCHEMA.INNODB_TRXG;

https://www.cnblogs.com/itcomputer/articles/5084611.html

mysql> SELECT * FROM INFORMATION_SCHEMA.INNODB_TRXG;
*************************** 1. row ***************************
                    trx_id: AC16
                 trx_state: RUNNING
               trx_started: 2019-08-03 11:50:27
     trx_requested_lock_id: NULL
          trx_wait_started: NULL
                trx_weight: 0
       trx_mysql_thread_id: 10
                 trx_query: NULL
       trx_operation_state: NULL
         trx_tables_in_use: 0
         trx_tables_locked: 0
          trx_lock_structs: 0
     trx_lock_memory_bytes: 376
           trx_rows_locked: 0
         trx_rows_modified: 0
   trx_concurrency_tickets: 0
       trx_isolation_level: READ COMMITTED
         trx_unique_checks: 1
    trx_foreign_key_checks: 1
trx_last_foreign_key_error: NULL
 trx_adaptive_hash_latched: 0
 trx_adaptive_hash_timeout: 10000
1 row in set (0.00 sec)

我开启了2个事务 AC18 AC17,事务计数器 = AC19

mysql> show engine innodb statusG;
------------
TRANSACTIONS
------------
Trx id counter AC19
mysql> SELECT * FROM INFORMATION_SCHEMA.INNODB_TRXG;
*************************** 1. row ***************************
                    trx_id: AC18
                 trx_state: RUNNING
               trx_started: 2019-08-03 11:56:40
*************************** 2. row ***************************
                    trx_id: AC17
                 trx_state: RUNNING
               trx_started: 2019-08-03 11:55:08
            
mysql> SELECT tx.trx_id
    -> FROM information_schema.innodb_trx tx
    -> WHERE tx.trx_mysql_thread_id = connection_id();
+--------+
| trx_id |
+--------+
| AC17   |
+--------+
1 row in set (0.07 sec)
mysql> SELECT tx.trx_id
    -> FROM information_schema.innodb_trx tx
    -> WHERE tx.trx_mysql_thread_id = connection_id();
+--------+
| trx_id |
+--------+
| AC18   |
+--------+
1 row in set (0.00 sec)
mysql> SELECT tx.trx_id
    -> FROM information_schema.innodb_trx tx
    -> WHERE tx.trx_mysql_thread_id = connection_id();
Empty set (0.00 sec)

https://www.jdon.com/51517

RR隔离级别(除了Gap锁之外)和RC隔离级别的差别是创建snapshot时机不同。 RR隔离级别是在事务开始时刻,确切地说是第一个读操作创建read view的;RC隔离级别是在语句开始时刻创建read view的。

创建/关闭 read view 需要持有 trx_sys->mutex ,会降低系统性能,5.7版本对此进行优化,在事务提交时 session 会 cache 只读事务的 read view。

Read view中保存的trx_sys状态主要包括

low_limit_id:high water mark,大于等于view->low_limit_id的事务对于view都是不可见的
up_limit_id:low water mark,小于view->up_limit_id的事务对于view一定是可见的
low_limit_no:trx_no小于view->low_limit_no的undo log对于view是可以purge的
rw_trx_ids:读写事务数组

Read view创建之后,读数据时比较记录最后更新的trx_id和view的 high/low water mark和读写事务数组即可判断可见性。
如前所述,如果记录最新数据是当前事务trx的更新结果,对应当前read view一定是可见的。

除此之外可以通过high/low water mark快速判断:

trx_id < view->up_limit_id的记录对于当前read view是一定可见的;
trx_id >= view->low_limit_id的记录对于当前read view是一定不可见的;
如果trx_id落在[up_limit_id, low_limit_id),需要在活跃读写事务数组查找trx_id是否存在,如果存在,记录对于当前read view是不可见的。
storage/innobase/read/read0read.c
http://blog.sina.com.cn/s/blog_4673e603010111ty.html

storage/innobase/include/read0read.ic

view->n_trx_ids 数量
view->up_limit_id
view->low_limit_id
/*********************************************************************//**
Checks if a read view sees the specified transaction.
@return	TRUE if sees */
UNIV_INLINE
ibool	
read_view_sees_trx_id(
/*==================*/
	const read_view_t*	view,	/*!< in: read view */
	trx_id_t		trx_id)	/*!< in: trx id */
{
	ulint	n_ids;
	ulint	i;

	if (trx_id < view->up_limit_id) {

		return(TRUE);
	}

	if (trx_id >= view->low_limit_id) {

		return(FALSE);
	}

	/* We go through the trx ids in the array smallest first: this order
	may save CPU time, because if there was a very long running
	transaction in the trx id array, its trx id is looked at first, and
	the first two comparisons may well decide the visibility of trx_id. */

	n_ids = view->n_trx_ids;

	for (i = 0; i < n_ids; i++) {
		trx_id_t	view_trx_id
			= read_view_get_nth_trx_id(view, n_ids - i - 1);

		if (trx_id <= view_trx_id) {
			return(trx_id != view_trx_id);
		}
	}

	return(TRUE);
}

storage/innobase/include/read0read.h

struct read_view_struct{
	ulint		type;	/*!< VIEW_NORMAL, VIEW_HIGH_GRANULARITY */
	undo_no_t	undo_no;/*!< 0 or if type is
				VIEW_HIGH_GRANULARITY
				transaction undo_no when this high-granularity
				consistent read view was created */
	trx_id_t	low_limit_no;
				/*!< The view does not need to see the undo
				logs for transactions whose transaction number
				is strictly smaller (<) than this value: they
				can be removed in purge if not needed by other
				views */
	trx_id_t	low_limit_id;
				/*!< The read should not see any transaction
				with trx id >= this value. In other words,
				this is the "high water mark". */
	trx_id_t	up_limit_id;
				/*!< The read should see all trx ids which
				are strictly smaller (<) than this value.
				In other words,
				this is the "low water mark". */
	ulint		n_trx_ids;
				/*!< Number of cells in the trx_ids array */
	trx_id_t*	trx_ids;/*!< Additional trx ids which the read should
				not see: typically, these are the active
				transactions at the time when the read is
				serialized, except the reading transaction
				itself; the trx ids in this array are in a
				descending order. These trx_ids should be
				between the "low" and "high" water marks,
				that is, up_limit_id and low_limit_id. */
	trx_id_t	creator_trx_id;
				/*!< trx id of creating transaction, or
				0 used in purge */
	UT_LIST_NODE_T(read_view_t) view_list;
				/*!< List of read views in trx_sys */
};
trx_id_t    trx_id = row_get_rec_trx_id(rec, index, offsets); //获取记录上的TRX_ID这里需要解释下,我们一个查询可能满足的记录数有多个。那我们每读取一条记录的时候就要根据这条记录上的TRX_ID判断这条记录是否可见
return(view->changes_visible(trx_id, index->table->name)); //判断记录可见性
--------------------- 
作者:仲培艺 
来源:CSDN 
原文:https://blog.csdn.net/joy0921/article/details/80128857 
版权声明:本文为博主原创文章,转载请附上博文链接!
对于不可见的记录都是通过row_vers_build_for_consistent_read函数查询UNDO构建老版本记录,直到记录可见。

这里需要说明一点 不同的事务隔离级别,可见性的实现也不一样:

READ-COMMITTED 
事务内的每个查询语句都会重新创建Read View,这样就会产生不可重复读现象发生

REPEATABLE-READ 
事务内开始时创建Read View , 在事务结束这段时间内 每一次查询都不会重新重建Read View , 从而实现了可重复读。

trx_sys->trx_list

https://blog.csdn.net/longxibendi/article/details/42012629

Read view创建之后,读数据时比较记录最后更新的trx_id和view的high/low water mark和读写事务数组即可判断可见性。

https://cloud.tencent.com/developer/ask/210171

https://blog.jcole.us/innodb/

https://github.com/jeremycole/innodb_ruby/wiki

[root@instance-fjii60o3 ~]# gem install innodb_ruby
-bash: gem: command not found

https://rubygems.org/

[root@instance-fjii60o3 develop]# wget https://rubygems.org/rubygems/rubygems-3.0.4.tgz
-rw-r--r-- 1 root root    883664 Jun 14 11:35 rubygems-3.0.4.tgz

Linux 安装 gem https://www.csdn.net/gather_2b/MtjaUgysNDYzNS1ibG9n.html

[root@instance-fjii60o3 rubygems-3.0.4]# bin/gem
/usr/bin/env: ruby: No such file or directory

Linux 安装 ruby

https://www.cnblogs.com/xuliangxing/p/7132656.html?utm_source=itdadao&utm_medium=referral

[root@instance-fjii60o3 rubygems-3.0.4]# yum list install | grep ruby
Error: No matching Packages to list
[root@instance-fjii60o3 rubygems-3.0.4]# yum list installed | grep ruby
ruby.x86_64                            2.0.0.648-35.el7_6             @updates  
ruby-irb.noarch                        2.0.0.648-35.el7_6             @updates  
ruby-libs.x86_64                       2.0.0.648-35.el7_6             @updates  
rubygem-bigdecimal.x86_64              1.2.0-35.el7_6                 @updates  
rubygem-io-console.x86_64              0.4.2-35.el7_6                 @updates  

Linux 安装 git

[root@instance-fjii60o3 ~]# yum install git
[root@instance-fjii60o3 ~]# git clone https://github.com/jeremycole/innodb_ruby.git
[root@instance-fjii60o3 rubygems-3.0.4]# gem install --user-install innodb_ruby

WARNING:  You don't have /root/.gem/ruby/bin in your PATH,
	  gem executables will not run.

Ruby学习之RubyGems(gem)包管理器的安装和使用

https://blog.csdn.net/luyaran/article/details/85698936

[root@instance-fjii60o3 rubygems-3.0.4]# yum list installed | grep ruby
Existing lock /var/run/yum.pid: another copy is running as pid 11250.
Another app is currently holding the yum lock; waiting for it to exit...
  The other application is: yum
    Memory :  90 M RSS (408 MB VSZ)
    Started: Sat Aug  3 14:39:05 2019 - 16:44 ago
    State  : Sleeping, pid: 11250

系统中活跃事务 ID

MVCC的可见性 https://blog.csdn.net/taozhi20084525/article/details/19501075

[root@instance-fjii60o3 develop]# yum localinstall mysql-community-server-8.0.13-1.el7.x86_64.rpm 
Loaded plugins: langpacks, versionlock
Existing lock /var/run/yum.pid: another copy is running as pid 11250.
Another app is currently holding the yum lock; waiting for it to exit...
  The other application is: yum
    Memory :  89 M RSS (408 MB VSZ)
    Started: Sat Aug  3 14:39:05 2019 - 1:15:13 ago
    State  : Traced/Stopped, pid: 11250
[root@instance-fjii60o3 develop]# tar -xvf mysql-8.0.13-1.el7.x86_64.rpm-bundle.tar -C mysql8.0
mysql-community-client-8.0.13-1.el7.x86_64.rpm
mysql-community-embedded-compat-8.0.13-1.el7.x86_64.rpm
mysql-community-libs-8.0.13-1.el7.x86_64.rpm
mysql-community-server-8.0.13-1.el7.x86_64.rpm
tar: Unexpected EOF in archive
tar: rmtlseek not stopped at a record boundary
tar: Error is not recoverable: exiting now

Ruby 工具分析 innodb 记录结构

https://cloud.tencent.com/developer/ask/210171

https://github.com/jeremycole/innodb_ruby/wiki

  1. 安装 ruby

  2. gem install innodb_ruby
    

    innodb 格式解析工具

https://rubygems.org/gems/innodb_ruby/versions

https://www.cnblogs.com/zengkefu/p/5678356.html

Linux 查看文件大小

[root@instance-fjii60o3 ~]# du -sh MySQL-5.5.62-1.el7.x86_64.rpm-bundle.tar 
2.1M	MySQL-5.5.62-1.el7.x86_64.rpm-bundle.tar
[root@instance-fjii60o3 ~]# du -sh develop/
1.1G	develop/

五分钟了解Mysql的行级锁——《深究Mysql锁》 https://blog.csdn.net/zcl_love_wx/article/details/81983267

Mysql有三种级别的锁定:表级锁定、页级锁定、行级锁定

  • 每次锁定的是一行数据的锁机制就是行级别锁定(row-level)。行级锁定不是MySQL自己实现的锁定方式,而是由其他存储引擎自己所实现的

  • 使用行级锁定的主要有InnoDB存储引擎,以及MySQL的分布式存储引擎NDBCluster

InnoDB的行级锁定同样分为两种类型:共享锁排他锁,而在锁定机制的实现过程中为了让行级锁定和表级锁定共存,InnoDB也同样使用了意向锁(表级锁定)的概念,也就有了意向共享锁和意向排他锁这两种。

原文地址:https://www.cnblogs.com/mozq/p/12093752.html