下面的内容排名不分先后,是我的实践过程中总结下来的可以在一定程度上避坑的方法,但不一定适用于所有项目,请酌情考虑。阅读前请确保自己在一定程度上了解了数仓是什么,可以阅读我之前写过的文章。报表相关的系列文章请参考:
- 报表自动化: 商业智能背后的秘密
- 报表自动化: 打开数据仓库的大门
- 报表自动化: 没有压力的维度建模
- 报表自动化: 抓住时间流逝的瞬间
- 报表自动化: 薅出数字背后的价值
- 报表自动化: 事实、维度与指标的三方会谈
既然是干货分享,闲话少说,直接以清单形式列举呀:
通用内容
- 各个字段若为维度表 id,建议使用 dim_XXX_id 命名
否则: customer_id 、user_id 是维度表 id 么?还是业务里的 id?还是既是维度又是业务 id?
-
维度表原始数据非数字类型,且数量有限,建议主键自增,此时数据字段需增加 unique 约束
维度表如果原始的 id 本身就是数据类型,可以直接作为维度表主键。如果不是则需要创建自增主键作为“代理主键”
-
所有表一定要有主键,并根据搜索条件建立索引
所有被 where 判断的字段,被 join 选中的字段一定要增加索引,因为任何一步的操作都可能是面向多个表的所有数据量执行的,如果没有索引的 join 出现在两个 1 亿表之间,这将会是漫长的灾难。
-
根据业务需要确定 left join or inner join
inner 可以在匹配失败时直接过滤掉对应数据,而 left 无匹配内容会存成 null,需要根据业务来决定。
如果选择 left,那意味着此值有 null 的可能,后续 aggregate 时要注意 null 处理
-
指标一定要提成独立的指标表以供重用,不要对外公开一个视图
数据库的一切都是开放的,都是 public 的,永远也不会知道 public api 会被多少人调用,一个指标的是包含复杂计算逻辑,千万不要只公开一个视图,这样只能保证逻辑复用,但会带来不可控的重复计算的性能开销
-
一个计算过于复杂,考虑是否可以拆成多层指标
尤其是在出现:嵌入了对大表进行 group by 的复杂逻辑,那是否这个 group by 的结果就可以作为一个指标,然后调用指标结果即可,将复杂操作行为的计算结果尽量存储起来以供复用
-
从 OBS 得到维度表,再以维度表 id 建立事实表,尽量等到最后生成报表再根据维度表 id 解析为需要的字段(时间维度除外,见后)
这样保证中间层有关维度的内容可以一直只存储维度 id,即方便理解也维持了设计
-
如果考虑 ETL 迁移,ETL 工具的功能要尽量少用
如果在规模较小,且变动很大,甚至 ETL 工具都随时可能变动时,可以考虑用 view 承载逻辑,有数据库来完成所有逻辑及数据存储功能。
而 ETL 只做一个 view 到一个 table 的拷贝(持久化、固化功能),同时尽量保证对应的 view 与 table 字段名有规律,方便规则化的匹配上每个 table 的逻辑源头。
-
SQL 连表条件尽量使用主键或者有唯一索引的字段,以明确不会产生笛卡尔积
最怕的就是我们有 10000 万条数据,join 了一个有重复值的表,然后得出了 50000 万条数,算了很久还是错的
-
SQL 在 where 内不要进行函数调用,随着数据量的增加,where 里的函数调用会让人怀疑它死机了
-
公共数据逐层下沉到临界边界即可
在有复用时,找到复用双方最近的共用的源库,存到其中即可
-
改动表结构前请深思,尤其是提交 / 部署 / 发布的部分
加索引要考虑已有数据量,如果有 1 亿条数据,此时加个索引需要的时间是很长的,如果使用 flyway 等工具进行添加,也许你会认为卡住了。
除此以外,改字段要考虑 ETL 工具同步修改。
注意数据库的操作与后端的差异,对于后端我们是复杂的业务逻辑去处理一条数据,但对于数仓各种操作都在任何一个操作改变都是全表的
-
数据清洗很重要
数仓的计算实际上是很脆弱的,尤其是按照这种多层以来的关系,如果最底层的数据出现了问题,就算中间逻辑完全正确也会出现波及面很大的修复成本。所以建议在 OBS 层优先明确数据的规则,进行数据清洗。
-
不建议中大型数仓使用数据库的 view 承载逻辑,建议通过 ETL 工具承载各层之间计算关系
如果报表过于庞大、复杂,那么通过 view 承载逻辑,会由于 view 的存在将表捆绑到了一起,任何表结构修改都需要改动对应的 view,并逐层的去想应该懂,最后导致了不可控的波及范围。且因为 view 内不指定的表名、字段名等无法批量重构,进一步导致有 view 代码引起的大泥球的产生
-
增量计算需要认真的梳理有关时间的事情
注意业务时间有两类:用户行为发生的时间(事件时间)、预约等提前存储的未来某一个时间点。
除此以外还有个系统时间,即数据库对当条数据最后一次 update 的时间
如果做增量,明确确定每一层到底用什么时间进行增量过程的“增”的依据
-
在报表初次上线时,一定会有大量的历史数据出现,请在设计之初优先考虑如何灵活的处理历史数据与每天每一时刻不断新增的数据如何正确的处理。
历史数据可能极其庞大 ,如果与实时新增的数据同时计算会严重影响第一次计算的时间
历史数据如果想要先看到最近的时间的数据,比如想看到今年的数据以往的以后慢慢算,那么需要考虑计算有一个“逆序”的过程,比如先计算 2020 年再计算 2019 年,那么如果 2019 年算得结果有重复数据是否要覆盖呢?
-
无论在 DW 还是 DM,在进行 view 里的 join 或者 ETL 里的 join 时,join 的条件最好是左表的某个字段 = 右表的主键。这样我们在 select 里面使用的字段所在的表一定是通过表的主键串起来的,使用时可直接拿到字段内容,否则有可能触发获取字段时再进行一次查找,很可能还是非索引字段的查找,极大的降低效率。(也可以选择可看执行计划进行性能优化)
-
请给未来的报表留后路,不要只 focus 当前的报表,报表的重构 effort 极大
-
一个报表的字段一定要只属于一个领域,千万要避免融合报表的出现(将本应是多个报表的内容强行塞到一个表里)
比如在分析订单的时候大量的加入了账单的相关信息。这样子如果一个订单有结账、部分退账、换货、再退运费、再退货流程呢?那这样可能会一下显示五行数据,每一行第一项都是一个订单 id,对使用者来说体验很不好。
除此以外订单再开始还没有结账的时候是没有账单单号的,那么这个时候的报表里显示空?那等结账以后还需要考虑把这个空置补上,后续的退账等操作就不能去更新而是插入新数据,那到底何时 upsert 何时 insert?(如果有足够多的算力与空间,不做增量每次都全量计算时可以忽略此问题)
-
不建议中大型数仓使用 view 承载一切逻辑,尽量使用 ETL 将数据存储、数据运算解耦。这样也许 ETL 工具支持的各种语言工具,可以让我们复用一些逻辑代码。请提前思考业务逻辑与报表逻辑一致性的问题如何保证?
-
view 计算的数据源一定不可跨层, OBS、DW、DM 不在一个库
如果用 view 承载逻辑,那么要注意 view 无法跨层、跨库访问数据。
这时候为了计算 DW 层的表,可能 view 要放到 OBS 层,这样算力的提供者就成了 OBS 数据库。
当然另一种方案是先将用到的表 copy 到 DW 库,但是 DW 可能会用到所有 OBS 表,此时可以说是各种数据源的原始数据完整的存了两份。
所以在大型的复杂的项目中,将逻辑与数据解耦可能是一种比较好的方式,而不是将逻辑与数据都由 SQL 承载。
-
在报表设计之初用一段时间进行设计是个不错的选择,不要认为前期一堆人一起讨论设计是浪费时间的,
……
ETL 工具
- informatic 有缓存,table 字段和字段类型 / view 返回字段及类型,修改后,优先考虑删除 mapping重建多次出现在修改字段类型后,Informatica 刷新无法刷到新类型;以及增加新字段后,无法显示出来的问题。尤其是 SQLserver 在使用 varchar 中文乱码后更改为 nvarchar 后,刷新 Informatica 有时仍然会继续写入乱码的数据。
……更多见后续文章
数据库
- sql server 中所有字符串用 nvarchar,ntext(text 不要用),nchar。千万不要去掉 n,用少量的空间换来全方位的防止中文乱码、或者其他特殊符号乱码问题
- 尽量都是用可控制长度的格式,不要使用 text 类型,因为有些 ETL 工具无法做这种类型的处理
……
最后,如果你有其他的心得体会欢迎评论交流。比如我上述所写的原则可能带来的什么不良后果?有或者还有什么能避免其他坑的原则?
上面的内容还涉及到一些并未在以往文章提到的信息,后续会继续整理,感谢阅读,欢迎关注微信公众号:coologic
报表相关的系列文章请参考: