乐趣区

关于后端:宽表为什么横行

宽表在 BI 业务中亘古未有,每次建设 BI 零碎时首先要做的就是筹备宽表。有时零碎中的宽表可能会有上千个字段,常常因为“过宽”超过了数据库表字段数量限度还要再拆分。

为什么大家乐此不疲地造宽表呢?次要起因有两个。

一是为了 进步查问性能。古代 BI 通常应用关系数据库作为后盾,而 SQL 通常应用的 HASH JOIN 算法,在关联表数量和关联层级变多的时候,计算性能会急剧下降,有七八个表三四层级关联时就能察看到这个景象,而 BI 业务中的关联复杂度远远超过这个规模,间接应用 SQL 的 JOIN 就无奈达到前端立等可取的查问须要了。为了防止关联带来的性能问题,就要先将关联打消,行将多表当时关联好采纳单表存储(也就是宽表),再查问的时候就能够不必再关联,从而达到晋升查问性能的目标。

二是为了 升高业务难度。因为多表关联尤其是简单关联在 BI 前端很难表白和应用。如果采纳主动关联(依据字段类型等信息匹配)当遇到同维字段(如一个表有 2 个以上地区字段)时会“晕掉”不晓得该关联哪个,表间循环关联或自关联的状况也无奈解决;如果将泛滥表凋谢给用户来自行抉择关联,因为业务用户无奈了解表间关系而简直没有可用性;分步关联能够形容简单的关联需要,但一旦前一步出错就要推倒重来。所以,无论采纳何种形式,工程实现和用户应用都很麻烦。然而基于单表来做就会简略很多,业务用户应用时没有什么阻碍,因而将多表组织成宽表就成了“自然而然”的事件。

不过,凡事都有两面性,咱们看到宽表益处而大量利用的同时,其毛病也不容忽视,有些毛病会对利用产生极大影响。上面来看一下。

宽表的毛病

数据冗余容量大

宽表不合乎范式要求,将多个表合并成一个表会存在大量冗余数据,冗余水平跟原表数据量和表间关系无关,通常如果存在多层外键表,其冗余水平会呈指数级回升。大量数据冗余不仅会带来存储上的压力(多个表组合进去的宽表数量可能十分多)造成数据库容量问题,在查问计算时因为大量冗余数据参加运算还会影响计算性能,导致尽管用了宽表但依然查问很慢。

数据谬误

因为宽表不合乎三范式要求,数据存储时可能呈现一致性谬误(脏写)。比方同一个销售员在不同记录中可能存储了不同的性别,同一个供应商在不同记录中的所在地可能呈现矛盾。基于这样的数据做剖析后果显然不对,而这种谬误十分荫蔽很难被发现。

另外,如果构建的宽表不合理还会呈现汇总谬误。比方基于一对多的 A 表和 B 表构建宽表,如果 A 中有计算指标(如金额),在宽表中就会反复,基于反复的指标再汇总就会呈现谬误。

灵活性差

宽表实质上是一种按需建模的伎俩,依据业务需要来构建宽表(尽管实践上能够把所有表的组合都造成宽表,但这只存在于实践上,如果要实际操作会发现须要的存储空间大到齐全无奈承受的水平),这就呈现了一个矛盾:BI 零碎建设的初衷次要是为了满足业务灵便查问的须要,即当时并不知道业务需要,有些查问是在业务发展过程中逐步催生进去的,有些是业务用户长期起意的查问,这种灵便多变的需要采纳宽表这种要当时加工的解决办法极为矛盾,想要取得宽表的好就得就义灵活性,堪称鱼与熊掌不可兼得。

可用性问题

除了以上问题,宽表因为字段过多还会引起可用性低的问题。一个事实表会对应多个维表,维表又有维表,而且表之间还可能存在自关联 / 循环关联的状况,这种构造在数据库系统中很常见,基于这些构造的表构建宽表,尤其要表白多个层级的时候,宽表字段数量会急剧减少,常常可能达到成千盈百个(有的数据库表有字段数量限度,这时又要横向分表),试想一下,在用户接入界面如果呈现上千个字段要怎么用?这就是宽表带来的可用性差的问题。

总体来看,宽表的害处在很多场景中常常要大于益处,那为什么宽表还大量横行呢?

因为没方法。始终没有比宽表更好的计划来解决后面提到的查问性能和业务难度的问题。其实只有解决这两个问题,宽表就能够不必,由宽表产生的各类问题也就解决了。

SPL+DQL 毁灭宽表

借助开源集算器 SPL 能够实现这个指标。

SPL(Structured Process Language)是一个开源结构化数据计算引擎,自身提供了不依赖数据库的弱小计算能力,SPL 内置了很多高性能算法,尤其是对关联运算做了优化,对不同的关联场景采纳不同的伎俩,能够大幅晋升关联性能,从而不必宽表也能实时关联以满足多维分析时效性的须要。同时,SPL 还提供了高性能存储,配合高效算法能够进一步施展性能劣势。

只有高性能还不够,SPL 原生的计算语法不适宜多维分析利用接入(生成 SPL 语句对 BI 零碎革新较大)。目前大部分多维分析前端都是基于 SQL 开发的,但 SQL 体系(不必宽表时)在形容简单关联计算上又很艰难,基于这样的起因,SPL 设计了专门的类 SQL 查问语法 DQL(Dimensional Query Language)用于构建语义层。前端生成 DQL 语句,DQL Server 将其转换成 SPL 语句,再基于 SPL 计算引擎和存储引擎实现查问返回给前端,实现全链路 BI 查问。须要留神的是,SPL 只作为计算引擎存在,前端界面仍要由用户自行实现(或选用相应产品)。

SPL:关联实现技术

SPL 如何不必宽表也能实现实时关联以满足性能要求的指标?

在 BI 业务中绝大部分的 JOIN 都是等值 JOIN,也就是关联条件为等式的 JOIN。SPL 把等值关联分为外键关联和主键关联。外键关联 是指用一个表的非主键字段,去关联另一个表的主键,前者称为事实表,后者称为维表,两个表是多对一的关系,比方订单表和客户表。主键关联 是指用一个表的主键关联另一个表的主键或局部主键,比方客户表和 VIP 客户表(一对一)、订单表和订单明细表(一对多)。

这两类 JOIN 都波及到主键,如果充分利用这个特色采纳不同的算法,就能够实现高性能的实时关联了。

不过很遗憾,SQL 对 JOIN 的定义并不波及主键,只是两个表做笛卡尔积后再按某种条件过滤。这个定义很简略也很宽泛,简直能够形容所有。然而,如果严格按这个定义去实现 JOIN,实践上没方法在计算时利用主键的特色来进步性能,只能是工程上做些无限的优化,在状况较简单时(表多且档次多)常常有效。

SPL 扭转了 JOIN 的定义,针对这两类 JOIN 别离解决,就能够利用主键的特色来缩小运算量,从而进步计算性能。

外键关联

和 SQL 不同,SPL 中明确地区分了维表和事实表。BI 零碎中的维表都通常不大,能够当时读入内存建设索引,这样在关联时能够少计算一半的 HASH 值。

对于多层维表(维表还有维表的状况)还能够用 外键地址化 的技术做好 预关联。行将维表(本表)的外键字段值转换成对应维表(外键表)记录的地址。这样被关联的维表数据能够间接用地址取出而不用再进行 HASH 值计算和比对,多层维表仅仅是多个按地址取值的工夫,和单层维表时的关联性能根本相当。

相似的,如果事实表也不大能够全副读入内存时,也能够通过预关联的形式解决事实表与维表的关联问题,晋升关联效率。

预关联能够在系统启动时一次性读入并做好,当前间接应用即可。

当事实表较大无奈全内存时,SPL 提供了 外键序号化 办法:将事实表中的外键字段值转换为维表对应记录的序号。关联计算时,用序号取出对应维表记录,这样能够取得和外键地址化相似的成果,同样能防止 HASH 值的计算和比对,大幅晋升关联性能。

主键关联

有的事实表还有明细表,比方订单和订单明细,二者通过主键和局部主键进行关联,前者作为主表后者作为子表(还有通过全副主键关联的称为同维表,能够看做奴才表的特例)。奴才表都是事实表,波及的数据量都比拟大。

SPL 为此采纳了 有序归并 办法:事后将外存表依照主键有序存储,关联时程序取出数据做归并,不须要产生长期缓存,只用很小的内存就能够实现计算。而 SQL 采纳的 HASH 分堆算法复杂度较高,不仅要计算 HASH 值进行比照,还会产生长期缓存的读写动作,运算性能很差。

HASH 分堆技术实现并行艰难,多线程要同时向某个分堆缓存数据,造成共享资源抵触;某个分堆关联时又会生产大量内存,无奈施行较大的并行数量。而有序归则易于分段并行。数据有序时,子表就能够依据主表键值进行同步对齐分段以保障正确性,无需缓存,且因为占用内存很少能够采纳较大的并行数,从而取得更高性能。

事后排序的老本虽高,然而一次性做好即可,当前就总能应用归并算法实现 JOIN,性能能够进步很多。同时,SPL 也提供了在有追加数据时依然保持数据整体有序的计划。

对于奴才表关联 SPL 还能够采纳更无效的存储模式将奴才表一体化存储,子表作为主表的汇合字段,其取值是由与该主表数据相干的多条子表记录形成。这相当于事后实现了关联,再计算时间接取数计算即可,不须要比对,存储量也更少,性能更高。

存储机制

高性能离不开无效的存储。SPL 也提供了 列式存储 ,在 BI 计算中能够大幅升高数据读取量以晋升读取效率。SPL 列存采纳了独有的 倍增分段 技术,绝对传统列存分块并行计划要在很大数据量时(否则并行会受到限制)才会发挥优势不同,这个技术能够使 SPL 列存在数据量不很大时也能取得良好的并行分段成果,充分发挥并行劣势。

SPL 还提供了针对数据类型的优化机制,能够显著晋升多维分析中的切片运算性能。比方将枚举型维度转换成整数,在查问时将切片条件转换成布尔值形成的对位序列,在比拟时就能够间接从序列指定地位取出切片判断后果。还有将多个标签维度(取值是或否的维度,这种维度在多维分析中大量存在)存储在一个整数字段中的 标签位维度 技术(一个整数字段能够存储 16 个标签),不仅大幅缩小存储量,在计算时还能够针对多个标签同时做按位计算从而大幅晋升计算性能。

有了这些高效机制当前,咱们就能够在 BI 剖析中不再应用宽表,转而基于 SPL 存储和算法做实时关联,性能比宽表还更高(没有冗余数据读取量更小,更快)。

不过,只有这些还不够,SPL 原生语法还不适宜 BI 前端间接拜访,这就须要适宜的语义转换技术,通过适宜的形式将用户操作转换成 SPL 语法进行查问。

这就须要 DQL 了。

DQL:关联形容技术

DQL 是 SPL 之上的语义层构建工具,在这一层实现对于 SPL 数据关联关系的形容(建模)再为下层应用服务。行将 SPL 存储映射成 DQL 表,再基于表来形容数据关联关系。

通过对数据表关系形容当前造成了一种以维度为核心的总线式构造(不同于 E - R 图中的网状结构),两头是维度,表与表之间不间接相干都通过维度过渡。

基于这种构造下的关联查问(DQL 语句)会很好表白。比方要依据订单表(orders)、客户表(customer)、销售员表(employee)以及城市表(city)查问:本年度华东的销售人员,在全国各销售区的销售额

用 SQL 写起来是这样的:

SELECT
 ct1.area,o.emp_id,sum(o.amount) somt
FROM
 orders o
 JOIN customer c ON o.cus_id = c.cus_id
 JOIN city ct1 ON c.city_id = ct1.city_id
 JOIN employee e ON o.emp_id = e.emp_id
 JOIN city ct2 ON e.city_id = ct2.city_id
WHERE
 ct2.area = 'east' AND year(o.order_date)= 2022
GROUP BY
 ct1.area,  o.emp_id

多个表关联要 JOIN 屡次,同一个地区表要重复关联两次能力查到销售员和客户的所在区域,对于这种状况 BI 前端表白起来会很吃力,如果将关联凋谢进去,用户又很难了解。

那么 DQL 是怎么解决的呢?

DQL 写法:

SELECT
 cus_id.city_id.area,emp_id,sum(amount) somt
FROM
 orders
WHERE
 emp_id.city_id.area == "east" AND year(order_date)== 2022
BY
 cus_id.city_id.area,emp_id

DQL 不须要 JOIN 多个表,只基于 orders 单表查问就能够了,外键指向表的字段当成属性间接应用,有多少层都能够援用上来,很好表白。像查问客户所在地区通过 cus_id.city_id.area 始终写下去就能够了,这样就打消了关联,将多表关联查问转化成单表查问。

更进一步,咱们再基于 DQL 开发 BI 前端界面就很容易,比方能够做成这样:

用树结构分多级表白多层维表关联,这样的多维分析页面不仅容易开发,一般业务用户应用时也很容易了解,这就是 DQL 的效劳。

总结一下,宽表的目标是为了解决 BI 查问性能和前端工程实现问题,而宽表会带来数据冗余和灵活性差等问题。通过 SPL 的实时关联技术与高效存储能够解决性能问题,而且性能比宽表更高,同时不存在数据冗余,存储空间也更小(压缩);DQL 构建的语义层解决了多维分析前端工程的实现问题,让实时关联成为可能,,灵活性更高(不再局限于宽表的按需建模),界面也更容易实现,利用范畴更广。

SPL+DQL 继承(超过)宽表的长处同时改善其毛病,这才是 BI 该有的样子。

SPL 材料

  • SPL 下载
  • SPL 源代码
退出移动版