查询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_attrdef或pg_attribute的统计信息太久没更新,优化器可能会选择低效的关联方式,拖慢整个查询; - 如果你用的是比较老的PostgreSQL版本(比如9.x及更早),
information_schema的视图定义和pg_get_expr()函数的性能都不如新版本,处理复杂场景时短板更明显。
另外还有个小概率情况:如果table1曾经频繁修改列默认值(比如反复添加、删除、修改默认值),可能导致pg_attrdef表出现数据膨胀,查询时需要扫描大量死元组,也会拖慢速度。
怎么解决这个问题?
根据不同的场景,你可以选下面这些方案:
1. 临时应急:只查询必需字段
如果你的业务逻辑可以暂时不需要column_default,那就直接去掉这个字段——就像你已经发现的那样,这是最快的临时解决办法。
2. 直接查询原生系统表,绕过information_schema
information_schema为了兼容SQL标准做了很多封装,性能远不如直接查PostgreSQL的原生系统表。你可以用下面的查询替换原来的语句,它直接关联pg_attribute、pg_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()的解析效率,还改进了系统表的查询计划生成逻辑,对复杂场景的支持好很多。
你可以根据自己的实际场景选对应的方案,有其他细节问题随时问~




