在安装MariaDB的时候了解到代替InnoDB的TokuDB,看简介很是的棒,这里对ToduDB作一个初步的整理,使用后再作更多的分享。html
在MySQL最流行的支持全事务的引擎为INNODB。其特色是数据自己是用B-TREE来组织,数据自己便是庞大的根据主键聚簇的B-TREE索引。 因此在这点上,写入速度就会有些下降,由于要每次写入要用一次IO来作索引树的重排。特别是当数据量自己比内存大不少的状况下,CPU自己被磁盘IO纠缠的作不了其余事情了。这时咱们要考虑如何减小对磁盘的IO来排解CPU的处境,常见的方法有:node
TokuDB 是一个支持事务的“新”引擎,有着出色的数据压缩功能,由美国 TokuTek 公司(如今已经被 Percona 公司收购)研发。拥有出色的数据压缩功能,若是您的数据写多读少,并且数据量比较大,强烈建议您使用TokuDB,以节省空间成本,并大幅度下降存储使用量和IOPS开销,不过相应的会增长 CPU 的压力。mysql
1.丰富的索引类型以及索引的快速建立git
TokuDB 除了支持现有的索引类型外, 还增长了(第二)集合索引, 以知足多样性的覆盖索引的查询, 在快速建立索引方面提升了查询的效率github
2.(第二)集合索引算法
也能够称做非主键的集合索引, 这类索引也包含了表中的全部列, 能够用于覆盖索引的查询须要, 好比如下示例, 在where 条件中直接命中 index_b 索引, 避免了从主键中再查找一次.sql
1
2
3
4
5
6
7
8
9
10
|
CREATE TABLE table (
column_a INT,
column_b INT,
column_c INT,
PRIMARY KEY index_a (column_a),
CLUSTERING KEY index_b (column_b)) ENGINE = TokuDB;
SELECT column_c
FROM table
WHERE column_b BETWEEN 10 AND 100;
|
见: http://tokutek.com/2009/05/introducing_multiple_clustering_indexes/数据库
3.索引在线建立(Hot Index Creation)缓存
TokuDB 容许直接给表增长索引而不影响更新语句(insert, update 等)的执行。能够经过变量 tokudb_create_index_online 来控制是否开启该特性, 不过遗憾的是目前还只能经过 CREATE INDEX 语法实如今线建立, 不能经过 ALTER TABLE 实现. 这种方式比一般的建立方式慢了许多, 建立的过程能够经过 show processlist 查看。不过 tokudb 不支持在线删除索引, 删除索引的时候会对标加全局锁。安全
1
2
3
4
|
> SET tokudb_create_index_online=ON;
Query OK, 0 rows affected (0.00 sec)
> CREATE INDEX index ON table (field_name);
|
4.在线更改列(Add, Delete, Expand, Rename)
TokuDB 能够在轻微阻塞更新或查询语句的状况下, 容许实现如下操做:
这些操做一般是以表锁级别阻塞(几秒钟时间)其余查询的执行, 当表记录下次从磁盘加载到内存的时候, 系统就会随之对记录进行修改操做(add, delete 或 expand), 若是是 rename 操做, 则会在几秒钟的停机时间内完成全部操做。
TokuDB的这些操做不一样于 InnoDB, 对表进行更新后能够看到 rows affected 为 0, 即更改操做会放到后台执行, 比较快速的缘由多是因为 Fractal-tree 索引的特性, 将随机的 IO 操做替换为顺序 IO 操做, Fractal-tree的特性中, 会将这些操做广播到全部行, 不像 InnoDB, 须要 open table 并建立临时表来完成.
看看官方对该特性的一些指导说明:
1
2
3
|
ALTER TABLE table
CHANGE column_old column_new
DATA_TYPE REQUIRED_NESS DEFAULT
|
5.数据压缩
TokuDB中全部的压缩操做都在后台执行, 高级别的压缩会下降系统的性能, 有些场景下会须要高级别的压缩. 按照官方的建议: 6核数如下的机器建议标准压缩, 反之可使用高级别的压缩。
每一个表在 create table 或 alter table 的时候经过 ROW_FORMAT 来指定压缩的算法:
1
2
3
4
|
CREATE TABLE table (
column_a INT NOT NULL PRIMARY KEY,
column_b INT NOT NULL) ENGINE=TokuDB
ROW_FORMAT=row_format;
|
ROW_FORMAT默认由变量 tokudb_row_format 控制, 默认为 tokudb_zlib, 能够的值包括:
6.Read free 复制特性
得益于 Fracal Tree 索引的特性, TokuDB 的 slave 端可以以低于读IO的消耗来应用 master 端的变化, 其主要依赖 Fractal Tree 索引的特性,能够在配置里启用特性
很差的是, 若是启用了 Read Free Replication 功能, Server 端须要作以下设置:
slave 端的设置能够在一台或多台 slave 中设置:MySQL5.5 和 MariaDB5.5中只有定义了主键的表才能使用该功能, MySQL 5.6, Percona 5.6 和 MariaDB 10.X 没有此限制
7.事务, ACID 和恢复
8.过程追踪
TokuDB 提供了追踪长时间运行语句的机制. 对 LOAD DATA 命令来讲,SHOW PROCESSLIST 能够显示过程信息, 第一个是相似 “Inserted about 1000000 rows” 的状态信息, 下一个是完成百分比的信息, 好比 “Loading of data about 45% done”; 增长索引的时候, SHOW PROCESSLIST 能够显示 CREATE INDEX 和 ALTER TABLE 的过程信息, 其会显示行数的估算值, 也会显示完成的百分比; SHOW PROCESSLIST 也会显示事务的执行状况, 好比 committing 或 aborting 状态.
9.迁移到 TokuDB
可使用传统的方式更改表的存储引擎, 好比 “ALTER TABLE … ENGINE = TokuDB” 或 mysqldump 导出再倒入, INTO OUTFILE 和 LOAD DATA INFILE 的方式也能够。
10.热备
Percona Xtrabackup 还未支持 TokuDB 的热备功能, percona 也为表示有支持的打算 http://www.percona.com/blog/2014/07/15/tokudb-tips-mysql-backups/ ;对于大表可使用 LVM 特性进行备份, https://launchpad.net/mylvmbackup , 或 mysdumper 进行备份。TokuDB 官方提供了一个热备插件 tokudb_backup.so, 能够进行在线备份, 详见 https://github.com/Tokutek/tokudb-backup-plugin, 不过其依赖 backup-enterprise, 没法编译出 so 动态库, 是个商业的收费版本, 见 https://www.percona.com/doc/percona-server/5.6/tokudb/tokudb_installation.html
总结
TokuDB的优势:
TokuDB缺点:
适用场景:
TokuDB和InnoDB最大的不一样在于TokuDB采用了一种叫作Fractal Tree的索引结构,使其在随机写数据的处理上有很大提高。目前不管是SQL Server,仍是MySQL的innodb,都是用的B+Tree(SQL Server用的是标准的B-Tree)的索引结构。InnoDB是以主键组织的B+Tree结构,数据按照主键顺序排列。对于顺序的自增主键有很好的性能,可是不适合随机写入,大量的随机I/O会使数据页分裂产生碎片,索引维护开销不少大。TokuDB解决随机写入的问题得益于其索引结构,Fractal Tree 和 B-Tree的差异主要在于索引树的内部节点上,B-Tree索引的内部结构只有指向父节点和子节点的指针,而Fractal Tree的内部节点不只有指向父节点和子节点的指针,还有一块Buffer区。当数据写入时会先落到这个Buffer区上,该区是一个FIFO结构,写是一个顺序的过程,和其余缓冲区同样,满了就一次性刷写数据。因此TokuDB上插入数据基本上变成了一个顺序添加的过程。
BTree和Fractal tree的比较:
Structure | Inserts | Point Queries | Range Queries |
B-Tree | Horrible | Good | Good (young) |
Append | Wonderful | Horrible | Horrible |
Fractal Tree | Good | Good | Good |
分形树是一种写优化的磁盘索引数据结构。 在通常状况下, 分形树的写操做(Insert/Update/Delete)性能比较好,同时它还能保证读操做近似于B+树的读性能。据Percona公司测试结果显示, TokuDB分形树的写性能优于InnoDB的B+树), 读性能略低于B+树。
ft-index的磁盘存储结构
ft-index采用更大的索引页和数据页(ft-index默认为4M, InnoDB默认为16K), 这使得ft-index的数据页和索引页的压缩比更高。也就是说,在打开索引页和数据页压缩的状况下,插入等量的数据, ft-index占用的存储空间更少。ft-index支持在线修改DDL (Hot Schema Change)。 简单来说,就是在作DDL操做的同时(例如添加索引),用户依然能够执行写入操做, 这个特色是ft-index树形结构自然支持的。 此外, ft-index还支持事务(ACID)以及事务的MVCC(Multiple Version Cocurrency Control 多版本并发控制), 支持崩溃恢复。正由于上述特色, Percona公司宣称TokuDB一方面带给客户极大的性能提高, 另外一方面还下降了客户的存储使用成本。
ft-index的索引结构图以下:
灰色区域表示ft-index分形树的一个页,绿色区域表示一个键值,两格绿色区域之间表示一个儿子指针。 BlockNum表示儿子指针指向的页的偏移量。Fanout表示分形树的扇出,也就是儿子指针的个数。 NodeSize表示一个页占用的字节数。NonLeafNode表示当前页是一个非叶子节点,LeafNode表示当前页是一个叶子节点,叶子节点是最底层的存放Key-value键值对的节点, 非叶子节点不存放value。 Heigth表示树的高度, 根节点的高度为3, 根节点下一层节点的高度为2, 最底层叶子节点的高度为1。Depth表示树的深度,根节点的深度为0, 根节点的下一层节点深度为1。
分形树的树形结构很是相似于B+树, 它的树形结构由若干个节点组成(咱们称之为Node或者Block,在InnoDB中,咱们称之为Page或者页)。 每一个节点由一组有序的键值组成。假设一个节点的键值序列为[3, 8], 那么这个键值将(-00, +00)整个区间划分为(-00, 3), [3, 8), [8, +00) 这样3个区间, 每个区间就对应着一个儿子指针(Child指针)。 在B+树中, Child指针通常指向一个页, 而在分形树中,每个Child指针除了须要指向一个Node的地址(BlockNum)以外,还会带有一个Message Buffer (msg_buffer), 这个Message Buffer 是一个先进先出(FIFO)的队列,用来存放Insert/Delete/Update/HotSchemaChange这样的更新操做。
按照ft-index源代码的实现, 对ft-index中分形树更为严谨的说法:
分形树的Insert/Delete/Update实现
咱们说到分形树是一种写优化的数据结构, 它的写操做性能要优于B+树的写操做性能。 那么它究竟如何作到更优的写操做性能呢?首先, 这里说的写操做性能,指的是随机写操做。 举个简单例子,假设咱们在MySQL的InnoDB表中不断执行这个SQL语句: insert into sbtest set x = uuid(), 其中sbtest表中有一个惟一索引字段为x。 因为uuid()的随机性,将致使插入到sbtest表中的数据散落在各个不一样的叶子节点(Leaf Node)中。 在B+树中, 大量的这种随机写操做将致使LRU-Cache中大量的热点数据页落在B+树的上层(以下图所示)。这样底层的叶子节点命中Cache的几率下降,从而形成大量的磁盘IO操做, 也就致使B+树的随机写性能瓶颈。但B+树的顺序写操做很快,由于顺序写操做充分利用了局部热点数据, 磁盘IO次数大大下降。
下面来讲说分形树插入操做的流程。 为了方便后面描述,约定以下:
详细流程以下:
这里有一个很是诡异的地方,在大量的插入(包括随机和顺序插入)状况下, Root节点会常常性的被撑饱满,这将会致使Root节点作大量的分裂操做。而后,Root节点作了大量的分裂操做以后,产生大量的height=1的节点, 而后height=1的节点被撑爆满以后,又会产生大量height=2的节点, 最终树的高度愈来愈高。 这个诡异的之处就隐藏了分形树写操做性能比B+树高的秘诀: 每一次插入操做都落在Root节点就立刻返回了, 每次写操做并不须要搜索树形结构最底层的BasementNode, 这样会致使大量的热点数据集中落在在Root节点的上层(此时的热点数据分布图相似于上图), 从而充分利用热点数据的局部性,大大减小了磁盘IO操做。
Update/Delete操做的状况和Insert操做的状况相似, 可是须要特别注意的地方在于,因为分形树随机读性能并不如InnoDB的B+树。所以,Update/Delete操做须要细分为两种状况考虑,这两种状况测试性能可能差距巨大:
此外,ft-index为了提高顺序写的性能,对顺序插入操做作了一些优化,例如顺序写加速。
分形树的Point-Query实现
在ft-index中, 相似select from table where id = ? (其中id是索引)的查询操做称之为Point-Query; 相似select from table where id >= ? and id <= ? (其中id是索引)的查询操做称之为Range-Query。 上文已经提到, Point-Query读操做性能并不如InnoDB的B+树, 这里详细描述Point-Query的相关流程。 (这里假设要查询的键值为Key)
查找到叶子节点后,咱们并不能直接返回叶子节点中的BasementNode的Value给用户。 由于分形树的插入操做是经过消息(Message)的方式插入的, 此时须要把从Root节点到叶子节点这条路径上的全部消息依次apply到叶子节点的BasementNode。 待apply全部的消息完成以后,查找BasementNode中的key对应的value,就是用户须要查找的值。
分形树的查找流程基本和 InnoDB的B+树的查找流程相似, 区别在于分形树须要将从Root节点到叶子节点这条路径上的messge buffer都往下推,并将消息apply到BasementNode节点上。注意查找流程须要下推消息, 这可能会形成路径上的部分节点被撑饱满,可是ft-index在查询过程当中并不会对叶子节点作分裂和合并操做, 由于ft-index的设计原则是: Insert/Update/Delete操做负责节点的Split和Merge, Select操做负责消息的延迟下推(Lazy Push)。 这样,分形树就将Insert/Delete/Update这类更新操做经过将来的Select操做应用到具体的数据节点,从而完成更新。
分形树的Range-Query实现
下面来介绍Range-Query的查询实现。简单来说, 分形树的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。
总结
整体来讲,分形树是一种写优化的数据结构,它的核心思想是利用节点的MessageBuffer缓存更新操做,充分利用数据局部性原理, 将随机写转换为顺序写,这样极大的提升了随机写的效率。Tokutek研发团队的iiBench测试结果显示: TokuDB的insert操做(随机写)的性能比InnoDB快不少,而Select操做(随机读)的性能低于InnoDB的性能,可是差距较小,同时因为TokuDB采用有4M的大页存储,使得压缩比较高。这也是Percona公司宣称TokuDB更高性能,更低成本的缘由。
另外,在线更新表结构(Hot Schema Change)实现也是基于MessageBuffer来实现的, 但和Insert/Delete/Update操做不一样的是, 前者的消息下推方式是广播式下推(父节点的一条消息,应用到全部的儿子节点), 后者的消息下推方式单播式下推(父节点的一条消息,应用到对应键值区间的儿子节点), 因为实现相似于Insert操做,因此再也不展开描述。
在传统的关系型数据库(例如Oracle, MySQL, SQLServer)中,事务能够说是研发和讨论最核心内容。而事务最核心的性质就是ACID。
TokuDB目前彻底支持事务的ACID。 从实现上看, 因为TokuDB采用的分形树做为索引,而InnoDB采用B+树做为索引结构,于是TokuDB在事务的实现上和InnoDB有很大不一样。
在InnoDB中, 设计了redo和undo两种日志,redo存放页的物理修改日志,用来保证事务的持久性; undo存放事务的逻辑修改日志,它实际存放了一条记录在多个并发事务下的多个版本,用来实现事务的隔离性(MVCC)和回滚操做。因为TokuDB的分形树采用消息传递的方式来作增删改更新操做,一条消息就是事务对该记录修改的一个版本,所以,在TokuDB源码实现中,并无额外的undo-log的概念和实现,取而代之的是一条记录多条消息的管理机制。虽然一条记录多条消息的方式能够实现事务的MVCC,却没法解决事务回滚的问题,所以TokuDB额外设计了tokudb.rollback这个日志文件来作帮助实现事务回滚。
这里主要分析TokuDB的事务隔离性的实现,也就是常提到的多版本并发控制(MVCC)。
TokuDB的事务表示
在tokudb中, 在用户执行的一个事务,具体到存储引擎层面会被拆开成许多个小事务(这种小事务记为txn)。 例如用户执行这样一个事务:
1
2
3
|
begin;
insert into hello set id = 1, value = '1';
commit;
|
对应到TokuDB存储引擎的redo-log中的记录为:
1
2
3
4
5
6
|
xbegin 'b': lsn=236599 xid=15,0 parentxid=0,0 crc=29e4d0a1 len=53
xbegin 'b': lsn=236600 xid=15,1 parentxid=15,0 crc=282cb1a1 len=53
enq_insert 'I': lsn=236601 filenum=13 xid=15,1 key={...} value={...} crc=a42128e5 len=58
xcommit 'C': lsn=236602 xid=15,1 crc=ec9bba3d len=37
xprepare 'P': lsn=236603 xid=15,0 xa_xid={...} crc=db091de4 len=67
xcommit 'C': lsn=236604 xid=15,0 crc=ec997b3d len=37
|
对应的事务树以下图所示:
对一个较为复杂一点,带有savepoint的事务例子:
1
2
3
4
5
6
|
begin;
insert into hello set id = 2, value = '2' ;
savepoint mark1;
insert into hello set id = 3, value = '3' ;
savepoint mark2;
commit;
|
对应的redo-log的记录为:
1
2
3
4
5
6
7
8
9
10
11
|
xbegin 'b': lsn=236669 xid=17,0 parentxid=0,0 crc=c01888a6 len=53
xbegin 'b': lsn=236670 xid=17,1 parentxid=17,0 crc=cf400ba6 len=53
enq_insert 'I': lsn=236671 filenum=13 xid=17,1 key={...} value={...} crc=8ce371e3 len=58
xcommit 'C': lsn=236672 xid=17,1 crc=ec4a923d len=37
xbegin 'b': lsn=236673 xid=17,2 parentxid=17,0 crc=cb7c6fa6 len=53
xbegin 'b': lsn=236674 xid=17,3 parentxid=17,2 crc=c9a4c3a6 len=53
enq_insert 'I': lsn=236675 filenum=13 xid=17,3 key={...} value={...} crc=641148e2 len=58
xcommit 'C': lsn=236676 xid=17,3 crc=ec4e143d len=37
xcommit 'C': lsn=236677 xid=17,2 crc=ec4cf43d len=37
xprepare 'P': lsn=236678 xid=17,0 xa_xid={...} crc=76e302b4 len=67
xcommit 'C': lsn=236679 xid=17,0 crc=ec42b43d len=37
|
这个事务组成的一棵事务树以下:
在tokudb中,使用{parent_id, child_id}这样一个二元组来记录一个txn和其余txn的依赖关系。这样从根事务到叶子几点的一组标号就能够惟一标示一个txn, 这一组标号列表称之为xids, xids我认为也能够称为事务号。 例如txn3的xids = {17, 2, 3 } , txn2的xids = {17, 2}, txn1的xids= {17, 1}, txn0的xids = {17, 0}。
因而对于事务中的每个操做(xbegin/xcommit/enq_insert/xprepare),都有一个xids来标识这个操做所在的事务号。 TokuDB中的每一条消息(insert/delete/update消息)都会携带这样一个xids事务号。这个xids事务号,在TokuDB的实现中扮演这很是重要的角色,与之相关的功能也特别复杂。
事务管理器
事务管理器用来管理TokuDB存储引擎全部事务集合, 它主要维护着这几个信息:
分形树LeafEntry
上文分形树的树形结构中说到,在作insert/delete/update这样的操做时,会把从root到leaf的全部消息都apply到LeafNode节点中。 为了后面详细描述apply的过程,先介绍下LeafNode的存储结构。
leafNode简单来讲,就是由多个leafEntry组成,每一个leafEntry就是一个{k, v1, v2, … }这样的键值对, 其中v1, v2 .. 表示一个key对应的值的多个版本。具体到一个key对应得leafEntry的结构详细以下图所示。
由上图看出,一个leafEntry其实就是一个栈, 这个栈底部[0~5]这一段表示已经提交(commited transaction)的事务的Value值。栈的顶部[6~9]这一段表示当前还没有提交的活跃事务(uncommited transaction)。 栈中存放的单个元素为(txid, type, len, data)这样一个四元组,代表了这个事务对应的value取值。更通用一点讲,[0, cxrs-1]这一段栈表示已经提交的事务,原本已经提交的事务不该存在于栈中,但之因此存在,就是由于有其余事务经过snapshot read的方式引用了这些事务,所以,除非全部引用[0, cxrs-1]这段事务的全部事务都提交,不然[0, cxrs-1]这段栈的事务就不会被回收。[cxrs, cxrs+pxrs-1]这一段栈表示当前活跃的还没有提交的事务列表,当这部分事务提交时,cxrs会日后移动,最终到栈顶。
MVCC实现
1)写入操做
这里咱们认为写入操做包括三种,分别为insert / delete / commit 三种类型。对于insert和delete这两种类型的写入操做,只须要在LeafEntry的栈顶放置一个元素便可。 以下图所示:
对于commit操做,只需把LeafEntry的栈顶元素放到cxrs这个指针处,而后收缩栈顶指针便可。以下图所示:
2)读取操做
对读取操做而言, 数据库通常支持多个隔离级别。MySQL的InnoDB支持Read UnCommitted(RU)、Read REPEATABLE(RR)、Read Commited(RC)、SERIALIZABLE(S)。其中RU存在脏读的状况(脏读指读取到未提交的事务), RC/RR/RU存在幻读的状况(幻读通常指一个事务在更新时可能会更新到其余事务已经提交的记录)。
TokuDB一样支持上述4中隔离级别, 在源码实现时, ft-index将事务的读取操做按照事务隔离级别分红3类:
多版本记录回收
随着时间的推移,愈来愈多的老事务被提交,新事务开始执行。 在分形树中的LeafNode中commited的事务数量会愈来愈多,假设不千方百计把这些过时的事务记录清理掉的话,会形成BasementNode节点占用大量空间,也会形成TokuDB的数据文件存放大量无用的数据。 在TokuDB中, 清理这些过时事务的操做称之为垃圾回收(Garbage Collection)。 其实InnoDB也存在过时事务回收这么一个过程,InnoDB的同一个Key的多个版本的Value存放在undo log 页上, 当事务过时时, 后台有一个purge线程专门来复杂清理这些过时的事务,从而腾出undo log页给后面的事务使用, 这样能够控制undo log无限增加。
TokuDB存储引擎中没有相似于InnoDB的purge线程来负责清理过时事务,由于过时事务的清理都是在执行更新操做是顺便GC的。 也就是在Insert/Delete/Update这些操做执行时,都会判断如下当前的LeafEntry是否知足GC的条件, 若知足GC条件时,就删除LeafEntry中过时的事务, 从新整理LeafEntry 的内存空间。按照TokuDB源码的实现,GC分为两种类型:
总结
本文大体介绍了TokuDB事务的隔离性实现原理, 包括TokuDB的事务表示、分形树的LeafEntry的结构、MVCC的实现流程、多版本记录回收方式这些方面的内容。 TokuDB之全部没有undo log,就是由于分形树中的更新消息自己就记录了事务的记录版本。另外, TokuDB的过时事务回收也不须要像InnoDB那样专门开启一个后台线程异步回收,而是才用在更新操做执行的过程当中分摊回收。总之,因为TokuDB基于分形树之上实现事务,于是各方面的思路都有大的差别,这也是TokuDB团队的创新吧。
参考资料:
转自:https://www.biaodianfu.com/tokudb.html