如果你用过ClickHouse,大概率听过这条铁律:能不用FINAL就别用。这个建议流传了很多年,而且说实话,它曾经很有道理。老版本的ClickHouse里,FINAL查询的成本可能高得吓人——表越大、分区越多、碎片越乱、合并越滞后,查询就越慢。于是FINAL几乎成了性能问题的代名词,看到就想绕路走。
但现在的ClickHouse已经变了。FINAL的实现方式改进了太多,以至于我们可能需要重新聊聊这个话题。
先搞清楚FINAL到底是干什么的。在ReplacingMergeTree、CollapsingMergeTree、VersionedCollapsingMergeTree这些引擎里,ClickHouse不会当场覆盖旧数据。插入操作创建新数据片段,后台合并任务慢慢去重,去重是异步发生的。这意味着查询时你可能看到重复版本、旧版本、中间状态。FINAL的作用就是强制在查询执行时完成合并逻辑——代价是多读数据、多做去重、多消耗CPU和内存。这就是老版本劝退FINAL的根本原因。
以前的问题有多严重?分区太大、碎片太多、合并跟不上、查询扫描范围太广,任何一个因素都能让FINAL变成性能杀手。很多人不加思考地往查询里塞FINAL来"修复"重复数据,结果查询变慢、内存暴涨、开销失控。社区的建议于是变成:好好设计表结构,能不用就不用。
这条建议现在依然成立。但FINAL本身的实现已经脱胎换骨。
近几年的ClickHouse版本给FINAL加了一堆优化:并行执行、分区感知优化、内存行为改进、更聪明的合并策略、减少无效读取。FINAL不再是以前的性能怪兽了。甚至在一些最近的社区讨论里,用FINAL做最新状态查询已经不再被自动视为错误——搁几年前这话会很有争议。
很多人以前用argMax来绕过FINAL,比如:
SELECT id, argMax(status, version) FROM users GROUP BY id;
在老版本和大数据量场景下,这招确实常管用。但现在FINAL改进够大,两者的权衡已经不再一边倒。某些最新状态查询场景下,FINAL甚至可能更干净、更易维护。
当然,这不是说FINAL可以随便滥用。表设计依然是第一位的。但如果你的场景确实需要保证查询时的数据一致性,FINAL现在是个值得重新考虑的选项,而不是条件反射式地回避。
热门跟贴