zoukankan      html  css  js  c++  java
  • 多版本并发控制 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也同样使用了意向锁(表级锁定)的概念,也就有了意向共享锁和意向排他锁这两种。

  • 相关阅读:
    一文让你明白Redis持久化
    spring-data-redis 2.0 的使用
    中间自适应布局的5种解法
    php实现只需要一个QQ号就可以获得用户信息
    基于LINUX下的进程管理问题
    【初码干货】记一次分布式B站爬虫任务系统的完整设计和实施
    初码-爬虫系列-文章目录
    初码-阿里云系列-文章目录
    初码-Azure系列-存储队列的使用与一个Azure小工具(蓝天助手)
    初码-Azure系列-记一次MySQL数据库向Azure的迁移
  • 原文地址:https://www.cnblogs.com/mozq/p/12093752.html
Copyright © 2011-2022 走看看