MyRocks DDL原理

最近一个日常实例在做DDL过程中,直接把数据库给干趴下了,问题还是比较严重的,于是赶紧排查问题,撸了下crash堆栈和alert日志,发现是在去除唯一约束的场景下,MyRocks存在一个严重的bug,于是紧急向官方提了一个bug。其实问题比较隐蔽,因为直接一条DDL语句,数据库是不会挂了,而是在特定情况下,并且对同一个索引操作多次才会发生,因此排查问题也费了一些时间,具体bug排查和复现过程不在此展开,有兴趣的童鞋可以直接看bug链接:https://github.com/facebook/mysql-5.6/issues/602。借着排查问题的机会,我梳理了MyRocks DDL的工作流程,下文主要包括3方面内容:MyRocks数据字典,DDL操作除了修改数据本身,很重要的一个工作是维护数据字典,第二部分是MyRocks DDL的流程,主要围绕增加/删除索引的场景展开,最后一部分是分析DDL异常处理逻辑。

数据字典
所谓数据字典,就是存储引擎元数据的地方。数据字典可以从两个维度来看,从用户角度来看,数据字典就是information_schema表中的
RocksDB相关的表,主要包括ROCKSDB_DDL,ROCKSDB_INDEX_FILE_MAP等。而从RockDB内部实现角度来看,所有元数据都以KV对的方式存储在system column family中。我们看到的information_schema中表的信息,其实都是通过system column family中的元数据构造出来的,同时在mysqld启动时,也会构造一份元数据存储在内存中,方便快速检索查询。下面我会列出RocksDB数据字典的几种类型,并列出每种类型KV对的形式。
// Data dictionary types

1). DDL_ENTRY_INDEX_START_NUMBER
表和索引之间的映射关系
key: Rdb_key_def::DDL_ENTRY_INDEX_START_NUMBER(0x1) + dbname.tablename
value: version + {global_index_id}*n_indexes_of_the_table

2). INDEX_INFO
索引id和索引属性的关系
key: Rdb_key_def::INDEX_INFO(0x2) + global_index_id
value: version, index_type, key_value_format_version

index_type:主键/二级索引/隐式主键
key_value_format_version: 记录存储格式的版本

3). CF_DEFINITION
column family属性
key: Rdb_key_def::CF_DEFINITION(0x3) + cf_id
value: version, {is_reverse_cf, is_auto_cf}

is_reverse_cf: 是否是reverse column family
is_auto_cf: column family名字是否是$per_index_cf,名字自动由table.indexname组成

4). BINLOG_INFO_INDEX_NUMBER
binlog位点及gtid信息,binlog_commit更新此信息
key: Rdb_key_def::BINLOG_INFO_INDEX_NUMBER (0x4)
value: version, {binlog_name,binlog_pos,binlog_gtid}

5). DDL_DROP_INDEX_ONGOING
删除的索引任务
key: Rdb_key_def::DDL_DROP_INDEX_ONGOING(0x5) + global_index_id
value: version

6). INDEX_STATISTICS
索引统计信息
key: Rdb_key_def::INDEX_STATISTICS(0x6) + global_index_id
value: version, {materialized PropertiesCollector::IndexStats}

7). MAX_INDEX_ID
当前的index_id,每次创建索引index_id都从这个获取和更新
key: Rdb_key_def::CURRENT_MAX_INDEX_ID(0x7)
value: version, current max index id

8). DDL_CREATE_INDEX_ONGOING
待创建的索引任务
key: Rdb_key_def::DDL_CREATE_INDEX_ONGOING(0x8) + global_index_id
value: version

DDL流程
RocksDB引擎并没有类似InnoDB引擎的增量row_log机制,因此MyRocks还不支持Online DDL,只是对部分DDL操作支持了inplace方式,从check_if_supported_inplace_alter接口实现可知,对于DROP_INDEX, DROP_UNIQUE_INDEX和ADD_INDEX这三个操作,可以通过inplace的方式完成DDL,inplace方式的优点就是不需要拷贝表,间接减少了锁表时间,其它操作都只能通过重建表的方式来实现。下面我以inplace的方式说明DDL的执行流程,copy方式相对会更简单一些。总的入口函数是mysql_inplace_alter_table,主要包含4个阶段。
1).检查存储引擎是否支持inplace的DDL操作
接口:ha_rocksdb::check_if_supported_inplace_alter
MyRocks支持inplace方式操作类型是HA_ALTER_INPLACE_SHARED_LOCK_AFTER_PREPARE,意味着执行DDL过程中会堵塞写。
2).准备阶段
接口:ha_rocksdb::prepare_inplace_alter_table
对于 RocksDB引擎来说,支持inplace方式主要是添加和删除索引,因此这个过程主要做的事情是搜集需要添加、删除的索引信息。涉及到数据字典操作具体入口函数是create_key_defs,最终调用create_key_def接口,每个索引对应一个Rdb_key_def对象。这里涉及到一个主要操作是为索引产生全局有序的index_id(ddl_manager.get_and_update_next_number)。

3).执行阶段
接口:ha_rocksdb::inplace_alter_table
这里主要是添加二级索引操作,具体实现在inplace_populate_sk接口。主要包括两部分内容,更新数据字典和创建索引。
a.更新数据字典
数据字典维护通过最终通过接口start_ongoing_index_operation完成,为新建索引构造KV对,写入system column family。
,所有添加的索引的KV对会作为一个事务commit,表示一批待创建索引的任务。

b.创建索引
接下来就是真正创建索引的操作,通过遍历PK索引,构造出新增二级索引的格式记录,然后写入索引,主要实现接口在update_sk里。由于RockDB行锁实现中,每个key对应一把锁,并且锁对象不能复用,因此锁消耗的总内存与key大小和key数量相关,为了保证系统运行中内存可控,一般开启rocksdb_commit_in_the_middle避免大事务。因此这个这个过程也会触发是否提前提交事务的检查,主要实现接口在do_bulk_commit里面。

4).提交或回滚阶段
接口:commit_inplace_alter_table
a.处理待删除的索引,最终通过接口start_ongoing_index_operation(drop)完成。
b.对于新增索引,写入索引字典信息
c.写入表和索引的映射关系
对表进行alter操作后,会增一些索引,并删除一些索引,因此表对应的索引关系需要重建,主要实现接口在Rdb_tbl_def::put_dict里面。
第1),2),3)涉及的字典操作整个作为一个事务提交。

d.维护数据字典在内存中对象m_ddl_hash。
主要工作是从hash表中摘掉老的tbl对象,写入新的tbl对象,主要实现接口在Rdb_ddl_manager::put里面。

e.清理DDL_CREATE_INDEX_ONGOING标记。
正常执行到这里,表示新建的索引已经成功执行,需要清理DDL_CREATE_INDEX_ONGOING标记。主要实现接口在finish_indexes_operation里面,最终调用end_ongoing_index_operation将之前加入的KV对进行删除动作。
(DDL_CREATE_INDEX_ONGOING,cf_id,index_id)->(DDL_CREATE_INDEX_ONGOING_VERSION),并将整个操作作为一个事务commit。我们可以看到,整个过程已经执行完毕,但并没有看到哪里将删除的索引真正清理掉,RocksDB里面删除索引实质是一个异步的过程,真正删除索引的动作通过后台线程Rdb_drop_index_thread完成。所以,到这里会主动触发一次唤醒rdb_drop_idx_thread的动作,告知线程有活干了。

Rdb_drop_index_thread工作流程
1).获取待删除索引列表key=(DDL_DROP_INDEX_ONGOING)
2).逐一遍历每个需要删除的索引,按照(index_id,index_id+1)key范围来删除记录
3).并调用CompactRange触发合并
4).通过index_id来查找key,若不存在index-id相同的key,则认为index已经被清理
5).最后调用finish_indexes_operation(DDL_DROP_INDEX_ONGOING)清理待删除索引标记,并将索引字典信息从数据字典中删除,具体实现参考delete_index_info。

DDL异常处理
从上述的实现来看,我们执行一个DDL操作,除了本身索引操作的事务,涉及数据字典的操作的事务也有好几个,所以整个DDL操作并不是一个原子操作。比如在执行阶段的第1步,字典相关的操作提交后,实例crash了,那么这些字典操作内容就残留在system Column family中了,但从业务角度来看,并不影响。上面介绍的mysql_inplace_alter_table包含了DDL的主要执行过程,实际上,在此之前还会通过mysql_prepare_alter_table创建临时表定义frm文件,(文件名一般以#sql开头),该文件包含了目标表的schema定义;并在DDL结束的时候,通过mysql_rename_table更新为目标表名.frm。如果在rename之前,实例crash了,就会导致frm文件的内容仍然是老版本,但RocksDB引擎字典已经更新。从表现形式来看,就会发现show create table xxx,显示的索引内容与information_schema.ROCKSDB_DDL的数据字典不一致。前面讨论的两种情况都是inplace方式带来的问题,对于copy方式,由于需要重建表,会将临时表#sqlxxx的信息写入数据字典,如果这个动作完成后,实例crash,会导致数据字典中残留有临时表的信息。mysqld重启时,会根据字典的信息检查表是否存在,主要通过接口validate_schemas实现,具体而言,通过数据字典中的表名查找对应的frm文件,并且查找过程中会忽略#开头的临时frm文件,因此会导致只要数据字典中包含了临时表的字典信息,则会导致mysqld启动失败,并报如下错误。

如果想正常启动,可以临时通过参数rocksdb_validate_tables=2设置忽略这个错误,毕竟临时表的数据字典不影响业务表的使用。从我这里分析来看,目前DDL在异常处理这块还处理的不够好,根本原因还在于DDL不是一个原子操作,server层和引擎层的修改在某些情况下无法保持一致,导致问题出现。

相关实现文件和接口
storage/rocksdb/rdb_datadic.cc //数据字典相关代码
storage/rocksdb/rdb_i_s.cc //information_schema相关代码
myrocks::ha_rocksdb::inplace_populate_sk //更新二级索引
Rdb_dict_manager::get_max_index_id //获取最大index_id
ha_rocksdb::check_if_supported_inplace_alter //检查是否支持inplace
myrocks::ha_rocksdb::create //copy方式建表接口
myrocks::ha_rocksdb::create_key_def //建立key对象
myrocks::Rdb_ddl_manager::get_and_update_next_number //获取下一个index_id
Rdb_dict_manager::start_ongoing_index_operation //添加一个建立/删除索引的任务

Original: https://www.cnblogs.com/cchust/p/6716823.html
Author: 天士梦
Title: MyRocks DDL原理

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/591736/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • 运行的第一个Django

    2022-09-24 创建了一个Django项目后,进行测试,输入指令: python manage.py runserver 出现了如下问题: 我还想着,Django模块通过安装…

    数据库 2023年6月14日
    087
  • 事物的隔离性和MVCC

    事物的隔离性 mysql的服务端是支持多个客户端同时与之连接的,每个客户端可能还并发了好几个连接,所以mysql是需要同时处理很多事情的,每一件独立的事情就叫做事务。我们知道事务有…

    数据库 2023年5月24日
    0106
  • 多商户商城系统功能拆解23讲-平台端分销等级

    多商户商城系统,也称为B2B2C(BBC)平台电商模式多商家商城系统。可以快速帮助企业搭建类似拼多多/京东/天猫/淘宝的综合商城。 多商户商城系统支持商家入驻加盟,同时满足平台自营…

    数据库 2023年6月14日
    0103
  • 6、SQL模糊查询LIKE concat用法

    concat用来拼接查询的字符串: SELECT * FROM page_demo WHERE name LIKE concat(‘%’,#{name},’%’) 模糊查询: 1、…

    数据库 2023年6月6日
    0145
  • gh-ost使用问题记录

    因为 pt-osc 对数据库性能影响较大,且容易造成死锁问题,目前我们在线更改表结构都使用 gh-ost 工具进行修改,这里记录一下使用 gh-ost 过程中的问题,以作记录;首先…

    数据库 2023年6月9日
    090
  • 能尽量用数据库代替内存就用吧,减少整天担心内存问题

    游戏不好搞啊,设计的东西,能尽量简单就简单,代码太多判断就写死行了,反正它运行起来是对的就行了。 情形:09:00 昨天发生了很痛苦的一件事情,那就是游戏中data内存同步不到da…

    数据库 2023年6月14日
    079
  • 【JDBC】笔记(1)— JDBC概述

    1、JDBC是什么?Java DataBase Connectivity(Java语言连接数据库) 2、JDBC的本质是什么?JDBC是SUN公司制定的一套 接口(实质)java….

    数据库 2023年5月24日
    0108
  • login方法访问不到解决过程

    背景:由于项目登录模块之前使用传统的字符验证码,干扰又太严重,经常会有输入十次以上才能蒙对的情况。于是提出让改为滑动验证码(斗鱼,B站等等)。如图所示: 原有的: 要改的: 这个实…

    数据库 2023年6月11日
    0155
  • MySQL函数学习(一)—–字符串函数

    注:笔记旨在记录 一、MySQL 字符串函数 \ 函 数 名 称 作 用 完 成 1 LENGTH 计算字符串长度 勾 2 CONCAT 字符串拼接,返回结果为连接参数产生的字符串…

    数据库 2023年6月16日
    084
  • Java学习-第一部分-第三阶段-项目实战:满汉楼项目

    满汉楼项目 笔记目录:(https://www.cnblogs.com/wenjie2000/p/16378441.html) 注意:笔记内容仅为实现该项目的基本后端功能,并不会实…

    数据库 2023年6月11日
    0127
  • 局域网内访问子网服务(访问电脑虚拟机中的服务)

    局域网内访问子网服务 问题描述: 同一个路由器(172.18.0.0)下面有两台电脑A(172.18.40.45)和B (172.18.44.173) ,在B电脑上安装虚拟机 ,使…

    数据库 2023年6月9日
    0108
  • 自动化测试练手项目推荐

    转载请注明出处❤️ 作者:测试蔡坨坨 原文链接:caituotuo.top/80599ac8.html 你好,我是测试蔡坨坨。 最近收到许多自学自动化测试的小伙伴私信,学习了理论知…

    数据库 2023年6月11日
    0130
  • Python实现XMind测试用例快速转Excel用例

    转载请注明出处❤️ 作者:测试蔡坨坨 原文链接:caituotuo.top/c2d10f21.html 你好,我是测试蔡坨坨。 今天分享一个Python编写的小工具,实现XMind…

    数据库 2023年6月11日
    0106
  • 索引的树结构

    二分查找 二叉树 二叉平衡树 B-TREE :二叉平衡树的基础上,使加载一次节点,可以加载更多路径数据,同时把查询范围缩减到更小 缺点:业务数据的大小可能远远超过了索引数据的大小,…

    数据库 2023年6月16日
    0146
  • MySQL启动过程详解一:启动整体流程

    MySQL启动流程如下: 设置进程名 处理配置文件及启动参数以及部分模块初始化,这包括: 2.1 从配置文件中读取选项,把他们放在 argc 和 argv 已有的参数之前 2.2 …

    数据库 2023年6月9日
    070
  • 2022-8-17 mysql 第三天

    子查询 按照结果集的行列数不同,子查询可以分为以下几类: 标量子查询:结果集只有一行一列(单行子查询) 列子查询:结果集有一列多行 行子查询:结果集有一行多列 表子查询:结果集多行…

    数据库 2023年6月14日
    093
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球