往MergeTree、ReplacingMergeTree、SummingMergeTree里写入同样的重复数据,对比它们在OPTIMIZE FINAL前后的差异,顺带验证SELECT...FINAL和物理合并到底是不是一回事。
同一个 key(k=1)写3次不同的值,同一个 key(k=2)写1次,分别插入普通 MergeTree、ReplacingMergeTree、SummingMergeTree。
插入(每张表用同一份数据)INSERT INTO demo.mt_plain VALUES (1,100,1),(1,200,2),(1,300,3),(2,50,1) INSERT INTO demo.mt_replacing VALUES (1,100,1),(1,200,2),(1,300,3),(2,50,1) INSERT INTO demo.mt_summing VALUES (1,100),(1,200),(1,300),(2,50)
三张表引擎不同,但收到的原始数据完全一样:k=1 被写了3次不同的值,k=2 只写了1次。
对3张表都执行 OPTIMIZE TABLE ... FINAL,强制触发一次彻底合并,再看结果。
OPTIMIZE TABLE ... FINAL 之后,查询结果-- mt_plain(普通MergeTree,什么都不做) 1 100 1 1 200 2 1 300 3 2 50 1 -- mt_replacing(ReplacingMergeTree(ver),按ver取最大版本) 1 300 3 2 50 1 -- mt_summing(SummingMergeTree(),按主键之外的数值列求和) 1 600 2 50
普通 MergeTree 老老实实保留了全部4行,一行都没少——它压根不做去重。ReplacingMergeTree 只留下了 k=1 里 ver 最大的那一行(300,3),把另外两行丢弃了。SummingMergeTree 则把 k=1 的3个值加总成了600,一行代表一个key。
如果3条重复数据是在同一条 INSERT 语句里一起写进去的,会怎样?
同一条INSERT里的3行重复key,立刻查询(还没有OPTIMIZE、没有等待合并)INSERT INTO demo.mt_replacing2 VALUES (1,100,1),(1,200,2),(1,300,3),(2,50,1) SELECT * FROM demo.mt_replacing2 → 立刻只有2行:(1,300,3) 和 (2,50,1) SELECT name, rows, level FROM system.parts → all_1_1_1,level=1(不是刚插入该有的level=0)
同一条 INSERT 语句里的重复 key,在写入的那一刻就已经被去重了,连一次真正的"合并"都没等——因为一次 INSERT 本身在落盘前就要经过跟合并同一套排序、折叠的处理逻辑。但如果把这3行拆成3条独立的 INSERT 语句分别写入,结果完全不同。
3次独立INSERT,同一个key,立刻查询INSERT ... VALUES (1,100,1) INSERT ... VALUES (1,200,2) INSERT ... VALUES (1,300,3) SELECT * FROM demo.mt_replacing3 → 3行全在:100、200、300 都还在 SELECT name, rows, level FROM system.parts → 3个part,各自level=1,谁都没被合并
这次3行都留着——因为它们分散在3个不同的 part 里,还没有发生任何合并,ReplacingMergeTree 的去重逻辑只在合并这个动作里生效,不会跨 part 主动去重。
对上面那3个还没合并的part,直接在查询里加 FINAL,会怎样?
对比SELECT * FROM demo.mt_replacing3 FINAL → 只返回1行:(1,300,3) -- 但这时候system.parts依然是3个独立part,物理上什么都没变 SELECT name, rows, level FROM system.parts WHERE table='mt_replacing3' → 还是3个part(此时未执行OPTIMIZE) -- 执行 OPTIMIZE TABLE ... FINAL 之后 SELECT name, rows, level FROM system.parts WHERE table='mt_replacing3' → 变成1个part:all_1_3_2,level=2,只有1行
SELECT ... FINAL 是在查询这一刻临时把结果算对(不改变磁盘上的任何东西,每次查询都要重新做一遍这个临时合并,有的额外开销);OPTIMIZE TABLE ... FINAL 才是真正把磁盘上的 part 物理合并成一份,之后不用再临时处理。这是两件完全不同的事,容易被名字里都带着"FINAL"搞混。
每一步都是本文上面建表、插入、查询得到的原始结果。
OPTIMIZE TABLE ... FINAL:强制物理合并SELECT ... FINAL:查询时临时合并,不改变磁盘system.parts:核对part数量与level