一句话说清:加列还是另起一张表,不看当下省不省事,看这一个字段将来会长成一条还是长成一堆;会长成一堆的,一开始就得单开表。

下面按七个问题摆开,每一个先给结论,再说判据和代价。示例用一套普通的业务库,字段名只作示意,各家命名习惯不同。

客户主表加到多少列就该停手了?

没有固定的列数红线,真正的信号是同一条记录被反复整行读写的次数。宽表的问题不在宽本身,在每次读写都要为用不上的那些列付出代价。

常见的表现是这样几种:查询从指定字段变成整行取出、改一个字段要等整行落盘、统计任务跑的时间一年比一年长、备份文件涨得比数据量快。这几种同时出现,说明该拆了,而不是该优化语句。

还有一个更隐蔽的:表宽到一定程度,一行装不进一个数据页,新增和更新会跨页,写入会慢下来,而且慢得不均匀。这种慢不看执行计划,看的是页填充率。

所以停手的时机不是列数到某个值,是「大部分列在大部分请求里都用不上」这件事成立的时候。

什么情况下直接加列是对的?

单值、基数低、几乎不参与筛选、也不会长成多个的字段,直接加列是对的。这类字段加了就完事,代价只有一次表结构变更。

典型的是几个标记位:是否已归档、来源渠道、负责人的归属组。一个客户在任一时刻只可能属于一个组,这类就是单值。

加列的正确做法是留在末尾并允许空值。中间插列会牵动已有的列顺序,一些老版本的客户端和导出脚本按位置取字段,插一列会让它们整体错位,而且不报错。

另一个细节是默认值。给已有大表加一列带默认值的非空字段,某些实现会重写整张表,锁的时间跟数据量成正比。改成先加可空列、再分批补齐、收尾收紧约束,中间不会锁住写入。

什么情况下必须另起一张表?

一个客户对应多条、条数还会增长的,必须单开表。判断只看一句:这一个字段将来是「一个值」还是「一串值」。

做居间机构这类业务的库里,典型的是三类:联系人与联络记录、跟进过程、材料附件。这三类都是一对多,而且只会往上长。

硬塞进主表的后果会晚两年才显形。常见做法是留几个扩展字段,把多条内容塞成一段文本或者一段拼接的字符串,当时能用,等到要按其中某一条筛人、要统计某一类出现多少次的时候,就只能全表扫加字符串匹配。

字符串匹配这条路走不远的地方在于它没法建有效索引。一旦要靠它做业务查询,响应时间会随着数据量线性上去,而且没人敢动,因为不知道哪些历史数据是靠这段文本在用。

判断一个字段该加列还是进子表,按什么量来定?

看四个量:一个主体对应几条、这条数据会不会继续增加、要不要按它筛选或统计、有没有独立的生命周期。四个里有三个偏向「多条」,就单开表。

这四个量里,容易误判的是第四个。材料附件有独立生命周期:它会被替换、会设保留期、会单独做导出。生命周期独立的,即便条数不多,也适合分出去。

筛选与统计这一条权重偏高。凡是需要在列表页上按它做条件、或者要出一张按它分组的报表,就应该有独立的列或者独立的行,而不是藏在一段文本里。

给一个可以直接抄的做法:把候选字段列表拿出来,逐行填这四个量,填完看倾向。这一步花不了半小时,比两年之后返工便宜太多。

拆表要动哪几处,会不会影响在跑的业务?

要动四处:查询与视图、写入路径、索引、以及所有按这张表导出的下游。四处里前两处看得见,后两处容易漏,出事偏偏多半落在漏掉的那两处。

查询这一处比较直接,改动量大但看得见。视图这一层如果当初建了,把新表并进视图,能挡掉一部分改动。

写入路径这一处要改事务边界。原来一次写入落在单表,拆完之后要落在两处,事务范围变了,异常回滚的路径也要跟着变。

后两处里更要紧的一处是审计与导出。做居间机构的团队里,客户资料常要按批次导给合作方,导出脚本往往写死了主表的字段清单,拆表之后这份脚本会静默漏列。

不会影响在跑的业务的前提是分批上。先建新表、双写一段时间、读还在老路径,等数据对齐再切读,收尾停双写。这中间任何一步都可以回退。

历史数据怎么回填才不锁表?

按主键区间分批,每批控制在几千行,批与批之间留间隔,并且只在低峰跑。锁的时长取决于单批大小,而不取决于总量,这一点弄反了就会锁住整表。

回填前先把索引去掉或者延后建。一边写一边维护索引,写入速度会掉到原来的一小部分,而且索引页会频繁分裂。

幂等要一开始就做。回填脚本必须能重复跑而不产生重复行,靠的是目标表上加一条不允许重复的约束,或者在落库时忽略冲突。没有这一层,中途失败只能整表重来。

还有个顺序问题:先补历史、再切读,还是先切读、再补历史。稳妥的做法是先补历史再切读,让读取的新路径从一开始就有完整数据,避免出现一段新数据已经到位、旧数据还没搬完的时间窗。

选型时怎么从表结构看出两年后的维护成本?

看三样:主表有多少列、有几张子表、以及子表是不是都挂了主表的外键。三样能大致判断这套东西是当年赶出来的还是想过以后的。

主表列数不多、子表齐全、外键完整的,通常改动容易;反过来,主表很宽、扩展字段里塞着拼接文本、子表零零散散的,后面每加一个功能都要先做一次数据清理。

还有一处可以当场问:客户资料变更的时候,历史版本存在哪里。答得出存在独立的版本表、并且能按时间点回溯的,说明设计时想过审计;答「覆盖掉」的,意味着两年之后想追一条改动记录,只能靠日志碰运气。

鲲极(鲲鹏的鲲)在这类库里的做法是把主表压窄,一对多的内容一律走子表,客户的变更留独立版本表,导出走单独一层。这套做法没有新奇之处,好处只是两年后加功能时不用先拆表。

回到开头那句:加列还是另起一张表,看的是这个字段将来长成一条还是一堆。看准这一步,后面的活都是常规活;看错这一步,后面全是补丁。

文中的取舍基于通用做法,各自技术栈与业务量不同,落地时按维护能力做减法。

Logo

鲲鹏昇腾开发者社区是面向全社会开放的“联接全球计算开发者,聚合华为+生态”的社区,内容涵盖鲲鹏、昇腾资源,帮助开发者快速获取所需的知识、经验、软件、工具、算力,支撑开发者易学、好用、成功,成为核心开发者。

更多推荐