测试机器
测试机器是一台建立在 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
版本相关
name | version |
---|---|
MariaDB | 10.0.13 |
InnoDB | 5.6.19-67.0 |
TokuDB | 7.1.7 |
MySQL 参数设置
Variable_name | Value | des |
---|---|---|
innodb_buffer_pool_size | 5368709120 | 5 G |
innodb_thread_concurrency | 20 | 并发数 |
innodb_compression_level | 6 | 压缩等级 |
tokudb_block_size | 4194304 | |
tokudb_cache_size | 4101552128 | |
tokudb_read_buf_size | 131072 | |
tokudb_row_format | tokudb_zlib | 压缩等级 |
为了避免缓存造成的测试查询结果失真,数据库关闭 Query_cache。
+------------------------------+-------+
| Variable_name | Value |
+------------------------------+-------+
| query_cache_type | OFF |
+------------------------------+-------+
模拟一张用户激活表,共计1414656条。
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';
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='';
两者都设置为中等级压缩
innodb 压缩等级 innodb_compression_level 设置为 6
tokudb 压缩等级 tokudb_row_format 设置为 tokudb_zlib
同数量级数据下,两表的压缩对比如下:
TABLE_NAME | data_size | index_size |
---|---|---|
active_innodb | 263.75 MB | 0.00 MB |
active_tokudb | 249.73 MB | 34.23 MB |
在百万数据级下似乎 tokudb 的压缩优势并不是很明显。
索引操作(Index Operations)
对于添加索引,添加/删除列、修改列NULL/NOT NULL属性等操作,需要修改MySQL内部的数据记录,对这类操作进行Online DDL操作时,需要重建表(rebuild)。
相反,对于删除索引,修改列默认值,修改列名等操作不需要修改MySQL内部的数据记录,只需要修改结构数据frm文件,而不需要重建表(no-rebuild)。
故在此仅仅测试增加索引。
一般索引
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;
唯一索引
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 | 一般索引 | 唯一索引 |
---|---|---|
innodb | 7.18 s | 12.87 s |
tokudb | 4.46 s | 20.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实现增减字段的操作。
但是也要注意一些点:
InnoDB中不同的版本或不同的操作对online DDL有不同的实现方式。
早期实现方式(MySQL5.6.7之前版本)
COPY方式。这是InnoDB最早期支持的方式,主要实现步骤:
INPLACE方式。是MySQL5.5及之后版本为了提高创建二级索引效率的方式,所以INPLACE方式仅限于二级索引的创建跟删除。主要实现步骤:
相对于COPY方式,INPLACE方式在原表上进行,不会生成临时表,也不会拷贝原表数据,减少了很多系统I/O资源占用,但还是无法进行DML操作,也只适用于索引的创建与删除,并不适用于其他类型的DDL语句。
当前实现方式(MySQL5.6.7及之后版本)
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则不涉及行记录格式的修改,如索引删除、字段名修改等。
基于上面了解到的内容开始增减字段的测试。
增加字段
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
;
删除字段
ALTER TABLE test
.active_tokudb
DROP COLUMN test_field
;
ALTER TABLE test
.active_innodb
DROP COLUMN test_field
;
表现如下:
engine | add column | drop column |
---|---|---|
Tokudb | 0.70 s | 0.16 s |
Innodb | 1 min 32.70 s | 1 min 4.28 s |
Tokudb因为 Fractal-tree 索引的特性, 将随机的 IO 操作替换为顺序 IO 操作。在字段的变更上有着更好的体现。
InnoDB 是以主键组织的B+Tree结构,数据按照主键顺序排列。对于顺序的自增主键有很好的性能,但是不适合随机写入,大量的随机I/O会使数据页分裂产生碎片,索引维护开销很多大。
TokuDB 采用 Fractal Tree的索引结构,使其在随机写数据的处理上有很大提升。
TokuDB 解决随机写入的问题得益于其索引结构,Fractal Tree 和 B-Tree 的差别主要在于索引树的内部节点上,B-Tree索引的内部结构只有指向父节点和子节点的指针,而 Fractal Tree 的内部节点不仅有指向父节点和子节点的指针,还有一块 Buffer 区。
当数据写入时会先落到这个 Buffer 区上,该区是一个 FIFO 结构,写是一个顺序的过程,和其他缓冲区一样,满了就一次性刷写数据。所以 TokuDB上 插入数据基本上变成了一个顺序添加的过程。
Query
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,就是用户需要查找的值。
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次数大大降低。
分形树插入操作的流程大致可分为以下几点:
在大量的插入(包括随机和顺序插入)情况下, Root 节点会经常性的被撑饱满,这将会导致Root节点做大量的分裂操作。然后,Root 节点做了大量的分裂操作之后,产生大量的height=1的节点, 然后height=1的节点被撑爆满之后,又会产生大量height=2的节点, 最终树的高度越来越高。
但每一次插入操作都落在Root节点就马上返回了,每次写操作并不需要搜索树形结构最底层的 BasementNode, 这样会导致大量的热点数据集中落在在 Root 节点的上层,从而充分利用热点数据的局部性,大大减少了磁盘IO操作。
对两表批量插入900条数据,在速度上差距不大,没有测出 TOkudb 的优势,后面想想其他办法再试试。
高压缩比 尤其是对字符串(varchar,text等)类型有非常高的压缩比,比较适合存储日志、原始数据等。但是我测试下来并不是很明显…
online DDL HCADER 特性,支持在线字段增加、删除、扩展、重命名操作。上面测试看到相对 Innodb 有明显的优势。
非常快的写入性能。哈哈哈,我没测出来…
支持show processlist 进度查看,这点比较人性化,Innodb 需要借助性能模式实现。
没有完善的热备工具,只能通过mysqldump进行逻辑备份。
不支持外键(foreign key)功能。