哈希分区建表时分区键选错会加剧热点块问题;必须选用高唯一性列(如主键)、避免低基数或单调递增字段,且分区数应为2的幂,否则数据倾斜、范围查询失效、并发插入性能下降。

哈希分区建表时分区键选错,直接导致热点块更严重
哈希分区本身不制造热点,但选错 partition_key 会让所有写入集中到少数几个分区——等于把单点压力放大成多个小单点。最典型的是用 status、is_deleted 这类只有 2–3 个值的列做分区键,结果全部数据挤进 p0 和 p1,其他分区空转。
必须满足两个硬条件:partition_key 值要高唯一性(优先主键或带唯一约束的列),且不能是单调递增字段(如 create_time 或序列 ID)。否则即使分区数够多,哈希后仍可能在物理块层面形成“伪热点”。
- MySQL 支持表达式分区:例如
PARTITION BY HASH(year(create_time)*10000 + month(create_time)) - Oracle 不支持表达式,得先加虚拟列再分区:
ADD COLUMN hash_key GENERATED ALWAYS AS (MOD(id, 64)) VIRTUAL,再按该列哈希 - DM8 强制用
DMHASHPART0等命名,且分区数必须是 2 的幂(如 8/16/32),否则底层散列不均
并发插入慢不是分区问题,而是索引设计没对齐
哈希分区表并发插入卡顿,90% 情况下根源是索引类型和唯一性约束。Oracle 对本地唯一索引(LOCAL UNIQUE INDEX)仍会跨分区做全局唯一性检查,等效于全局索引,引发 enq: TX - index contention 和大量逻辑读。
真正适配高并发写入的索引方案只有一条路:LOCAL NONUNIQUE INDEX,且分区键必须作为索引前导列。这样每个分区独立维护 B-tree,分裂和 latch 争用都局限在单一分区内部。
- 若业务强依赖唯一性,改用
GLOBAL INDEX HASH PARTITIONS 64,比 B-tree 全局索引减少 latch 争用 - 已有全局索引无法删除?插入前执行
ALTER INDEX idx_name UNUSABLE,批量插入完再REBUILD,提速 3–5 倍 - 避免在哈希分区表上建普通全局索引,尤其不要以非分区键为前导列
RAC 环境下 gc buffer busy acquire 根源常被误判
RAC 中出现 gc buffer busy acquire,第一反应不该是调参数,而应查是否因哈希分区键选择不当,导致多个实例反复争抢同一数据块。比如用升序 ID 当分区键,新数据持续写入同一个哈希桶对应分区,该分区所在节点就成了事实上的写入中心。
ASH 中必须同时看 current_file#、current_block# 和 P3:若 P3 = 1(普通数据块),且这些块集中在某几个分区的数据文件上,基本可锁定是分区键分布不均;若 P3 = 4(段头争用),说明还卡在 ASSM 的 freelist 争用上,得切自动段空间管理,而非改分区。
- 查热点块归属:用
current_block#和current_file#查DBA_EXTENTS,条件必须是&block_id BETWEEN block_id AND block_id + blocks - 1 - RAC 下必须查
gv$active_session_history并带上inst_id,否则漏掉跨节点争用 - 确认是否真热点:排除连接池泄漏,运行
SELECT module, machine, COUNT(*) FROM v$session WHERE status = 'INACTIVE' AND event = 'SQL*Net message from client' AND last_call_et > 3600 GROUP BY module, machine
哈希分区对范围查询完全失效,别指望它兼顾点查和范围拉取
哈希分区靠 MOD(hash_value, partition_count) 定位分区,原始值的大小关系彻底打散。所以 WHERE user_id = 12345 能精准落一个分区,但 WHERE user_id BETWEEN 1000 AND 2000 必须扫全表所有分区——失去分区剪枝能力,性能比不分区还差。
如果业务既有高频点查(如用户详情页),又有报表类时间范围查询(如月度订单汇总),硬扛哈希分区只会两头不讨好。此时唯一合理路径是组合分区:外层按时间做 RANGE 分区,内层在每个子分区里按 ID 做 HASH 子分区。
- MySQL 5.7+ 和 Oracle 支持这种嵌套分区,但语法差异大:Oracle 需显式写
PARTITION BY RANGE (create_time) SUBPARTITION BY HASH (user_id) - DM8 不支持子分区,只能通过应用层路由 + 手动指定分区名来模拟
- 上线前务必验证执行计划:用
EXPLAIN PLAN FOR确认范围查询是否真的只扫目标 RANGE 分区,而非全扫
实际落地时最容易被忽略的,是分区键和索引前导列必须严格一致——哪怕只是顺序颠倒(如分区键是 (a,b),索引却是 (b,a)),都会让本地索引失效,退化成全局行为。这点在 RAC 和高并发场景下会立刻暴露为 latch 争用激增。


















