You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

查询information_schema.columns时包含column_default字段耗时极久的原因及解决方案咨询

查询information_schema.columns时包含column_default字段耗时极久的原因及解决方案咨询

嗨,这个问题我之前帮好几个开发者排查过,核心原因其实和PostgreSQL处理column_default字段的方式有关,咱们一步步拆解:

为什么包含column_default会这么慢?

你要知道,information_schema.columns本质是个兼容SQL标准的视图,不是直接存数据的物理表。其中column_default这个字段并不是预先生成好存在那里的——每次查询它时,PostgreSQL都要调用pg_get_expr()函数,把系统表pg_attrdef里存储的内部表达式节点(二进制格式)转换成人类能读的SQL文本。

如果你的table1满足以下任一情况,这个转换过程就会变得异常缓慢:

  • 表有大量列,且很多列都有默认值,导致需要批量解析大量复杂表达式;
  • 某些列的默认值是极复杂的表达式:比如嵌套调用自定义函数、引用了其他表/视图的子查询,或者包含大量逻辑的计算式,pg_get_expr()解析这类表达式的开销会指数级上升;
  • 系统表的统计信息过时:PostgreSQL的查询优化器依赖统计信息来生成高效的执行计划,如果pg_attrdefpg_attribute的统计信息太久没更新,优化器可能会选择低效的关联方式,拖慢整个查询;
  • 如果你用的是比较老的PostgreSQL版本(比如9.x及更早),information_schema的视图定义和pg_get_expr()函数的性能都不如新版本,处理复杂场景时短板更明显。

另外还有个小概率情况:如果table1曾经频繁修改列默认值(比如反复添加、删除、修改默认值),可能导致pg_attrdef表出现数据膨胀,查询时需要扫描大量死元组,也会拖慢速度。

怎么解决这个问题?

根据不同的场景,你可以选下面这些方案:

1. 临时应急:只查询必需字段

如果你的业务逻辑可以暂时不需要column_default,那就直接去掉这个字段——就像你已经发现的那样,这是最快的临时解决办法。

2. 直接查询原生系统表,绕过information_schema

information_schema为了兼容SQL标准做了很多封装,性能远不如直接查PostgreSQL的原生系统表。你可以用下面的查询替换原来的语句,它直接关联pg_attributepg_attrdef等系统表,跳过冗余的兼容逻辑,性能会提升很多:

SELECT
    a.attname AS column_name,
    format_type(a.atttypid, a.atttypmod) AS data_type,
    CASE WHEN d.adbin IS NOT NULL THEN pg_get_expr(d.adbin, d.adrelid) ELSE NULL END AS column_default,
    a.attnotnull IS NOT TRUE AS is_nullable,
    -- 以下为原查询对应字段,按需调整
    CASE WHEN a.atttypid IN ('varchar'::regtype, 'char'::regtype, 'text'::regtype) THEN a.atttypmod - 4 ELSE NULL END AS character_maximum_length,
    CASE WHEN a.atttypid IN ('numeric'::regtype) THEN (a.atttypmod >> 16) & 65535 ELSE NULL END AS numeric_precision,
    CASE WHEN a.atttypid IN ('timestamp'::regtype, 'timestamptz'::regtype, 'time'::regtype, 'timetz'::regtype) THEN a.atttypmod & 65535 ELSE NULL END AS datetime_precision,
    CASE WHEN a.atttypid IN ('numeric'::regtype) THEN a.atttypmod & 65535 ELSE NULL END AS numeric_scale,
    a.attnum AS ordinal_position
FROM pg_attribute a
LEFT JOIN pg_attrdef d 
    ON a.attrelid = d.adrelid 
    AND a.attnum = d.adnum
JOIN pg_class c 
    ON a.attrelid = c.oid
JOIN pg_namespace n 
    ON c.relnamespace = n.oid
WHERE n.nspname = 'our_schema'
    AND c.relname = 'table1'
    AND a.attnum > 0  -- 排除系统隐藏列
    AND NOT a.attisdropped  -- 排除已删除的列
ORDER BY a.attnum;

3. 检查并简化复杂的默认值

先看看table1里哪些列的默认值是罪魁祸首,用这个语句直接查:

SELECT 
    attname AS column_name,
    pg_get_expr(adbin, adrelid) AS default_expression
FROM pg_attrdef 
JOIN pg_attribute 
    ON pg_attrdef.adrelid = pg_attribute.attrelid 
    AND pg_attrdef.adnum = pg_attribute.attnum
WHERE adrelid = 'our_schema.table1'::regclass;

如果发现某些默认值是没必要的复杂表达式(比如用了多层嵌套函数但其实可以预计算成静态值),可以考虑简化它——比如把DEFAULT (SELECT max(id) +1 FROM other_table)换成用序列,或者业务层处理,这样既能提升查询column_default的速度,也能提升表的写入性能(插入时计算默认值也会更快)。

4. 刷新系统表的统计信息

执行下面的语句刷新系统表的统计信息,让优化器能生成更高效的执行计划:

ANALYZE pg_attrdef;
ANALYZE pg_attribute;

这个操作很快,也不会锁表,适合随时执行。

5. 清理系统表的膨胀(低峰期操作)

如果pg_attrdef有数据膨胀的情况(可以用SELECT relname, n_live_tup, n_dead_tup FROM pg_stat_user_tables WHERE relname = 'pg_attrdef';查看死元组数量),可以在业务低峰期执行:

VACUUM FULL pg_attrdef;

注意:VACUUM FULL会锁表,执行期间无法对pg_attrdef做写操作,所以一定要选业务量最小的时候做。

6. 升级PostgreSQL版本

如果你的版本比较老(比如10.x及更早),升级到最新的稳定版(比如15或16)会有明显的性能提升——新版本不仅优化了pg_get_expr()的解析效率,还改进了系统表的查询计划生成逻辑,对复杂场景的支持好很多。

你可以根据自己的实际场景选对应的方案,有其他细节问题随时问~

火山引擎 最新活动