TokuDB行锁的实现

本文发表于2015-11的Mysql内核月报 

行锁申请

与 InnoDB 类似,TokuDB 也支持行级锁用来协调多个 txn 对数据库表某一段数据的并发访问。一个表中所有已经 grant 的行锁是用一个 binary search tree 来表示的,TokuDB 的术语称它为 lock tree。lock tree 与数据库表之间是一一对应的关系。当打开 cursor 读写 TokuDB 数据库的时候,需要首先尝试申请row lock,成功后再调用 db_put/ha_tokudb::read_range_first 方法来读写数据。TokuDB 的 row lock 是用 range lock 表示的,一个 range lock 代表按key值连续的一个行锁区间。rangelock是一个同步锁,如果获取成功立即返回;若失败则会同步等待若干时间,等待超时整个操作就会失败返回。range lock的申请分为三个阶段,下面将逐个说明。

  1. 创建range lock:在TokuDB中就是创建一个 lock_request 对象 创建的过程很简单,主要是初始化,创建成功后调用 lock_request 的 start 函数来申请锁;
  2. 申请range lock:大部分事情都在这个阶段完成的 申请锁的时候需要指定五元组(lt,txn,type,left_key,right_key),分别表示数据库表对应的lock tree,txn结构,锁类型(read/write),键值区间(left_key, right_key)。申请的时候会根据锁 类型来调用 locktree 的 acquire_write/read_lock方法来获取锁。这里有个 tricky 的地方:在 locktree 实现中隐式地将 read lock 升级成 write lock。 在获取锁的函数里面,首先调用了 check_current_lock_constraints 来进行 throttle 控制当前锁占用的内存,这块不展开讨论了。 locktree 有一个为 single txn 做的优化,当系统猜测当前是工作在 single txn 的方式下(不存在锁竞争的问题),所有的锁都会被 grant 并记录在 sto_buffer 里面。如果不是 single txn 的模式,已经 grant 的锁则保存在 concurrent_tree 里面,这个就是我们在前面提到的那个 binary search tree。Single txn 模式的判断是用启发式的方法,由两个因素控制 sto_buffer 和 concurrent_tree 的切换: 积分 score 和 sto_buffer 长度,因篇幅有限这块也留给大家分析了。要提的一点是如果正处在 single txn 模式,遇到了一个新的 txn,那么 sto_buffer 的锁会被转移到 concurrent_tree 上。 我们重点讨论是 concurrent_tree 的情况。函数 acquire_lock_consolidated 会根据五元组里面的 left_key 和 right_key 构造一个 request_range,然后用这个 range 在 concurrent_tree 上定位到与它存在 overlap 关系的最小子树,并把子树里面与 request_range 存在 overlapped 关系的那些锁保存在一个变长数组里面。然后 iterate 这个数组看看是否存在锁冲突,冲突的条件是与五元组里的 txnid 不同但锁区间是 overlapped 的。如果不存在锁冲突,就可以立即 grant 这个锁申请了。 剩下的是些维护工作,就是依次把区间重叠的已经 grant 的锁和我们正在申请的锁进行区间 merge,保证 concurrent_tree 里面的所有锁的区间都是不相交的(不overlapped的)。如果不幸,申请的锁和concurrent_tree里面的锁有冲突,那么请求操作会失败。
  3. 等待range lock:申请失败会把这个 range lock 放到 locktree 的 pending list 里等待以后重试。 锁申请失败可能是发生了死锁,还需要调用 deadlock_exists 递归构造锁等待 DAG 图判断是否真的发生了死锁。

举例说明

上面描述比较枯燥,我们结合4月份月报里的例子一起看看吧。

mysql> show create table t\G
--------------------------- 1. row ---------------------------
       Table: t
Create Table: CREATE TABLE `t` (
  `id` int(11) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=TokuDB DEFAULT CHARSET=latin1

mysql> set autocommit=off;
mysql> insert into t values (1),(10),(100);
mysql> select * from information_schema.tokudb_locks\G
--------------------------- 1. row ---------------------------
               locks_trx_id: 238
      locks_mysql_thread_id: 3
              locks_dname: ./test/t-main
             locks_key_left: 0001000000
            locks_key_right: 0001000000
        locks_table_schema: test
          locks_table_name: t
locks_table_dictionary_name: main
--------------------------- 2. row ---------------------------
               locks_trx_id: 238
      locks_mysql_thread_id: 3
             locks_dname: ./test/t-main
             locks_key_left: 000a000000
            locks_key_right: 000a000000
         locks_table_schema: test
           locks_table_name: t
locks_table_dictionary_name: main
--------------------------- 3. row ---------------------------
               locks_trx_id: 238
      locks_mysql_thread_id: 3
              locks_dname: ./test/t-main
             locks_key_left: 0064000000
            locks_key_right: 0064000000
         locks_table_schema: test
           locks_table_name: t
locks_table_dictionary_name: main

再看一个例子。id 是 primary key,c1 上有 index 。Insert 三条记录产生6个行锁,3个在primary key上,3个在c1 index上。primary key上锁的key值主要由pk值构成,非 unique 的 index 锁的 key 值主要由 index 上的 key 值和 primary key 值组成。

Create Table: CREATE TABLE `t` (
  `id` int(11) NOT NULL,
  `c1` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `c1` (`c1`)
) ENGINE=TokuDB DEFAULT CHARSET=latin1
1 row in set (0.00 sec)

mysql> alter table t2 add index (c1);
mysql> set autocommit=off;
mysql> insert into t values(1,2),(10,11),(100,101);
mysql> select * from information_schema.tokudb_locks\G
--------------------------- 1. row ---------------------------
               locks_trx_id: 451
      locks_mysql_thread_id: 1
             locks_dname: ./test/t-main
             locks_key_left: 0001000000
            locks_key_right: 0001000000
         locks_table_schema: test
           locks_table_name: t
locks_table_dictionary_name: main
--------------------------- 2. row ---------------------------
               locks_trx_id: 451
      locks_mysql_thread_id: 1
             locks_dname: ./test/t-main
             locks_key_left: 000a000000
            locks_key_right: 000a000000
         locks_table_schema: test
           locks_table_name: t
locks_table_dictionary_name: main
--------------------------- 3. row ---------------------------
               locks_trx_id: 451
      locks_mysql_thread_id: 1
             locks_dname: ./test/t-main
             locks_key_left: 0064000000
            locks_key_right: 0064000000
         locks_table_schema: test
           locks_table_name: t
locks_table_dictionary_name: main
--------------------------- 4. row ---------------------------
               locks_trx_id: 451
      locks_mysql_thread_id: 1
             locks_dname: ./test/t-key-c1
             locks_key_left: 00010200000001000000
            locks_key_right: 00010200000001000000
         locks_table_schema: test
           locks_table_name: t
locks_table_dictionary_name: key-c1
--------------------------- 5. row ---------------------------
               locks_trx_id: 451
      locks_mysql_thread_id: 1
             locks_dname: ./test/t-key-c1
             locks_key_left: 00010b0000000a000000
            locks_key_right: 00010b0000000a000000
         locks_table_schema: test
           locks_table_name: t
locks_table_dictionary_name: key-c1
--------------------------- 6. row ---------------------------
               locks_trx_id: 451
      locks_mysql_thread_id: 1
             locks_dname: ./test/t-key-c1
             locks_key_left: 00016500000064000000
            locks_key_right: 00016500000064000000
         locks_table_schema: test
           locks_table_name: t
locks_table_dictionary_name: key-c1

问题探讨

前面描述中提到 locktree 会自动升级读锁为写锁,这会不会带来性能问题呢?我看看下面的例子,假设场景是 isolation 级别是 read commit,关闭autocommit。

例1:

  • txn1: select 执行 index range scan ==> 在 read_range_first 之前会尝试获取读锁 ==> locktree 自动把读锁升级为写锁
  • txn2: select 执行 index range scan,与 txn1 相同的 index,数据有 overlapped ==> 在 read_range_first 之前会尝试获取读锁 ==> locktree 自动把读锁升级为写锁

例2:

  • txn3: select 执行 index range scan ==> 在 read_range_first 之前会尝试获取读锁 ==> locktree 自动把读锁升级为写锁
  • txn4: insert 插入的数据落在 txn3 操作的区间内 ==> 在 db_put 之前会尝试获取写锁 ==> locktree 获取写锁

这样看起来 txn1 与 txn2,txn3 与 txn4 申请的 rangelock 存在 overlapped 关系,可能引起等待。但事实上,在 read commit 的隔离级别上,txn1&txn2,tx3&txn4 是不需要等待的。

TokuDB 中读数据申请 row lock 是在 c_set_bounds 函数实现的。c_set_bounds 有个tricky的处理:对于 READ_UNCOMMITTED,READ_COMMITTED 和 REPEATABLE_READ 隔离级别下并且没有设置 DB_RMW 标志的话,读数据是不需要去拿 range lock 的。

时间: 2024-10-01 18:22:16

TokuDB行锁的实现的相关文章

MySQL · TokuDB · TokuDB 中的行锁

前言 4月份月报有篇文章<行锁(row-lock)与区间锁(range-lock)>,介绍了 TokuDB 的行锁/区间锁是如何使用的.这篇文章是其姐妹篇,介绍TokuDB行锁的实现,大家可以对照着看. 行锁申请 与 InnoDB 类似,TokuDB 也支持行级锁用来协调多个 txn 对数据库表某一段数据的并发访问.一个表中所有已经 grant 的行锁是用一个 binary search tree 来表示的,TokuDB 的术语称它为 lock tree.lock tree 与数据库表之间是一

RDS for MySQL InnoDB 行锁等待和锁等待超时的处理

RDS for MySQL InnoDB 行锁等待和锁等待超时的处理   1. InnoDB 引擎表行锁等待和等待超时发生的场景 2.InnoDB 引擎行锁等待情况的处理 2.1 InnoDB 行锁等待超时参数 innodb_lock_wait_timeout 2.2 大量行锁等待和行锁等待超时的处理 1. InnoDB 引擎表行锁等待和等待超时发生的场景 当一个 RDS for MySQL 连接会话等待另外一个会话持有的互斥行锁时,会发生 InnoDB 引擎表行锁等待情况. 通常情况下,持有该

《HBase权威指南》一3.4 行锁

3.4 行锁 像put().delete().checkAndPut()这样的修改操作是独立执行的,这意味着在一个串行方式的执行中,对于每一行必须保证行级别的操作是原子性的.region服务器提供了一个行锁(row lock)的特性,这个特性保证了只有一个客户端能获取一行数据相应的锁,同时对该行进行修改.在实践中,大部分客户端应用程序都没有提供显式的锁,而是使用这个机制来保障每个操作的独立性. 文字用户应该尽可能地避免使用行锁.就像在RDBMS中,两个客户端很可能在拥有对方要请求的锁时,又同时请

mysql-MYSQL INNODB表锁和行锁的问题

问题描述 MYSQL INNODB表锁和行锁的问题 Id是主键.以下语句分别是行锁还是表锁? 第一句:update Table set X=1 where Id IN (1,2,4,7); 第二句:update Table set X=1 where Id Between 1 AND 10; 第三句:update Table set X=1 where Id>=1 AND Id<100; 解决方案 这些都是行锁,只有lock table语句innodb才会申请表锁

c#+oracle数据库行锁写法问题

问题描述 c#+oracle数据库行锁写法问题 请教各位高手: 我在c#+oracle,里面,想如此操作.当修改某一数据的前,先执行select行锁定,待修改完毕后再解锁.请问在不用存储过程的情况下,程序该如何写呢? 解决方案 最简单的是使用事务.http://www.cnblogs.com/yanghucheng/archive/2013/01/25/2876492.htmlhttp://happypigs.iteye.com/blog/1576282 解决方案二: 谢谢,能举个实际例子吗 解

SQL Server2000使用行锁解决并发库存为负超卖问题

假设库存表结构及数据如下:  代码如下 复制代码 create table Stock (     Id int identity(1,1) primary key,     Name nvarchar(50),     Quantity int ) --insert insert into Stock select 'orange',10 在秒杀等高并发情况下,使用下面的存储过程会导致库存为负产生超卖问题:  代码如下 复制代码 create procedure [dbo].Sale    

PostgreSQL 使用advisory lock或skip locked消除行锁冲突, 提高几十倍并发更新效率

PostgreSQL 使用advisory lock或skip locked消除行锁冲突, 提高几十倍并发更新效率 作者 digoal 日期 2016-10-18 标签 PostgreSQL , advisory lock , 高并发更新 背景 通常在数据库中最小粒度的锁是行锁,当一个事务正在更新某条记录时,另一个事务如果要更新同一条记录(或者申请这一条记录的锁),则必须等待锁释放. 通常持锁的时间需要保持到事务结束,也就是说,如果一个长事务持有了某条记录的锁,其他会话要持有这条记录的锁,可能要

HBase行锁原理及实现

        请带着如下问题阅读本文.        1.什么是行锁?        2.HBase行锁的原理是什么?        3.HBase行锁是如何实现的?        4.HBase行锁是如何应用的?         一.什么是行锁?         我们知道,数据库中存在事务的概念.事务是作为单个逻辑工作单元执行的一系列操作,要么完全地执行,要么完全的不执行.而事务的四大特点即原子性.一致性.分离性和持久性.其中,原子性首当其冲,那么在HBase内部实现其原子性的重要保证是什么

拨云见日—深入解析Oracle TX 行锁(上)

在刚刚过去不久的第七届数据技术嘉年华上,性能优化专家怀晓明老师进行了Oracle性能优化的主题分享.在他多年的优化生涯中,一直遵守的优化理念是,平衡是唯一的核心.我们整理了怀老师大会的演讲内容,今天一起来学习,如何在实践中应用这一理念并实现有效的性能优化. 演讲实录 优化的核心思想是平衡.在数据库的运行中,平衡取决于三个方面: 需求:指的是要做什么: 资源:是系统中所能提供的内容: 实现:指的是为了满足需求,应该如何利用提供的资源. 只有三者达到平衡,系统才能够高效地运行. 今天的内容将会通过O