当前位置: 首页 > 工具软件 > TokuDB > 使用案例 >

Innodb 和 Tokudb 基本性能对比

姬飞飙
2023-12-01

一 硬件和参数

1.1 测试机器

测试机器是一台建立在 KVM 的 Centos, 4C、8G。作为个人开发所用。测试中只运行 MySQL。

top - 19:33:02 up 176 days,  2:47,  1 user,  load average: 0.08, 0.03, 0.05
Tasks: 139 total,   1 running, 138 sleeping,   0 stopped,   0 zombie
%Cpu0  :  0.3 us,  1.3 sy,  0.0 ni, 98.3 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu1  :  0.3 us,  0.3 sy,  0.0 ni, 99.3 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu2  :  0.3 us,  0.3 sy,  0.0 ni, 99.3 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu3  :  0.3 us,  0.7 sy,  0.0 ni, 99.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
KiB Mem :  8010844 total,   140608 free,  2698796 used,  5171440 buff/cache
KiB Swap:  2097148 total,  2093812 free,     3336 used.  4563576 avail Mem

1.2 选取版本

nameversion
MariaDB10.0.13
InnoDB5.6.19-67.0
TokuDB7.1.7

1.3 MySQL 参数设置

Variable_nameValuedes
innodb_buffer_pool_size53687091205 G
innodb_thread_concurrency20并发数
innodb_compression_level6压缩等级
tokudb_block_size4194304
tokudb_cache_size4101552128
tokudb_read_buf_size131072
tokudb_row_formattokudb_zlib压缩等级

二 缓存设置

为了避免缓存造成的测试查询结果失真,数据库关闭 Query_cache。

+------------------------------+-------+
| Variable_name                | Value |
+------------------------------+-------+
| query_cache_type             | OFF   |
+------------------------------+-------+

三 结构和数据

模拟一张用户激活表,共计1414656条。

3.1 TukuDB

CREATE TABLE `active_tokudb` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '',
  `active_date` int(11) unsigned NOT NULL DEFAULT '19700101' COMMENT '',
  `device_id` varchar(50) NOT NULL DEFAULT '',
  `original_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '',
  
  ..... 省去部分字段 .....
  
  `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '',
  `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '',
  PRIMARY KEY (`id`),
  UNIQUE KEY `device_id` (`device_id`,`original_id`) USING BTREE,
  KEY `active_date` (`active_date`) USING BTREE
) ENGINE=TokuDB AUTO_INCREMENT=528991 DEFAULT CHARSET=utf8 COMMENT='' `compression`='tokudb_zlib';

3.2 InnoDB

CREATE TABLE `active_innodb` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '',
  `active_date` int(11) unsigned NOT NULL DEFAULT '19700101' COMMENT '',
  `device_id` varchar(50) NOT NULL DEFAULT '',
  `original_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '',
  
    ..... 省去部分字段 .....

  `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '',
  `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '',
  PRIMARY KEY (`id`),
  UNIQUE KEY `device_id` (`device_id`,`original_id`) USING BTREE,
  KEY `active_date` (`active_date`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=528991 DEFAULT CHARSET=utf8 COMMENT='';

四 基准测试

4.1 压缩对比

两者都设置为中等级压缩

innodb 压缩等级 innodb_compression_level 设置为 6
tokudb 压缩等级 tokudb_row_format 设置为 tokudb_zlib

同数量级数据下,两表的压缩对比如下:

TABLE_NAMEdata_sizeindex_size
active_innodb263.75 MB0.00 MB
active_tokudb249.73 MB34.23 MB

在百万数据级下似乎 tokudb 的压缩优势并不是很明显。

4.2 OnLine DDL

索引操作(Index Operations)

对于添加索引,添加/删除列、修改列NULL/NOT NULL属性等操作,需要修改MySQL内部的数据记录,对这类操作进行Online DDL操作时,需要重建表(rebuild)。

相反,对于删除索引,修改列默认值,修改列名等操作不需要修改MySQL内部的数据记录,只需要修改结构数据frm文件,而不需要重建表(no-rebuild)。

故在此仅仅测试增加索引。

  1. 一般索引
    ALTER TABLE test.active_innodb ADD INDEX active_time(active_time) USING BTREE;
    ALTER TABLE test.active_tokedb ADD INDEX active_time(active_time) USING BTREE;

  2. 唯一索引
    ALTER TABLE test.active_innodb ADD UNIQUE INDEX device_id(device_id, original_id) USING BTREE;
    ALTER TABLE test.active_tokudb ADD UNIQUE INDEX device_id(device_id, original_id) USING BTREE;

表现如下表:

engine一般索引唯一索引
innodb7.18 s12.87 s
tokudb4.46 s20.46 s

tokudb采用的分型树索引ft-index采用更大的索引页和数据页(ft-index默认为4M, InnoDB默认为16K), 这使得ft-index的数据页和索引页的压缩比更高。也就是说,在打开索引页和数据页压缩的情况下,插入等量的数据, ft-index占用的存储空间更少。

但tokudb采用压缩索引的速度相对innodb还是会慢一些。

字段操作(schema Operations)

TokuDB支持在轻微阻塞DML情况下,增加或删除表中的字段或者扩展字段长度。

执行在线增减字段时表会锁一小段时间,一般是秒级锁表。锁表时间短得益于fractal tree的实现。TokuDB会把这些操作放到后台去做,具体实现是:往root块推送一个广播msg,通过逐层apply这个广播msg实现增减字段的操作。

但是也要注意一些点:

  1. 所有的这些操作不是立即执行, 而是放到后台中由 Fractal Tree 完成, 操作包括主键和非主键索引。也可以手工强制执行这些操作, 使用 OPTIMIZE TABLE X 命令即可, TokuDB 从1.0 开始OPTIMIZE TABLE命令也支持在线完成, 但是不会重建索引。
  2. 不要一次更新多列, 分开对每列进行操作。
  3. 避免同时对一列进行 add, delete, expand 或 drop 操作。
  4. 表锁的时间主要由缓存中的脏页(dirty page)决定, 脏页越多 flush 的时间就越长. 每做一次更新, MySQL 都会关闭一次表的连接以释放之前的资源。
  5. 避免删除的列是索引的一部分, 这类操作会特别慢, 非要删除的话可以去掉索引和该列的关联再进行删除操作
    扩充类的操作只支持 char, varchar, varbinary 和 int 类型的字段。
  6. 一次只 rename 一列, 操作多列会降级为标准的 MySQL 行为, 语法中列的属性必须要指定上。如:ALTER TABLE table CHANGE column_old column_new DATA_TYPE REQUIRED_NESS DEFAULT
  7. rename 操作还不支持字段: TIME, ENUM, BLOB, TINYBLOB, MEDIUMBLOB, LONGBLOB。
  8. 不支持更新临时表。

InnoDB中不同的版本或不同的操作对online DDL有不同的实现方式。

早期实现方式(MySQL5.6.7之前版本)

  1. COPY方式。这是InnoDB最早期支持的方式,主要实现步骤:

    1. 创建与原表结构定义一致的临时表;
    2. 对原表加锁,不允许执行DML,但允许查询;
    3. 在临时表上执行DDL语句;
    4. 逐行拷贝原表数据到临时表;
    5. 原表与临时表进行RENAME操作,此时会升级原表上的锁,不允许读写,直至完成DDL操作;
  2. INPLACE方式。是MySQL5.5及之后版本为了提高创建二级索引效率的方式,所以INPLACE方式仅限于二级索引的创建跟删除。主要实现步骤:

    1. 创建临时的frm文件;
    2. 对原表加锁,不允许执行DML,但允许查询;
    3. 根据聚集索引的顺序,构造新的索引项,按照顺序插入新索引页;
    4. 升级原表上的锁,不允许读写操作;
    5. 进行RENAME操作,替换原表的frm文件,完成DDL操作。

相对于COPY方式,INPLACE方式在原表上进行,不会生成临时表,也不会拷贝原表数据,减少了很多系统I/O资源占用,但还是无法进行DML操作,也只适用于索引的创建与删除,并不适用于其他类型的DDL语句。

当前实现方式(MySQL5.6.7及之后版本)

  1. Online DDL方式。

    Online DDL特性是基于MySQL5.5的InnoDB fast index creation上改进增强的。Online DDL同样包含COPY方式、INPLACE方式。
    其中,某些DDL语句不支持Online DDL的就采用COPY方式,支持Online DDL的则采用INPLACE方式,因为Online DDL是对早期INPLACE方式的增加,所以INPLACE方式根据是否涉及到记录格式的修改又分为Rebuilds Table、No-Rebuilds Table两种情形。
    Rebuilds Table操作是因为DDL有涉及到行记录格格式的修改,如字段的增、删、类型修改等。
    No-Rebuilds Table则不涉及行记录格式的修改,如索引删除、字段名修改等。

基于上面了解到的内容开始增减字段的测试。

  1. 增加字段

    ALTER TABLE test.active_tokudb ADD COLUMN test_field varchar(50) NOT NULL DEFAULT ‘’ AFTER oaid;

    ALTER TABLE test.active_innodb ADD COLUMN test_field varchar(50) NOT NULL DEFAULT ‘’ AFTER oaid;

  2. 删除字段

    ALTER TABLE test.active_tokudb DROP COLUMN test_field;

    ALTER TABLE test.active_innodb DROP COLUMN test_field;

表现如下:

engineadd columndrop column
Tokudb0.70 s0.16 s
Innodb1 min 32.70 s1 min 4.28 s

Tokudb因为 Fractal-tree 索引的特性, 将随机的 IO 操作替换为顺序 IO 操作。在字段的变更上有着更好的体现。

4.3 DML

InnoDB 是以主键组织的B+Tree结构,数据按照主键顺序排列。对于顺序的自增主键有很好的性能,但是不适合随机写入,大量的随机I/O会使数据页分裂产生碎片,索引维护开销很多大。

TokuDB 采用 Fractal Tree的索引结构,使其在随机写数据的处理上有很大提升。
TokuDB 解决随机写入的问题得益于其索引结构,Fractal Tree 和 B-Tree 的差别主要在于索引树的内部节点上,B-Tree索引的内部结构只有指向父节点和子节点的指针,而 Fractal Tree 的内部节点不仅有指向父节点和子节点的指针,还有一块 Buffer 区。

当数据写入时会先落到这个 Buffer 区上,该区是一个 FIFO 结构,写是一个顺序的过程,和其他缓冲区一样,满了就一次性刷写数据。所以 TokuDB上 插入数据基本上变成了一个顺序添加的过程。

Query

  1. Point-Query。 where id = ? (其中id是索引)的查询操作称之为Point-Query。
    select * from active_tikedb where active_time = ‘2021-01-10 14:00:20’;
    select * from active_innodb where active_time = ‘2021-01-10 14:00:20’;

    采用分形树索引的 TokuDB 会稍微慢一些,但是在百万数量级并没有啥差距。TokuDB 稍慢是需要加载Root节点,通过二分搜索确定Key落在Root节点的键值区间Range, 找到对应的Range的Child指针。

    加载Child指针对应的的节点。 若该节点为非叶子节点,则继续沿着分形树一直往下查找,一直到叶子节点停止。 若当前节点为叶子节点,则停止查找。

    查找到叶子节点后,我们并不能直接返回叶子节点中的BasementNode的Value给用户。 因为分形树的插入操作是通过消息(Message)的方式插入的, 此时需要把从Root节点到叶子节点这条路径上的所有消息依次apply到叶子节点的BasementNode。 待apply所有的消息完成之后,查找BasementNode中的key对应的value,就是用户需要查找的值。

  2. Range-Query。 where id >= ? and id <= ? (其中id是索引)的查询操作称之为Range-Query。
    select * from active_tokudb where active_time >= ‘2021-01-10 14:00:20’ and active_time <= ‘2021-01-11 23:0:00’;
    select * from active_innodb where active_time >= ‘2021-01-10 14:00:20’ and active_time <= ‘2021-01-11 23:0:00’;

    范围查询中 Tokudb 花费了1.23S, Innodb 花费了 0.56 s, 采用了分形树索引的 TokuDB 在查询上慢的特性就体现出来了。

    分形树的 Range-Query 基本等价于进行N次 Point-Query 操作,操作的代价也基本等价于N次 Point-Query 操作的代价。 由于分形树在非叶子节点的 msg_buffer 中存放着 BasementNode 的更新操作,因此在查找每一个 Key 的 Value 时,都需要从根节点查找到叶子节点,然后将这条路径上的消息apply 到 basenmentNode 的 Value上。

    在 B+ 树中,由于底层的各个叶子节点都通过指针组织成一个双向链表。 因此,只需要从跟节点到叶子节点定位到第一个满足条件的Key, 然后不断在叶子节点迭代 next 指针,即可获取到 Range-Query 的所有 Key-Value键值。因此,对于 B+ 树的 Range-Query 操作来说,除了第一次需要从 root 节点遍历到叶子节点做随机写操作,后继数据读取基本可以看做是顺序IO。

    结论通过比较分形树和B+树的 Range-Query 实现可以可知,分形树的 Range-Query 查询代价明显比B+ 树代价高,因为分型树需要遍历 Root 节点的覆盖 Range 的整颗子树,而 B+ 树只需要一次 Seed 到 Range 的起始Key,后续迭代基本等价于顺序IO。

Write (Insert/Delete/Update)

分形树是一种写优化(随机写)的数据结构, 它的写操作性能要优于B+树的写操作性能。而在B+树中,大量的这种随机写操作将导致LRU-Cache中大量的热点数据页落在B+树的上层。这样底层的叶子节点命中Cache的概率降低,从而造成大量的磁盘IO操作,导致B+树的随机写性能瓶颈。但B+树的顺序写操作很快,因为顺序写操作充分利用了局部热点数据,磁盘IO次数大大降低。

分形树插入操作的流程大致可分为以下几点:

  1. 加载 Root 节点;
  2. 判断 Root 节点是否需要分裂(或合并),如果满足分裂(或者合并)条件,则分裂(或者合并)Root节点。
  3. 当 Root 节点 height > 0, 也就是 Root 是非叶子节点时,通过二分搜索找到Key所在的键值区间Range,将(Key, Value)包装成一条消息(Insert, Key, Value),放入到键值区间 Range 对应的 Child 指针的 Message Buffer中。
  4. 当 Root 节点 height = 0 时,即 Root 是叶子节点时,将消息(Insert, Key, Value)应用(Apply) 到 BasementNode 上,也就是插入(Key, Value)到 BasementNode中。

在大量的插入(包括随机和顺序插入)情况下, Root 节点会经常性的被撑饱满,这将会导致Root节点做大量的分裂操作。然后,Root 节点做了大量的分裂操作之后,产生大量的height=1的节点, 然后height=1的节点被撑爆满之后,又会产生大量height=2的节点, 最终树的高度越来越高。

但每一次插入操作都落在Root节点就马上返回了,每次写操作并不需要搜索树形结构最底层的 BasementNode, 这样会导致大量的热点数据集中落在在 Root 节点的上层,从而充分利用热点数据的局部性,大大减少了磁盘IO操作。

对两表批量插入900条数据,在速度上差距不大,没有测出 TOkudb 的优势,后面想想其他办法再试试。

五 结论

  1. 高压缩比 尤其是对字符串(varchar,text等)类型有非常高的压缩比,比较适合存储日志、原始数据等。但是我测试下来并不是很明显…

  2. online DDL HCADER 特性,支持在线字段增加、删除、扩展、重命名操作。上面测试看到相对 Innodb 有明显的优势。

  3. 非常快的写入性能。哈哈哈,我没测出来…

  4. 支持show processlist 进度查看,这点比较人性化,Innodb 需要借助性能模式实现。

  5. 没有完善的热备工具,只能通过mysqldump进行逻辑备份。

  6. 不支持外键(foreign key)功能。

 类似资料: