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 的。

相关实践学习
每个IT人都想学的“Web应用上云经典架构”实战
本实验从Web应用上云这个最基本的、最普遍的需求出发,帮助IT从业者们通过“阿里云Web应用上云解决方案”,了解一个企业级Web应用上云的常见架构,了解如何构建一个高可用、可扩展的企业级应用架构。
MySQL数据库入门学习
本课程通过最流行的开源数据库MySQL带你了解数据库的世界。   相关的阿里云产品:云数据库RDS MySQL 版 阿里云关系型数据库RDS(Relational Database Service)是一种稳定可靠、可弹性伸缩的在线数据库服务,提供容灾、备份、恢复、迁移等方面的全套解决方案,彻底解决数据库运维的烦恼。 了解产品详情: https://www.aliyun.com/product/rds/mysql 
目录
相关文章
|
10月前
|
传感器 监控 网络协议
【开源源码】基于 STM32智能温度监控系统 | 一个支持远程监控与告警的嵌入式实践项目
本项目基于STM32与μC/OS实时操作系统,结合LWIP协议栈,实现支持远程监控与告警的智能温度系统。通过多任务协同,完成温度采集、网络通信与阈值告警,支持手机App实时查看与配置。系统具备高实时性、稳定性与扩展性,是RTOS与物联网技术融合的典型嵌入式实践案例,开源可学,适用于工业、智能家居等场景。
【开源源码】基于 STM32智能温度监控系统 | 一个支持远程监控与告警的嵌入式实践项目
|
人工智能 监控 数据可视化
企业级LLMOps落地指南:蜂巢架构×可视化编排实战
本文将基础的单应用扩展成多应用,并实现工作流组件,包括:多应用模块设计、工作流模块设计、LangGraph实现图应用、前端Vue-Flow组件使用、工作流转LLM工具设计思路、关联工作流登技巧。
667 3
企业级LLMOps落地指南:蜂巢架构×可视化编排实战
|
Cloud Native 安全 Java
Go语言深度解析:从入门到精通的完整指南
🌟蒋星熠Jaxonic,Go语言探索者。深耕云计算、微服务与并发编程,以代码为笔,在二进制星河中书写极客诗篇。分享Go核心原理、性能优化与实战架构,助力开发者掌握云原生时代利器。#Go语言 #并发编程 #性能优化
780 43
Go语言深度解析:从入门到精通的完整指南
|
9月前
|
缓存 JavaScript 前端开发
Vue的生命周期详解及业务场景应用
本文详细介绍Vue.js的生命周期概念及各阶段钩子函数的作用,结合实际业务场景讲解如何合理使用created、mounted、updated等钩子进行数据初始化、DOM操作、资源清理等,帮助开发者提升组件管理能力与代码性能。
343 0
Vue的生命周期详解及业务场景应用
|
消息中间件 存储 NoSQL
RocketMQ实战—6.生产优化及运维方案
本文围绕RocketMQ集群的使用与优化,详细探讨了六个关键问题。首先,介绍了如何通过ACL配置实现RocketMQ集群的权限控制,防止不同团队间误用Topic。其次,讲解了消息轨迹功能的开启与追踪流程,帮助定位和排查问题。接着,分析了百万消息积压的处理方法,包括直接丢弃、扩容消费者或通过新Topic间接扩容等策略。此外,提出了针对RocketMQ集群崩溃的金融级高可用方案,确保消息不丢失。同时,讨论了为RocketMQ增加限流功能的重要性及实现方式,以提升系统稳定性。最后,分享了从Kafka迁移到RocketMQ的双写双读方案,确保数据一致性与平稳过渡。
|
人工智能 运维 中间件
CTI中间件:现代呼叫中心的核心开发平台
乐科CTI中间件助力企业高效构建智能呼叫中心,集成通信与业务系统,支持多协议、AI融合、高可用部署,广泛应用于客服、营销等场景,降低开发成本,提升运维效率。
|
存储 算法 物联网
RFID室内人员定位管理
RFID室内人员定位管理系统通过射频信号实现人员位置的精确追踪。为人员配备带有唯一识别码的RFID标签(如卡片、腕带),室内安装多个阅读器与标签通信,读取信息。系统基于RSSI、TOA等算法计算标签位置,并在电子地图上显示实时动态。管理软件可分析人员轨迹、停留时间等数据,助力高效调度与管理,广泛适用于复杂室内场景。
|
运维 Prometheus 监控
持续监控和反馈:优化反馈机制与改进流程
持续监控和反馈:优化反馈机制与改进流程
1066 1
|
存储 Dragonfly 安全
阿里云服务器历史优惠价格展示(2023年1月到7月)
阿里云服务器每个月都有活动,不过优惠价格并不是每个月都一样的,活动内的云服务器实例规格也从年初的共享型s6、计算型c6等六代云服务器过渡到了通用算力型u1和计算型c7、通用型g7等七代云服务器为主,进入5月份之后又增加了八代计算型c8y、内存型r8y等八代云服务器,下面是小编整理汇总的2023年阿里云服务器1月到7月的历史优惠价格,以供大家参考。
643 0
阿里云服务器历史优惠价格展示(2023年1月到7月)
|
SQL 前端开发 JavaScript
IDM 平替 Gopeed Flutter 开源免费下载工具
IDM 替代品 Gopeed 是一个开源免费的 Flutter 下载工具,支持 HTTP、BitTorrent、Magnet 等协议。项目采用 getx 进行构建,已获得 13k Star。功能包括多平台下载、自定义下载目录和并发数、代理设置等。它还拥有浏览器扩展和各种下载插件。开发者可以参考代码学习 Flutter 和 getx。项目源码可在 GitHub 上找到,同时提供了编译和配置指南。Gopeed 是一个值得尝试的现代化下载工具,适用于 Flutter 开发者和用户。
1002 4
IDM 平替 Gopeed Flutter 开源免费下载工具

热门文章

最新文章