我要提问
ARTICLE DETAIL

资讯详情

前沿编程新知与开发实战干货的深度解读。

PostgreSQL自定义统计信息:解决查询计划预估偏差的核心技术

PostgreSQL自定义统计信息:解决查询计划预估偏差的核心技术 1. 什么是PostgreSQL自定义统计信息不只是“让查询变快”的模糊概念你可能在某次慢查询排查中被DBA或资深开发随口提过一句“这表缺统计信息”也可能在执行EXPLAIN时看到执行计划里赫然写着“rows1000”而实际返回结果动辄百万级——这种离谱的预估偏差正是统计信息失准最直观的伤疤。但绝大多数人对“统计信息”的理解还停留在“PostgreSQL自己会收集”“ANALYZE一下就好了”这种模糊层面。真正能动手定制、干预、甚至重写统计信息生成逻辑的人不到活跃PostgreSQL使用者的5%。而“自定义统计信息”这个标题说的恰恰就是这5%人群每天在干的事不是被动等待系统猜而是主动告诉优化器“这张表的数据到底长什么样”。核心关键词“PostgreSQL自定义统计信息”拆开来看“PostgreSQL”框定了技术栈边界——它不是MySQL的直方图不是Oracle的动态采样更不是SQL Server的统计对象“自定义”二字是灵魂意味着脱离pg_stats视图的默认字段、绕过ANALYZE的固定采样策略、突破单列/多列统计的固有范式“统计信息”则指向底层目标为查询优化器提供足够精确、足够细粒度、足够及时的数据分布描述从而让JOIN顺序不乱、索引选择不偏、并行度分配不傻。它解决的不是“能不能查出来”而是“能不能用最省资源的方式查出来”。适合谁不是刚学SQL的新人而是已经能看懂执行计划、会调work_mem、敢改shared_buffers的中级以上数据库工程师是那些在千万级订单表上加个WHERE status IN (paid,shipped)就触发全表扫描、却找不到原因的后端负责人更是需要支撑实时报表、AB测试分流、用户行为路径分析等强数据敏感型业务的架构师。它不教你怎么写SQL而是帮你让写的每一条SQL都跑在最优路径上。2. 为什么必须自定义默认统计信息的三大硬伤与真实业务场景反噬PostgreSQL的默认统计信息机制设计初衷是“够用就好”而非“精准无误”。它像一个经验丰富的老司机能应付城市主干道但一旦驶入山间盘山路、雨夜湿滑弯道、突发施工围挡就容易判断失误。这种“够用”在中小规模、数据分布均匀、查询模式固定的系统里确实可靠但一旦进入真实业务战场三处硬伤立刻暴露无遗2.1 硬伤一单列统计的“盲区效应”——当WHERE条件从来不是单点触发默认ANALYZE为每列生成独立的值频度most_common_vals和数值直方图histogram_bounds但它完全不描述列与列之间的关联性。举个电商场景一张orders表有user_id用户ID、product_category商品类目、order_time下单时间三列。运营同学想查“近7天内北京地区用户购买手机类目的订单”SQL形如SELECT * FROM orders WHERE order_time NOW() - INTERVAL 7 days AND user_id IN (SELECT id FROM users WHERE city Beijing) AND product_category mobile_phone;优化器看到order_time有直方图、product_category有频度表便各自估算order_time范围筛选掉90%product_category mobile_phone命中5%于是粗暴相乘得4.5%——预估返回4500行。但现实是北京用户集中在朝阳、海淀两区而这俩区的手机类目订单占全市同类订单的82%且近7天恰逢618大促手机类目订单量激增3倍。真实结果是28万行。预估偏差超60倍直接导致优化器放弃使用idx_order_time索引转而选择嵌套循环全表扫描查询从200ms飙升至12秒。这不是SQL写得差是优化器“瞎了”因为它根本不知道user_id的地理属性和product_category的促销周期存在强耦合。自定义统计信息要做的第一件事就是补上这个“盲区”——通过创建扩展的多列统计对象CREATE STATISTICS让优化器知道“北京用户手机类目近7天”这个组合其实际出现概率是单列概率乘积的18倍。2.2 硬伤二直方图的“分辨率陷阱”——当你的数据是“长尾”而非“钟形”默认直方图将列值域线性切分为N等份default_statistics_target控制默认100对正态分布数据效果极佳。但现实数据往往是残酷的“长尾”比如user_activity_score用户活跃分95%的用户分数在0-100之间但头部100个KOL分数高达5000-20000。默认直方图把0-20000一刀切成100份每份宽200结果0-100这个密集区只占半份直方图桶所有细节被抹平而5000-20000这个稀疏区却被强行塞进99个桶里每个桶只含1-2个值毫无统计意义。优化器看到“user_activity_score 1000”只能根据那个被严重稀释的直方图桶估算结果要么过度乐观认为高分用户很多该走索引要么过度悲观认为高分用户极少该走顺序扫描。我们曾在一个社交APP的推荐流服务中遇到此问题feed_rank_score列的长尾特性导致 5000的查询始终无法命中idx_feed_rank即使该索引选择性极高。解决方案不是调高statistics_target那会让整个库的ANALYZE变慢而是为高分区间单独创建一个聚焦统计对象先用CREATE STATISTICS stats_high_score ON user_id, feed_rank_score FROM feeds WHERE feed_rank_score 5000;定义子集再对其执行ANALYZE feeds (stats_high_score);。这样优化器对高分用户的分布认知就从“模糊的1%”变成了“清晰的TOP100用户ID列表精确的分数分布密度”索引选择率从32%提升至99.7%。2.3 硬伤三采样策略的“时效性悖论”——当你的数据每分钟都在爆炸式增长默认ANALYZE采用随机采样default_statistics_target本质是采样行数的对数尺度对静态表友好但对实时写入的流式表如IoT设备上报、日志采集、交易流水是灾难。假设一张device_metrics表每秒插入500条记录ANALYZE按默认100目标执行实际采样约10000行但若ANALYZE间隔设为1小时这1小时内新插入的180万行数据在采样中占比不足0.5%其分布特征如新设备型号的出现、异常指标的突增完全无法反映。更糟的是ANALYZE本身会锁表虽是轻量级锁高频执行影响写入吞吐。我们服务过一家车联网公司其vehicle_telemetry表每分钟新增20万行ANALYZE设为每10分钟一次结果WHERE vehicle_type electric_truck AND battery_level 20的查询因统计信息未及时捕获新上线的电动卡车队列持续走错执行计划导致告警延迟超5分钟。破局点在于解耦统计信息的“生成”与“应用”利用pg_statistic_ext系统表手动注入统计信息需超级用户权限或更安全地通过pg_create_logical_replication_slot配合自定义逻辑解码插件在数据写入时同步计算关键分布指标并定期用UPDATE pg_statistic_ext SET ...刷新。这不再是“定时体检”而是“实时心电监护”。提示自定义统计信息不是银弹它解决的是“优化器认知偏差”而非“SQL逻辑错误”或“硬件瓶颈”。如果你的查询慢是因为没建索引、SELECT *滥用、或JOIN笛卡尔积先做基础优化。自定义统计信息是给已经打磨好的SQL配上一副精准的“数据透视镜”。3. 核心实现路径从声明式定义到动态注入的三层能力矩阵PostgreSQL自定义统计信息并非单一功能而是一个分层演进的能力矩阵从低侵入、易上手的声明式定义到高自由度、需深度理解的动态注入覆盖不同风险偏好与技术深度的使用者。我将其划分为三层基础层CREATE STATISTICS、增强层扩展统计函数与自定义采样、掌控层系统表直写与逻辑复制联动。每一层都对应明确的适用场景、实施成本与收益阈值。3.1 基础层CREATE STATISTICS——用一条SQL教会优化器“看组合”这是官方支持、最安全、最推荐的起点。PostgreSQL 10 引入的CREATE STATISTICS命令允许你显式声明“哪些列的组合需要被统计”并指定统计类型ndistinct、dependencies、mcv。它的核心价值在于零代码、零重启、零风险且效果立竿见影。以电商订单表为例修复前述“北京用户手机类目”预估偏差只需三步声明统计对象CREATE STATISTICS orders_geo_cat_stats ON user_id, product_category FROM orders;这行命令告诉PostgreSQL“请为orders表的user_id和product_category这两列的组合建立一个名为orders_geo_cat_stats的统计对象”。注意此时并未生成任何数据只是注册了一个“统计需求”。执行针对性ANALYZEANALYZE orders (orders_geo_cat_stats);这才是关键一步。ANALYZE不再扫描整张表而是仅针对user_id和product_category的组合值进行深度采样与分析。它会计算ndistinct该组合的唯一值总数如北京用户×手机类目有多少种有效组合dependencies两列间的函数依赖强度如user_id确定后product_category的可选范围是否大幅缩小mcv最常见的组合值如(user_id1001, product_categorymobile_phone)出现频率最高。验证效果 查询pg_statistic_ext视图确认统计对象已激活SELECT stxname, stxkeys, stxkind FROM pg_statistic_ext WHERE stxname orders_geo_cat_stats;再执行原慢查询的EXPLAIN你会看到rows预估值从4500跃升至27.8万与真实值28万高度吻合执行计划也自动切换回Index Scan using idx_order_time。实操心得CREATE STATISTICS的威力常被低估。我们曾在一个金融风控系统中为transaction_amount和risk_score组合创建统计对象使“高金额高风险”联合查询的预估准确率从41%提升至96%避免了因误判导致的数千笔误拦截。记住不要试图为所有列组合都创建统计对象——优先选择WHERE条件中高频共现、且单列统计无法解释其选择性的列对。一个表3-5个高质量的组合统计远胜于20个低效的冗余统计。3.2 增强层扩展统计函数与自定义采样——当默认算法不够“聪明”基础层解决了“统计什么”但没解决“怎么统计”。默认ANALYZE的采样算法是通用的对特殊数据分布可能力不从心。这时你需要“增强层”利用PostgreSQL的扩展机制植入更智能的统计逻辑。扩展统计函数Statistics FunctionsPostgreSQL允许你编写C或PL/pgSQL函数作为统计信息的“计算引擎”。例如对于前述feed_rank_score的长尾问题你可以编写一个tail_density_estimate()函数专门对score 5000的区间进行非线性采样如对数间隔采样并返回一个包含min,max,avg,stddev,top_100_ids的复合类型。然后在CREATE STATISTICS中引用它CREATE STATISTICS stats_tail_rank ON user_id, feed_rank_score FROM feeds USING tail_density_estimate;这样ANALYZE调用的不再是内置算法而是你的定制化逻辑。自定义采样Custom Sampling更进一步你可以绕过ANALYZE的随机采样直接构造采样数据集。这适用于数据有明确业务分区的场景。比如日志表app_logs按log_date分区你知道log_date 2024-06-01这一天的数据分布最具代表性因是大促日。那么可以创建临时表存储该分区的完整样本CREATE TEMP TABLE logs_sample AS SELECT level, module, duration_ms FROM app_logs_20240601;对临时表执行ANALYZE生成高精度统计将pg_statistic中的相关记录通过INSERT INTO pg_statistic ... SELECT ... FROM pg_statistic WHERE ...方式复制到主表app_logs的对应列统计项中需超级用户权限且操作前务必备份pg_statistic。注意增强层操作需谨慎。pg_statistic是核心系统表直接写入有风险。我们建议所有自定义采样操作必须在测试库充分验证并编写回滚脚本如DELETE FROM pg_statistic WHERE ...。生产环境首次使用务必安排在低峰期并监控pg_statistic的stavalid字段确保其有效性。3.3 掌控层系统表直写与逻辑复制联动——让统计信息“活”起来当业务要求统计信息与数据变更“零延迟”同步时前两层都显得滞后。掌控层的目标是让统计信息成为数据流的一个环节而非事后的补救措施。这需要深入PostgreSQL的WALWrite-Ahead Log机制。系统表直写Direct pg_statistic Update这是终极手段也是风险最高的。原理是监听业务数据的INSERT/UPDATE事件可通过触发器或逻辑复制当检测到关键数据变化如新product_category值出现、user_activity_score突破阈值立即计算新的分布参数并用UPDATE pg_statistic SET ...直接更新。例如为实时维护orders表的status列MCVMost Common Values-- 在orders表上创建触发器函数 CREATE OR REPLACE FUNCTION update_status_mcv() RETURNS TRIGGER AS $$ DECLARE new_mcv TEXT[]; BEGIN -- 计算当前status的TOP 10值 SELECT ARRAY_AGG(status) INTO new_mcv FROM (SELECT status FROM orders GROUP BY status ORDER BY COUNT(*) DESC LIMIT 10) t; -- 直接更新pg_statistic需超级用户 UPDATE pg_statistic SET stadistinct -1, -- 表示唯一值数未知但MCV有效 stavalues1 new_mcv::text[] WHERE starelid orders::regclass AND staattnum (SELECT attnum FROM pg_attribute WHERE attrelid orders::regclass AND attname status); RETURN NEW; END; $$ LANGUAGE plpgsql; CREATE TRIGGER trg_update_status_mcv AFTER INSERT OR UPDATE OF status ON orders FOR EACH STATEMENT EXECUTE FUNCTION update_status_mcv();此方案让status列的MCV永远是最新的WHERE status IN (paid,shipped)的预估永不偏离。但代价是每次INSERT/UPDATE都触发一次系统表更新性能开销巨大且极易因并发冲突导致统计信息损坏。逻辑复制联动Logical Replication Integration更优雅的方案。利用PostgreSQL 10的逻辑复制功能创建一个专用的“统计信息消费者”进程在主库创建逻辑复制槽SELECT * FROM pg_create_logical_replication_slot(stats_slot, pgoutput);编写一个Python消费者使用wal2json或decoderbufs插件订阅orders表的变更消费者收到INSERT事件后不落库而是解析status字段累加计数到内存哈希表当哈希表中某个status值计数超过阈值如1000或每5分钟消费者将TOP 10状态及计数通过psql命令调用ANALYZE orders (status)或更高效地调用一个预编译的C函数批量更新pg_statistic。实操心得掌控层不是为了炫技而是解决特定痛点。我们为一家实时竞价广告平台实施此方案其ad_bids表每秒数万次INSERTbid_price和advertiser_id的组合统计必须毫秒级更新否则出价策略失效。最终采用逻辑复制联动将统计信息延迟从分钟级压缩至200ms内CPM千次展示成本波动率下降63%。记住掌控层的复杂度是指数级的只有当基础层和增强层都无法满足SLA服务等级协议时才值得投入。4. 实战全流程从诊断、设计到上线的七步法纸上谈兵终觉浅绝知此事要躬行。下面以一个真实复现的案例——“用户行为日志表user_events查询性能陡降”——完整演示自定义统计信息的落地七步法。该表日均写入800万行核心查询为SELECT COUNT(*) FROM user_events WHERE event_type click AND page_id product_detail AND event_time 2024-06-01过去稳定在120ms上周起飙升至3.2秒。4.1 第一步精准定位——用EXPLAIN ANALYZE揪出“元凶”不假思索地ANALYZE是最大误区。先做诊断EXPLAIN (ANALYZE, BUFFERS) SELECT COUNT(*) FROM user_events WHERE event_type click AND page_id product_detail AND event_time 2024-06-01;关键输出- Bitmap Heap Scan on user_events (cost1245.67..18923.45 rows1024 width0) (actual time210.321..3198.742 rows12856 loops1) Recheck Cond: ((event_type click::text) AND (page_id product_detail::text) AND (event_time 2024-06-01 00:00:00::timestamp without time zone)) Rows Removed by Index Recheck: 184231 Buffers: shared hit1245 read8923 - BitmapAnd (cost1245.67..1245.67 rows1024 width0) (actual time209.842..209.842 rows0 loops1) - Bitmap Index Scan on idx_event_type (cost0.00..321.45 rows15678 width0) (actual time12.345..12.345 rows15678 loops1) - Bitmap Index Scan on idx_page_id (cost0.00..456.78 rows21034 width0) (actual time15.678..15.678 rows21034 loops1) - Bitmap Index Scan on idx_event_time (cost0.00..467.44 rows23456 width0) (actual time181.819..181.819 rows23456 loops1)发现致命线索BitmapAnd节点显示“rows0 loops1”即三个位图索引的交集为空但actual rows12856证明数据真实存在。这说明优化器完全不相信这三个条件能同时成立其预估交集为0导致后续执行计划彻底失效。根源必在统计信息——它认为event_typeclick、page_idproduct_detail、event_time...三者同时发生的概率为0。4.2 第二步深挖数据分布——用SQL探针揭开真相检查单列统计SELECT schemaname, tablename, attname, n_distinct, most_common_vals, histogram_bounds FROM pg_stats WHERE tablename user_events AND attname IN (event_type, page_id, event_time);发现event_type的most_common_vals包含clickpage_id包含product_detailevent_time直方图也覆盖2024-06-01。单看都没问题。问题出在组合event_type和page_id的组合clickproduct_detail是否常见执行SELECT event_type, page_id, COUNT(*) as cnt FROM user_events WHERE event_time 2024-06-01 GROUP BY event_type, page_id ORDER BY cnt DESC LIMIT 5;结果震惊(click, product_detail)以287万次高居榜首占当日总点击量的42%但默认统计信息对此一无所知。4.3 第三步设计统计方案——选择最经济的实现路径基于诊断event_type和page_id的强关联是核心。event_time是范围条件其直方图已足够。因此基础层的CREATE STATISTICS是最佳选择无需上增强层或掌控层。目标创建event_type_page_stats覆盖event_type和page_id。4.4 第四步构建与验证——在测试库完成闭环创建统计对象CREATE STATISTICS event_type_page_stats ON event_type, page_id FROM user_events;执行针对性ANALYZEANALYZE user_events (event_type_page_stats);验证统计对象激活SELECT stxname, stxkeys, stxkind FROM pg_statistic_ext WHERE stxname event_type_page_stats; -- 返回一行stxkind包含ddependencies和mmcv证明成功重跑EXPLAIN ANALYZEEXPLAIN (ANALYZE, BUFFERS) SELECT COUNT(*) FROM user_events WHERE event_type click AND page_id product_detail AND event_time 2024-06-01;关键变化- Index Only Scan using idx_event_type_page_time on user_events (cost0.56..1245.67 rows287123 width0) (actual time1.234..118.567 rows12856 loops1) Index Cond: ((event_type click::text) AND (page_id product_detail::text) AND (event_time 2024-06-01 00:00:00::timestamp without time zone))rows287123预估vsrows12856实际偏差22倍虽不完美但已从“0 vs 12856”的荒谬变为可接受的工程误差。执行时间回落至135ms。4.5 第五步灰度上线——最小化风险的发布策略生产环境绝不ANALYZE全表。采用灰度Step 1在凌晨低峰期对user_events表执行ANALYZE user_events (event_type_page_stats);耗时18秒。Step 2立即监控pg_stat_activity确认无长事务阻塞检查pg_stat_database的blks_read确认无异常IO。Step 3选取5个核心业务查询用EXPLAIN对比预估行数变化确保无负向影响。Step 4观察15分钟QPS、P99延迟、CPU负载确认平稳。Step 5若一切正常通知所有相关方该统计对象已生效。4.6 第六步长效监控——让自定义统计“可感知、可度量”创建监控视图跟踪统计对象健康度-- 创建视图统计对象新鲜度 CREATE VIEW v_stats_freshness AS SELECT s.stxname as stats_name, c.relname as table_name, pg_size_pretty(pg_total_relation_size(c.oid)) as table_size, (now() - s.stxlastanalyze)::interval as last_analyze_ago, CASE WHEN (now() - s.stxlastanalyze) INTERVAL 24 hours THEN STALE WHEN (now() - s.stxlastanalyze) INTERVAL 1 hour THEN WARNING ELSE FRESH END as freshness_status FROM pg_statistic_ext s JOIN pg_class c ON s.stxrelid c.oid WHERE s.stxname LIKE %event%; -- 查询SELECT * FROM v_stats_freshness;将此视图接入PrometheusGrafana设置告警freshness_status STALE持续30分钟即触发运维工单。4.7 第七步效果复盘——用数据说话量化ROI上线一周后汇总数据指标上线前周均上线后周均变化目标查询P99延迟3200ms138ms↓95.7%目标查询QPS42187↑345%user_events表CPU占用率68%31%↓54%因该查询引发的告警次数12次/天0次/天↓100%结论投入2人日诊断实施换来核心链路稳定性质的飞跃。这不仅是性能数字的提升更是业务信心的重建——产品团队敢基于此表做实时漏斗分析运营团队敢在大促期间高频调用该查询做秒级决策。常见问题速查表问题现象可能原因排查命令解决方案CREATE STATISTICS后ANALYZE报错column does not exist列名大小写不匹配或列不存在于表中\d user_events使用双引号包裹列名ON event_type, page_idEXPLAIN显示预估行数未变化统计对象未激活或ANALYZE未执行SELECT * FROM pg_statistic_ext WHERE stxnamexxx;确认stxlastanalyze不为空否则重执行ANALYZE查询变慢且EXPLAIN显示Bitmap Heap Scan大量Recheck Cond统计信息过时导致位图过大EXPLAIN (ANALYZE) SELECT * FROM xxx WHERE yyy LIMIT 10;执行ANALYZE table_name (stats_name);pg_statistic_ext中stxkind为空PostgreSQL版本低于10SELECT version();升级至PG 105. 避坑指南那些文档不会写的血泪教训与独家技巧在数十个生产环境的自定义统计信息项目中踩过的坑比读过的文档还多。这些经验没有一条来自手册全部来自深夜的线上故障、反复的压测对比、以及和PostgreSQL源码的“搏斗”。它们不性感但保命。5.1 陷阱一“ANALYZE全表”是懒政不是捷径新手最容易犯的错就是看到查询慢不管三七二十一ANALYZE user_events;。这看似勤奋实则是埋雷。ANALYZE会扫描整张表对800万行的user_events默认采样可能耗时数分钟期间持有AccessShareLock阻塞VACUUM和部分DDL。更糟的是它会冲刷掉所有已有的、精心设计的组合统计。因为ANALYZE table_name;是“全量重置”而非“增量更新”。我们曾在一个金融核心库目睹此景DBA为修复一个慢查询执行ANALYZE accounts;结果导致依赖accounts.user_id, accounts.status组合统计的12个关键报表查询全部回归慢速故障持续47分钟。正确姿势永远是ANALYZE table_name (stats_name);。为防手误我们在所有生产库的psql配置中设置了别名alias analyzeecho ERROR: Never use ANALYZE without stats name! Use: ANALYZE table (stats_name); 2。5.2 陷阱二statistics_target不是越大越好而是“刚刚好”default_statistics_target默认100和单表ALTER TABLE ... SET (STATISTICS N)控制着采样粒度。调高它统计更准但ANALYZE更慢、pg_statistic更大、内存消耗更高。我们做过极限测试将statistics_target设为10000对1亿行表执行ANALYZE耗时从23秒飙升至18分钟pg_statistic体积膨胀12倍导致shared_buffers缓存压力剧增其他查询响应变慢。黄金法则是对高频查询、高选择性列设为500-1000对低频、低选择性列保持默认100对超大表10亿行宁可创建多个小范围的组合统计也不盲目拉高target。一个简单公式optimal_target ≈ 100 * log10(table_row_count / 1000000)。对800万行表log10(8)≈0.9100*0.990所以默认100已足够。5.3 技巧一用pg_stats_ext替代pg_stats获取组合统计详情pg_stats只显示单列统计pg_stats_ext才是组合统计的“真相之眼”。但它不直接暴露需用pg_get_statisticsobjdef()函数解析-- 获取event_type_page_stats的详细定义 SELECT pg_get_statisticsobjdef(oid) FROM pg_statistic_ext WHERE stxname event_type_page_stats; -- 返回CREATE STATISTICS event_type_page_stats ON event_type, page_id FROM user_events -- 查看其内部统计值需超级用户 SELECT stxkeys, stxkind, stxndistinct, stxdependencies, stxmcv FROM pg_statistic_ext WHERE stxname event_type_page_stats;stxmcv字段是bytea类型需用pg_mcv_list_items()函数解码SELECT m.* FROM pg_statistic_ext s, LATERAL pg_mcv_list_items(stxmcv) m WHERE s.stxname event_type_page_stats; -- 返回value, frequency, base_frequency —— 这就是优化器看到的“MCV清单”这个技巧让我们在排查“为何预估还是不准”时能直接看到优化器的“内心OS”而不是靠猜。5.4 技巧二为“时间窗口”创建虚拟统计规避直方图缺陷对event_time这类时间戳列直方图在“最近数据”上总是失真。一个独创技巧创建一个“虚拟列”统计。不修改表结构而是用表达式-- 创建基于时间窗口的统计如将event_time映射为“小时段” CREATE STATISTICS event_time_hour_stats ON date_part(hour, event_time) FROM user_events;date_part(hour, event_time)返回0-23的整数其分布是典型的离散均匀分布直方图处理得极好。然后在查询中将event_time 2024-06-01重写为date_part(
返回列表