在DB2的多节点分区数据库(DPF)环境中,数据按照分布键的哈希值被散列到各个分区上,理想状态下每个节点的数据量和负载应当大致均衡。但现实中经常会遇到这样的现象:某个节点的CPU占用率长期在90%以上,而其他节点却只有10%左右;一条查询语句的执行时间远超预期,执行计划显示大部分时间耗在单个分区上。这些现象背后的元凶通常就是数据倾斜。数据倾斜不仅拖慢单个查询,还会让整个集群的并行能力大打折扣,相当于花钱买了多台服务器却只用了一台的算力。

如何准确定位哪些表和数据出现了倾斜
处理倾斜的第一步是发现问题所在。DB2提供了多个工具可以从不同维度观察数据分布情况。最直接的方法是查询系统目录视图,比较表在各分区上的行数分布。通过SYSIBM.SYSTABLESPACES配合管理视图ADMIN_GET_TAB_INFO,可以获得每个数据分区上表占用的行数和空间大小。如果某个分区上的行数明显高出其他分区数倍,倾斜就基本可以确认了。
另一个常用手段是使用db2pd命令配合表分布统计信息。对表执行RUNSTATS时加上WITH DISTRIBUTION子句,DB2会收集列值的频率分布和分位数统计,优化器据此判断谓词的选择性。如果某一列的值高度集中在少数几个取值上,比如订单表中80%的记录状态都是同一个值,那么基于该列的过滤条件会让大量工作压到持有这些数据的分区上。
-- 收集表的分布统计信息,便于分析列值分布
RUNSTATS ON TABLE sales.orders
ON ALL COLUMNS WITH DISTRIBUTION ON ALL COLUMNS
AND DETAILED INDEXES ALL;
-- 查看表在各分区的行数分布情况
SELECT DATAPARTITIONNAME, CARD
FROM SYSIBM.SYSDATAPARTITIONS
WHERE TABNAME = 'ORDERS';
-- 使用管理视图检查每个分区成员的数据量
SELECT TABNAME, TABSCHEMA, ROWS_PER_PARTITION
FROM TABLE(SYSPROC.ADMIN_GET_TAB_INFO('SALES', 'ORDERS'));除了静态的数据量对比,动态运行时的倾斜更值得警惕。在哈希连接或分组操作中,某个键值出现频率极高时,负责处理该键值对应数据的分区会成为整个并行执行链条的瓶颈,其他分区干完活只能空等。这时候可以借助EXPLAIN查看执行计划中各分区的预估基数,结合db2pd -stack和监控表函数MON_GET_ACTIVITY分析语句在各分区上的实际耗时,定位到具体是哪一步、哪一列引发了运行时倾斜。
分布键选择不当是倾斜的主要根源
DPF环境下表按分布键做哈希取模,把行分散到各分区。哈希算法本身是均匀的,倾斜几乎都源于分布键本身选得不好。典型的问题场景包括:选择了取值种类极少的列作为分布键,比如性别、状态标志位,值种类只有两三个时,再好的哈希也无法均匀;选择了严重偏斜的列,比如绝大部分业务集中在少数几个地区或几个大客户上,以客户ID或地区码做分布键必然造成某些分区数据暴涨;选择了NULL值极多的列,NULL会被散列到同一个位置,也会形成局部堆积。
分布键的选择需要在数据均匀性和查询局部性之间做权衡。理想的分布键应当满足三个条件:基数足够高、值分布均匀、并且经常出现在连接条件和等值过滤条件中。把经常用于JOIN的列作为分布键,可以让连接的两个表在同一分区上完成本地连接(co-located join),避免跨节点的数据搬运。反之,如果为了均匀性选了一个查询中几乎不用的随机列,虽然数据分散了,但大量的JOIN和聚合都需要在节点间重新分发数据,网络开销会吃掉并行带来的收益。
-- 创建表时指定分布键
CREATE TABLE sales.orders (
order_id BIGINT NOT NULL,
customer_id BIGINT NOT NULL,
order_status SMALLINT,
order_amount DECIMAL(12,2),
create_time TIMESTAMP
)
DISTRIBUTE BY HASH(customer_id)
IN ts_orders;
-- 查看已有表的分布键定义
SELECT TABNAME, COLNAME, COLSEQ
FROM SYSCAT.KEYCOLUSE
WHERE CONSTNAME LIKE 'HASH%'
AND TABNAME = 'ORDERS';有一个常见误区值得提醒:有人认为把自增主键作为分布键就一定均匀。实际上如果主键是单调递增的序列号,虽然哈希取模后理论上还是均匀的,但范围查询和按时间扫描的语句会命中所有分区,无法利用分区裁剪。而如果用的是IDENTITY列配合默认的排序方式,某些版本下还会出现数据写入热点。因此分布键不是简单地选唯一列,而是要结合数据画像和查询模式综合判断。
倾斜发生后有哪些可落地的处理方案
确认了倾斜原因之后,处理思路大致分为三类:调整分布策略、改造数据本身、以及在应用层缓解。调整分布策略最彻底的办法是重建分布键。DB2的REDISTRIBUTE DATABASE PARTITION GROUP命令可以对表进行在线或离线重分布,把数据按新的分布键重新散列到各分区。需要注意重分布是一个代价较高的操作,会消耗大量日志空间和临时表空间,建议在业务低峰期执行,并提前评估停机窗口或采用在线模式。
-- 对表执行重分布,更换分布键为新的列 REDISTRIBUTE TABLESPACE ts_orders UNIFORM; -- 也可以通过导出导入方式重建表结构后更换分布键 EXPORT TO orders.del OF DEL SELECT * FROM sales.orders; CREATE TABLE sales.orders_new (...) DISTRIBUTE BY HASH(order_id) IN ts_orders_new; IMPORT FROM orders.del OF DEL INSERT INTO sales.orders_new; -- 最后重命名替换旧表并重新收集统计信息 RUNSTATS ON TABLE sales.orders_new ON ALL COLUMNS WITH DISTRIBUTION;
如果分布键受限于业务无法更改,可以考虑对高度偏斜的值做特殊处理。一种做法是在表上针对频繁出现的值建立物化查询表(MQT),把大值对应的数据单独存放和优化;另一种做法是在分区表内部再按某个低偏斜的列做二级分区,把大分区的数据进一步打散。对于多维度分析类查询,使用随机分布(DISTRIBUTE BY RANDOM)也是一个选项,它不依赖任何列的值,直接把行轮流分散到各分区,适合没有明显JOIN键的全表扫描场景,但代价是所有JOIN都会退化为再分布操作。
应用层的缓解手段同样重要。对于运行时倾斜,可以在SQL改写上想办法:把GROUP BY或JOIN中偏斜严重的键值拆出来单独处理,其余数据正常并行执行,最后合并结果,这就是所谓的偏斜连接优化思路。DB2 9.5之后优化器对某些场景自带了倾斜感知能力,保持统计信息足够新鲜(尤其是WITH DISTRIBUTION的详细统计)能让优化器做出更合理的并行计划。定期安排统计信息更新作业,在数据批量加载后立即执行RUNSTATS,是保障执行计划质量的基础动作。
-- 检查最近的统计信息收集时间,避免优化器基于过期信息决策 SELECT TABSCHEMA, TABNAME, STATS_TIME FROM SYSIBM.SYSTABLES WHERE TABSCHEMA = 'SALES' AND STATS_TIME < CURRENT TIMESTAMP - 7 DAYS; -- 对自动统计信息收集进行配置 UPDATE DATABASE CONFIGURATION FOR proddb USING AUTO_RUNSTATS ON AUTO_STMT_STATS ON;
最后,数据倾斜的处理往往不是一锤子买卖。业务数据会随时间变化,原来均匀的分布键可能因为业务扩张出现新的热点。建议建立常态化的监控机制,定期对比各分区的行数和空间增长趋势,把倾斜检查纳入健康巡检清单。发现轻微倾斜时不必立即大动干戈重分布,可以先通过统计信息和SQL调优缓解;当倾斜比例超过两到三倍并实际影响性能时,再安排重分布操作。分阶段、有节奏地处理,才能在稳定性和性能之间取得平衡。