SQLServer的列存更新方案

最近读了一下 Microsoft 在 2015 年 VLDB 上发表的论文 《Real-Time Analytical Processing with SQL Server》[1]。SQLServer算是业内较早实现并落地 HTAP 行列更新方案的产品,我其实觉得 行存(Row-Wise Index) 在OLTP场景下的各种设计以及取舍,大家都已经讨论的非常清楚了。对于高效率的 毫秒级列存更新 方案,业界有一些方案设计和实现,典型的如Kudu[2]、Positional Delta Tree[3] 等,但远不如行存讨论的多。我仔细的 Review 了一下 SQLServer 的技术方案,觉得他们的设计还是颇具参考意义。本文分享一下我对这篇文章的一些看法。 论文主要讲了 4 个方面的话题: 内存中实现列存表; 对于行存表,怎么实现一个列存索引结构,从而实现分析型 SQL 的极大提速。这部分的重点就是怎么实现列存二级索引的实时更新; 对于列存表怎么实现一个 B-Tree 的二级索引,使得这个列存表可以用来做点查和小范围扫描; 计算层在列存表的扫描性能上做了哪些优化。 第1部分纯内存的列存表相对比较简单,第4部分的列存表扫描在业界的很多论文中都可以找到类似的方案。第2部分和第3部分相对比较独特,可以说是整篇文章的精华,也是我比较感兴趣的部分。 其实第2部分,可以认为是在一个已经存在的OLTP表上,怎么来创建一个OLAP的列存索引,使得这个表可以跑 “TP为主,AP为辅” 的两种QUERY;而第3部分正好完全相反,相当于是在一个纯粹的OLAP列存表之上,创建一个适合做 点查 和 小范围 查询的行存索引,使得这个表可以完成 “AP为主,TP为辅” 的混合QUERY。 “TP为主,AP为辅“ 的索引方案 SQL Server的这种 CSI (Columnar Store Index)更新方案,说实话其实在 写入延迟 和 读取延迟 上处理的还算比较恰当。对于列存索引来说,如果是 INSERT 的话,那么直接 Append 到 Delta Store 里面就行,没啥其他的额外耗时操作;如果是 DELETE 的话,先看一下 DELETE 的 key 是否在 Delta Store 里面,如果在就直接删除即可;如果不在,就插入一条 Delete Marker 到这个由 B-Tree 索引实现的 Delete Buffer。无论是 INSERT 还是 DELETE 操作,都没有特别耗时的地方。可能往 B-Tree 索引实现的 Delete Buffer 里面写 Delete Marker 会有点儿耗时,但这个 Delete Buffer 一般都是最近更新比较热的数据。首先体量不会特别大,其次,大部分的都是热数据,都会被 Cache 到内存。然后,后台有一个任务, 会默默地把 Delete Buffer 里面的 Delete 操作都转换成 Delete Bitmap。...

July 1, 2022 · 3 min · 557 words · Me

TokuDB的多版本并发控制(MVCC)

本文讲讲TokuDB事务的隔离性,在源码实现中,比较复杂。为了便于描述,本文只对最关键的内容进行描述,对细节的东西略过。 背景介绍 在传统的关系型数据库(例如Oracle, MySQL, SQLServer)中,事务可以说是研发和讨论最核心内容。而事务最核心的性质就是ACID。 其中 A表示事务的原子性,也就是组成事务的所有子任务只有两种结果: 要么随着事务的提交,所有子任务都成功执行;要么随着事务的回滚,所有子任务都撤销。 C表示一致性,也就是无论事务提交或者回滚,都不能破坏数据的一致性约束,这些一致性约束包括键值唯一约束、键值关联关系约束等。I表示隔离性,隔离性一般是针对多个并发事务而言的,也就是在同一个时间点,t1事务和t2事务读取的数据应该是隔离的,这两个事务就好像进了同一酒店的两间房间一样,各自在各自的房间里面活动,他们相互之间并不能看到各自在干嘛。D表示持久性,这个性质保证了一个事务一旦承诺用户成功提交,那么即便是后继数据库进程crash或者操作系统crash,只要磁盘数据没坏,那么下次启动数据库后,这个事务的执行结果仍然可以读取到。 TokuDB目前完全支持事务的ACID。 从实现上看, 由于TokuDB采用的分形树作为索引,而InnoDB采用B+树作为索引结构,因而TokuDB在事务的实现上和InnoDB有很大不同。 本文主要讲讲TokuDB的事务隔离性的实现,也就是常提到的多版本并发控制(MVCC)。在InnoDB中, 设计了redo和undo两种日志,redo存放页的物理修改日志,用来保证事务的持久性; undo存放事务的逻辑修改日志,它实际存放了一条记录在多个并发事务下的多个版本,用来实现事务的隔离性(MVCC)和回滚操作。 由于TokuDB的分形树采用消息传递的方式来做增删改更新操作,一条消息就是事务对该记录修改的一个版本,因此,在TokuDB源码实现中,并没有额外的undo-log的概念和实现,取而代之的是一条记录多条消息的管理机制。虽然一条记录多条消息的方式可以实现事务的MVCC,却无法解决事务回滚的问题,因此TokuDB额外设计了tokudb.rollback这个日志文件来做帮助实现事务回滚。 TokuDB的事务表示 在tokudb中, 在用户执行的一个事务,具体到存储引擎层面会被拆开成许多个小事务(这种小事务记为txn)。 例如用户执行这样一个事务: begin; insert into hello set id = 1, value = '1'; commit; 对应到TokuDB存储引擎的redo-log中的记录为: 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 对应的事务树如下图所示:...

December 13, 2015 · 2 min · 349 words · Me