news 2026/10/15 5:01:52

Oracle数据字典基表CDEF$与CON$竞争引发的性能问题排查与优化

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Oracle数据字典基表CDEF$与CON$竞争引发的性能问题排查与优化

做DBA这些年,性能问题见了不少,有一种问题最让人头疼:你翻遍AWR、盯了半天活动会话,发现罪魁祸首居然是Oracle数据字典里那几个不起眼的内部基表。CDEF$和CON$就是这类角色。它们平时藏在数据字典深处,几乎没人关心,可一旦约束变更频繁、数据字典查询量暴涨,它们就能把系统拖得步履蹒跚。最近处理某业务系统的卡顿问题,最终定位到这两个表上的竞争,顺手把相关的原理和排查过程整理一下,给遇到类似问题的朋友一个参考。

1. 先认识CDEF$和CON$:Oracle约束元数据的源头

1.1 数据字典里的“约束档案柜”

Oracle数据库的元数据不是凭空存在的,而是以普通表的形式,存放在SYSTEM表空间的一组内部基表中。这些基表平时被隐藏得很深,普通用户连查询权限都没有,但执行DDL、查询数据字典视图时,Oracle内部会不断读写它们。

CDEF$和CON$就是这组内部基表里专门负责约束信息的两张表。约束是什么?主键、唯一键、外键、检查约束、非空约束,都属于约束。只要你在数据库里创建过表、加过主键,Oracle就需要把这条约束的元数据记录下来。记录到哪里?一部分进CON$,一部分进CDEF$,还有一部分进CCOL$等关联表。

很多DBA对这两张表不熟,是因为日常工作主要和DBA_CONSTRAINTS、USER_CONSTRAINTS这些视图打交道。但视图只是一层“窗口”,窗口背后的数据源正是这些基表。当你在数据库里查询一个表有哪些约束时,Oracle会自动把视图SQL重写成对基表的访问,这个过程对你是透明的,但它的消耗会真实地体现在数据库性能数据中。

1.2 两个基表的分工:一张“总台账”和一张“明细卡片”

要理解性能问题,先要理解数据结构。CON$可以理解成约束的“总台账”,一条约束在CON$里对应一条记录。它保存了约束的编号、所属用户、约束名称、约束类型等基本信息。比如你创建了一个主键约束“PK_EMP_ID”,CON$里就会多一行,记下约束名为PK_EMP_ID,类型是主键,属主是哪个USER#。

CDEF$则是同一份约束的“明细卡片”,它和CON$通过约束编号CON#关联,保存了约束的扩展属性,比如是否启用、是否处于验证状态、是否可延迟、检查约束的具体条件文本等。一个约束在CON$里有一条总台账,在CDEF$里也会有一条明细记录。

从数据查询的角度看,两张表要连接起来才能看到一个约束的完整信息。这也是为什么如果它们上的执行计划或竞争出现问题,数据字典查询会整体变慢。除了这两张表,约束列信息还存放在CCOL$中,它记录了约束涉及了哪些列,但本文主要聚焦在CDEF$和CON$上。

1.3 从SQL视角看:查询约束时背后发生了什么

拿一个最常见的操作举例:

SELECT constraint_name, constraint_type, status FROM user_constraints WHERE table_name = 'EMPLOYEES';

你以为这只是读一张视图,其实Oracle的递归SQL会访问USER$、CON$、CDEF$、CCOL$等基表。具体逻辑大体是:先通过USER#定位当前用户,再通过OBJ#找到EMPLOYEES表对应的对象,然后从CON$里找到该对象上所有约束,再从CDEF$里取出状态等字段。

这个过程在约束很少的库上几乎无感,但如果一个Schema里有几百万条约束,或者正在批量创建约束,递归SQL可能变成性能瓶颈。所以我一直建议DBA不要忽略那些看起来简单的数据字典查询,它的背后可能是多张基表的大连接。

理解了这个底层结构,后面再看性能问题就清晰多了。所有和约束有关的活动,最终都会汇聚在这两张基表上,就像所有车辆都要经过同一座桥。

2. 为什么看起来不相干,却会和性能问题挂钩

2.1 所有约束变更都绕不开这两个表

Oracle里创建、删除、启用、禁用约束,本质上都是在这两张基表上执行DML操作。

  • CREATE TABLE语句里带上了CONSTRAINT关键字,那么建表时就会地插入CON$和CDEF$的记录。
  • ALTER TABLE ADD CONSTRAINT,同理,新增一条约束记录。
  • ALTER TABLE DROP CONSTRAINT,会从这两张表删除对应记录。
  • ALTER TABLE DISABLE CONSTRAINT,不会删除记录,但会把CDEF$里的ENABLED状态位改掉。
  • DROP TABLE,会把该表上的所有约束记录一并删除。

这些操作平时单独看非常轻,一次插入一条记录而已。但数据库是共享资源,当大量会话同时做类似操作时,它们会去修改同一张基表,甚至是同一个Block上的数据。这时候就会产生行锁竞争、Buffer忙等待、Latch竞争,然后以等待事件的形式出现在AWR报告里。

在很多具体案例里,系统并不是因为业务SQL写得差,而是因为某个自动化工具在后台批量建表、批量加约束,把数据字典基表“打”成了热块。性能问题的根源,往往不在业务侧,而在这些基础操作。

2.2 高频DDL场景:大家都挤在同一扇门里

我遇到过一个典型场景:某公司的数据中台系统,每天凌晨会有大量作业并发建表,每个作业创建几十张表,每张表平均有十来个主键、外键、检查约束。作业之间还会重启,失败后反复重试。

一开始系统只是慢,后来直接出现大量“row cache lock”等待,所有会话都堵在字典缓存上。排查时发现,活动会话里的SQL几乎全是递归DDL,内容就是往CON$和CDEF$里插入数据。为什么慢?因为大家都往同一个高频Block上插入,每次插入都要拿Latch、排队、等Commit,互相拖累。

更麻烦的是RAC环境。两个节点的会话如果同时修改同一个Buffer,会产生跨节点的Buffer竞争,等待事件可能还会带上gc buffer busy。这种情况下,问题会被进一步放大,看起来像节点间通信出了问题,实际源头还是数据字典基表上的并发DDL太密集。

2.3 大规模数据加载/结构迁移:递归SQL成了“隐形杀手”

数据导入、克隆Schema、应用发版、数据库升级等场景,也会大量触发约束元数据操作。比如用数据泵导入一批表,数据泵工具在导入时会重建约束。把导入的并行度调得过高,所有并行进程都会同时去操作CON$/CDEF$。这时候你看到的可能是大量INSERT INTO CON$、INSERT INTO CDEF$这样的递归SQL。

有些DBA遇到这类情况,总是怀疑数据泵参数配得不对,或者怀疑表空间I/O不行。但实际上,如果I/O和等待都集中在数据字典基表上,就需要意识到,这是约束元数据写入的排队问题。并行度不是越高越好,尤其是涉及到系统基表的DDL密集型作业,并行度过高反而会加剧竞争。

在大规模Schema迁移时,我建议先评估源端的约束总量:约束少,并行度可以高一些;约束多,并行度要保守一些。最好在测试环境模拟一次,看看等待事件会不会集中到row cache lock上。

2.4 基表上的执行计划坑:统计信息缺失与全表扫描

CDEF$和CON$也是表,也归优化器管。在正常情况下,Oracle对系统基表的访问采用内部优化过的固定执行计划,用CON#或OBJ#快速定位记录。但有些场景下,基表统计信息不准确,优化器就会算出全表扫描加嵌套循环的烂计划。

我见过一个案例:升级后某系统查询DBA_CONSTRAINTS非常慢,一条原本毫秒级的语句,执行成了几十秒。把执行计划抓出来一看,CDEF$被全表扫描,过滤条件是CON#等于某个值。理论上CON#是主键,应该走索引或直接定位,但优化器因为缺少统计信息,估算出CDEF$只有几十行,所以选择了全表扫描。

问题在于这个库的CDEF$里有上百万行,全表扫描一触发,Buffer Gets暴涨,直接拖垮Buffer Cache。这种问题用“优化SQL”的思路怎么都绕不过去,因为SQL是递归的、你改不了,只能从基表访问路径上想办法。

关于统计信息,这里必须提醒一句:不要轻易对SYS模式的内部表收集统计信息。Oracle对数据字典基表有自己的管理方式,乱收集可能导致更多计划异常。如果确实怀疑统计信息问题,先在测试库验证,或者在Oracle支持指导下操作。

3. 如何一步步定位是它们在“捣乱”

3.1 先看AWR:从等待事件和SQL统计里找线索

排查任何性能问题,我习惯先拿一个高峰期或问题时段的AWR报告,看Top 10等待事件。如果CDEF$/CON$有问题,AWR里往往会出现以下等待事件:

  • row cache lock:字典缓存上的锁竞争,常见于并发DDL、序列、约束操作。
  • buffer busy waits:多个会话争抢同一个数据块,对应基表上的热块。
  • enq: TX - row lock contention:行锁竞争,通常是并发修改同一行约束记录。
  • latch: row cache objects:访问字典缓存时的Latch争用。

只看等待事件还不够,要看SQL统计。如果SQL ordered by Gets里大量出现SQL_TEXT包含“CON$”“CDEF$”的递归语句,那基本可以锁定方向了。递归SQL平时很少出现在TopSQL里,一旦它们上榜,说明数据字典内部操作已经成了系统的主要负担。

3.2 借助动态性能视图锁定递归SQL和具体会话

拿到AWR线索后,下一步是实时或从历史会话中抓取具体SQL。可以这样查:

SELECT sql_id, sql_text, executions, buffer_gets, cpu_time FROM v$sql WHERE sql_text LIKE '%CDEF$%' ORDER BY buffer_gets DESC;

重点看两类语句:一类是以INSERT、DELETE、UPDATE开头的DML,另一类是SELECT开头的递归查询。前者通常来自DDL,后者通常来自数据字典视图查询。

如果问题还在发生,可以直接查V$ACTIVE_SESSION_HISTORY或DBA_HIST_ACTIVE_SESS_HISTORY:

SELECT sample_time, session_id, sql_id, event, wait_class, blocking_session FROM v$active_session_history WHERE event LIKE '%row cache lock%' ORDER BY sample_time DESC;

通过ASH的SQL_ID,可以反查出对应的语句文本,确认是不是在操作CON$和CDEF$。

3.3 现场取证:抓当时的Session、锁和Latch

问题发生时,如果条件允许,直接抓现场。下面这几个手段比较管用:

  • 查询V$SESSION,找等待事件为row cache lock或buffer busy waits的会话,记录SQL_ID和调用的对象。
  • 查询V$LOCKED_OBJECT,看是否有会话持有CON$、CDEF$上的行锁。这需要以SYS或合适权限查看。
  • 使用如下SQL看Buffer上有多少会话在等待:
SELECT obj.owner, obj.object_name, w.session_id, w.wait_event_text FROM v$wait_chains w, dba_objects obj WHERE w.in_wait = 'WAITING' AND w.object_id = obj.object_id;

当然,V$WAIT_CHAINS在一些版本中不直接给出对象,可能需要结合P1/P2参数反查文件号、块号,再映射到对象。这个流程虽然麻烦,但定位结果非常准确。

还有一种经典方法,就是对某个DDL会话做10046事件跟踪,打开递归SQL输出:

ALTER SESSION SET events '10046 trace name context forever, level 12';

跟踪文件里会完整显示递归的INSERT INTO CON$、CDEF$语句,以及它们消耗的时间和执行计划。通过这种方式,能很清楚地看到一次ALTER TABLE ADD CONSTRAINT到底做了多少内部操作。

3.4 一个虚拟案例:看着CDEF$/CON$上的竞争怎么出现

为了更直观,我描述一个模拟场景。某核心系统在晚上20点出现会话堆积,AWR显示Top事件为row cache lock和enq: TX - row lock contention。

排查时从DBA_HIST_ACTIVE_SESS_HISTORY中,抓到了几十个阻塞型会话的SQL_ID,指向同一类语句。利用V$SQL查看SQL_TEXT,内容大致是:

text:INSERT INTO CON$ (CON#,OWNER#,NAME,TYPE#...)

另一个SQL_ID对应的是:

text:INSERT INTO CDEF$ (CON#,OBJ#,ENABLED,CONSISTENT,VALIDATED...)

结合时间点,发现是一批大数据平台的调度作业正在并发创建维度表和事实表,每个作业有20个并发线程。每个线程建表后会再加主键、外键和检查约束,相当于在短时间内往这两个基表插入几十万条记录。这些插入集中在同一个数据块附近,于是所有线程互相排队。

定位到根因后,把并发从20降到4,并在每个DDL之间加了极小的间隔,问题立刻缓解。后续把批量建表脚本优化为“建表和加约束合并到同一条DDL里”,从根源上减少了字典基表的访问次数。

4. 优化与预防:别让字典基表成为性能瓶颈

4.1 约束设计层面的“减肥”

很多人以为性能问题是DBA的锅,其实约束本身的设计也很重要。每一条约束都会在CON$和CDEF$里增加记录,约束越多,字典基表越大,相关操作越容易产生性能开销。

我见过一些系统,因为建表工具配置不当,每张表生成了一堆SYS_C开头的系统命名约束,其中还有大量重复的检查约束。业务上,一个列已经有非空约束了,又额外加一个CHECK(XX IS NOT NULL);或者约束条件互相重叠,完全没有必要。

优化方向不是让你去掉必要的数据完整性约束,而是合并、简化。比如:

  • 对于必须保证非空的列,直接用NOT NULL约束,不要再用冗余CHECK约束。
  • 对于状态列,能用CHECK约束限定取值范围,就不要同时加多个单值约束。
  • 约束如果可以放在表级一次声明,就不要在列级重复声明。

约束数量减少了,字典基表上的操作自然减少。这个道理很简单,但很多项目在最初建表时根本不会想那么远。

4.2 DDL操作节奏控制与拆解

如果业务上确实需要大量建表、加约束,就要控制DDL节奏。我总结了几条可落地的经验:

  • 尽量把约束定义直接写在CREATE TABLE语句里,这是最高效的。Oracle建表时一次性创建表和约束,比建完表再逐个ALTER TABLE ADD CONSTRAINT少很多递归调用。
  • 若要批量执行DDL,控制并发度在2到4个DBE之间,不要动辄开到十几个线程。
  • 每个DDL语句之间,可以加上短暂的sleep(比如0.5秒到1秒),让字典基表的热度降下来。
  • 在RAC环境中,尽量让不同节点的批量作业错开执行时间,避免跨节点争用同一数据块。
  • 对于超大的导入作业,不要用INSERT触发器或嵌套DDL,尽量保证约束创建过程平滑。

有时候运维人员为了缩短总耗时,拼命加大并行度,结果等待事件比执行时间还长,总耗时反而更差。性能优化不是一味追求“快”,而是找到合适的并发度。

4.3 字典基表维护建议

CDEF$和CON$位于SYSTEM表空间。很多时候SYSTEM表空间的I/O能力被人忽略,实际上它承载了数据字典的大量读写。如果SYSTEM表空间在慢盘上,字典基表的竞争会更明显。有条件的情况下,保证SYSTEM表空间所在存储的性能,是基础性工作。

其次,不要对这两张内部表做任何手工DML操作。我见过有人试图通过DELETE FROM CDEF$来删除所谓的“无效约束记录”,结果导致字典不一致,数据库只能做恢复。这是绝对的高危操作。

如果确认是执行计划问题,可以考虑对特定系统基表收集统计信息。但要遵循两条原则:先备份、测试环境验证;最好在Oracle官方支持指导下进行。例如:

EXEC DBMS_STATS.GATHER_TABLE_STATS(USERNAME => 'SYS', TABNAME => 'CDEF$');

这条命令可能在特定版本下有用,但可能影响其他递归SQL。我在生产环境从来不会上来就收集SYS表统计信息,除非已经通过10046和99事件确认了计划问题的确来自统计信息缺失。

4.4 监控与巡检清单

要把问题挡在发生之前,建议把下面这些检查项加入日常巡检:

  • 定期检查AWR中row cache lock和buffer busy waits趋势。
  • 查询V$ROW_CACHE,关注dc_constraints、dc_objects的Gets和Misses,对比历史基线。
  • 监控CON$和CDEF$的行数变化,可以这样查:
SELECT (SELECT COUNT(*) FROM sys.con$) con_cnt, (SELECT COUNT(*) FROM sys.cdef$) cdef_cnt FROM dual;

正常情况下,行数应该缓慢增长。如果某个时间段内行数突然暴增,那大概率有批量DDL作业在集中创建约束。

  • 对生产环境的高频DDL作业进行审核,记录每个作业峰值会话数和约束影响量。
  • 建立DDL操作申报机制,避免随意在业务高峰期执行批量建表命令。

我把这些内容整理成一张表格,也方便大家直接拿去对照:

监控对象关键指标异常信号建议动作
AWR等待事件Row Cache Lock连续多个报告上榜检查并发DDL和递归SQL
V$SQLSQL_TEXT含CDEF$/CON$出现在Top SQL抓取SQL_ID和来源
V$ROW_CACHEDC_CONSTRAINTS MissMiss率持续上升评估约束维护频率
DBA_OBJECTS当前对象DDL会话长时间占用分析锁和会话状态
CON$/CDEF$数量COUNT(*)非预期突增定位新增约束来源

5. 常见问题排查实录与避坑心得

5.1 问题类型速查表

下面这张表汇总了我遇到过的几类典型问题,给大家参考:

症状可能原因核心定位手段解决方法
数据库偶发停顿,AWR出现Row Cache Lock并发DDL,字典缓存争用查看ASH中事件、SQL_ID降低DDL并发,错峰执行
大量Buffer Busy Waits,面向系统基表约束记录热块争用反查块号、对象映射合并DDL,减少递归DML
查询DBA_CONSTRAINTS非常慢CDEF$/CON$执行计划差EXPLAIN PLAN、10046在指导下修正统计信息
数据泵导入期间严重阻塞并行进程争用字典基表SQL_TEXT含INSERT INTO CDEF$降低并行度,拆分导入批次
DROP大表/分区时长时间等待删除约束记录引发行锁V$SESSION阻塞链先离线禁用约束,分批操作

如果你遇到的问题和上面某一行吻合,可以直接按对应方向排查。但要注意,一个复杂问题可能同时涉及多个因素,AWR、ASH、V$SQL、10046这些工具要组合使用,才不会有盲区。

5.2 我的几点实操心得

这类问题经历多了,我有几个体会非常深。

第一,不要一看到CON$和CDEF$就觉得是系统坏了,它们只是“受害者”。真正的原因是业务侧产生了大量约束变更。这时候应该去查是谁在DDL,而不是盯着基表想办法。我之前有过一次弯路,花了两个小时研究CDEF$的访问路径,最后才发现是一套自动化压测脚本在疯狂建删表。找到源头,问题自然就解决了。

第二,10046追踪是分析这类问题的终极武器。普通视图查不到递归SQL的细节,但10046能完整看到一条ALTER TABLE背后发起了多少次对CON$、CDEF$的DML,以及每次消耗了多长事件。有了这个证据,说服应用团队优化DDL脚本才有力度。

第三,要注意数据字典缓存的特殊性。即使你优化了SQL,字典缓存竞争也可能依旧存在。因为DDL本身会持有字典锁和缓存锁,这是Oracle内部机制决定的。你能做的是减少同时发生的DDL,而不是试图绕过缓存。

第四,如果是在RAC环境,看到gc buffer busy与数据字典基表相关,往往不是网络问题。先确认所有节点上是否有相同业务的并发DDL。跨节点对同一个数据块的争用,本质上和单实例的Buffer Busy一样,只是表现更隐蔽。

最后分享一个小技巧:在分析V$SQL时,建议过滤掉普通的业务SQL,只保留递归SQL:

SELECT sql_id, substr(sql_text,1,100) sql_text, executions, buffer_gets FROM v$sql WHERE sql_text LIKE 'INSERT INTO CON$%' OR sql_text LIKE 'INSERT INTO CDEF$%' OR sql_text LIKE '%CDEF$%' ORDER BY buffer_gets DESC;

很多时候,你根本不需要分析完整执行计划,只要列出来就能看出是哪个模块在制造压力。把递归SQL和业务时间点对上,基本就能定位到具体作业。

CDEF$和CON$这两个基表,平时安静地躺在数据字典里,但它们承载了Oracle所有约束的生命周期。性能问题一旦和它们挂钩,往往意味着整个系统的DDL活动已经超过了数据库内部能平滑处理的阈值。与其等它爆发,不如提前做好监控和节奏控制。这样等下次遇到奇怪卡顿时,你也能第一时间辨别:这次到底是不是CDEF$和CON$在背后“捣乱”。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/15 5:01:21

8086循环程序设计:从DEBUG跟跑到硬件级执行流理解

简介:本资源是面向高校《微机原理与接口技术》课程学生的经典实验教学材料,聚焦循环程序设计核心能力训练,重点解决重复控制指令、串操作指令及重复前缀的选型应用与循环结构建模问题。资源为1个224KB的Word文档(.doc)…

作者头像 李华
网站建设 2026/10/15 5:01:12

Apollo-11源码启示录:AGC汇编与容错设计在现代嵌入式中的实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/15 5:00:50

基于协同过滤的旅游推荐系统设计与实现

做旅游推荐系统这个选题,最初的想法很简单:毕设既要体现工作量,又得把数据采集、算法、可视化这几块核心技术串成一条完整的链路。旅游推荐系统恰好能满足这个要求——数据端爬取真实旅游平台的景点和用户行为数据,算法端用协同过…

作者头像 李华
网站建设 2026/10/15 5:00:46

5款电脑端批量剪辑混剪工具实测盘点,效率与差异化兼得

2026年了,如果你还在靠人工一帧一帧地剪短视频、一天只能交三五条成片,那我建议你真的要认真看一下电脑端批量剪辑混剪工具这几年进化到什么程度了。我自己是因为接了矩阵账号的代运营需求,每天固定要出二三十条不同主题的短视频,…

作者头像 李华
网站建设 2026/10/15 5:00:42

Vue动态路由后端封装:JavaBean设计与树形组装实战

1. 从后端视角拆解动态路由:为什么JavaBean封装是绕不开的一环做过后端权限系统的朋友大概率都碰过这样一个场景:前端用 Vue 搭好了框架,路由表却没法写死。不同角色登录后看到的菜单不一样,同一个页面不同用户能点的按钮也不一样…

作者头像 李华
网站建设 2026/10/15 5:00:31

Unity PC游戏增量更新插件PATCH:文件级比对与差量分发实战

1. 这个补丁插件到底在解决什么问题做过PC端独立游戏发布的朋友应该都有过这种体验:游戏上线第一天,玩家反馈一个致命bug,你火急火燎改完代码,重新打包,上传一个几百兆甚至几个G的完整安装包,然后告诉玩家“…

作者头像 李华