predicate 列存储索引扫描_SIGMOD'10 | SQL Server Column Store Indexes

概述

SQL Server 作为一个通用数据库,一直以来都是行存。本论文说的是 SQL Server 11(代号 Denali)中引入的 Column Store Index (列存索引)功能。

从名字上可以看出,这是一种新的 Index 类型,就像 B 树索引那样,我们也可以给一个表上的一组列建立列存索引。下面这个语句给 catalog_sales 表的所有 34 个列建立了列存索引:

CREATE COLUMNSTORE INDEX cstore on catalog_sales (

cs_sold_date_sk, cs_sold_time_sk, ···

···,cs_net_paid_inc_ship_tax, cs_net_profit)

有了这个索引之后,执行以下查询(这是个典型的 star join)的性能大幅提高:

select w_city, w_state, d_year,

SUM(cs_sales_price) as cs_sales_price

from warehouse, catalog_sales, date_dim

where w_warehouse_sk = cs_warehouse_sk

and cs_sold_date_sk = d_date_sk

and w_state = 'SD'

and d_year = 2002

group by w_city, w_state, d_year

order by d_year, w_state, w_city;

存储结构

列存索引的构建过程如下,将大表切成 Row Group,然后分别对每个 Row Group 内部的列构建出索引 segment,这些 segment 用了 SQL Server 原本就支持的 BLOB 方式存储,可以保证 scan 的时候不会频繁地 page break。

图中的 Encode、Compress 技术也跟其他列存大同小异。

对于数字等类型,可以用 value-based encoding,其实就是尝试将原来的值减去 base 除以 expnent 映射到 32/64 位整数上。压缩效果取决于数据是否规整。

对于其他类型用 dictionary-based encoding,为每一个 unique 的数据分配一个 data_id,把这个映射关系(字典)存下来,此后只要保存每行的 data_id 即可。

Compress 方面有 Run-length Encoding (RLE),如果数据有很多相邻的相同值,RLE 效果很好,反之则没有什么效果。这里用了MS自家的一个算法,把一个 row group 内的行重新排序(因为不care顺序),使得各列的RLE压缩效果最大化。

论文上提到的这些设计并不支持 update,当主表内容变化时,需要手动重建才行。

查询执行

不出意外的:列存索引的 IndexScan 支持下推 predicate 以及 bitmap(可能由 HashJoin 生成)。此外,还有 delayed materialization(延迟物化)、SIMD 指令集等支持。

SQL Server 本身用的是 tuple-at-a-time 的经典 volcano 模型,但是这样列存的优势无法完全发挥出来:只有 scan 的时候勉强能用上,内存中计算的时候数据又变回一行一行的,对于上层的 filter、join 等算子享受不到 vectorize 加速。

所以 SQL Server 为了配合列存索引还做了 batch-at-a-time 的一套新的执行算子,这套算子是融入到现有执行引擎中的,一个 plan 中可以既有这些 batch 的算子也有原来的算子。优化器会决定 plan 中的哪些部分使用 batch 算子。

查询优化

哪些情况下要使用列存索引呢?很简单,就像 Access Path Selection 那篇文章中提到的,通过估算 CPU 和 IO 代价。对于点查来说,由于每个 Row Group 内部是乱序的,为了找到某一行要顺序扫描整个 Row Group,显然不如 B 树索引

对于 Scan 量很大的(例如没有其他 B 树索引能用),则扫描列的 IO 更小(有压缩、编码更紧凑)、CPU 代价更低(vectorize 读更快),所以此时列存索引 IndexScan 的代价更低

第二个问题是,如何确定 plan 中哪些部分用 batch 算子?

将 batch 数据恢复成 row 是需要额外代价的。借鉴 volcano 优化器框架,我们将 batch 与 row 看作一种 physical property:所有 batch 算子都要求它的子节点是 batch 的,所有 row 算子都要求它的子节点是 row 的。PS. SQL Server 中的 HashJoinBuild 是独立的一个算子,所以 batch 的 HashJoin 只会要求 probe-side 是 batch 的。

本文还提出了一个 snowflake 模型下启发式地构建 join order 的算法。这个算法会尽可能将 dimension table 作为 HashJoin 的 build side,从而构建出 bitmap 下推给 fact table 的 Index Scan(假设含有列式索引),减少 Index Scan 输出的数据量。

效果

以下面的查询为例:

select i_brand, count(*)

from catalog_sales, date_dim, item

where cs_sold_date_sk = d_date_sk

and cs_item_sk = i_item_sk

and d_year > 2001

group by i_brand

输出的执行计划如下:

其中:执行过程是首先用两个 dimension table(item、data_dim) 做过滤并 build 哈希表,然后将它的 bloom filter 下推给 fact table (catalog_sales) 做提前过滤

大部分算子都走了 batched 模式,只有最顶上的 Sort 等算子是 row 模式

速度是原来的 16.4 倍(warm buffer pool)或 12.3 倍(cold buffer pool)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值