FEATURED · 精选文章

PostgreSQL执行计划深度解析:从原理到实战的性能调优指南

发布时间 / 2026/8/16 2:37:17
来源 / 创域科博编辑部
栏目 / 资讯中心
PostgreSQL执行计划深度解析:从原理到实战的性能调优指南 1. 项目概述为什么执行计划是数据库性能的“X光片”如果你在维护一个基于PostgreSQL的应用某天突然收到用户反馈“页面加载好慢”或者监控告警显示某个接口的响应时间飙升你的第一反应是什么十有八九你会想到是数据库查询出了问题。但面对成百上千行复杂的SQL如何快速定位瓶颈是索引没生效还是表连接顺序不对或者是数据量太大导致的全表扫描这时候查看SQL的执行计划就成了我们数据库从业者最核心、最直接的诊断工具。你可以把执行计划想象成数据库引擎为执行一条SQL语句所制定的“作战蓝图”。它详细描述了PostgreSQL将如何获取数据是先扫描A表还是B表用哪个索引如何将多张表的数据合并起来每一步预估会处理多少行数据消耗多少成本这份蓝图就是我们优化慢查询、理解数据库行为的“X光片”。没有它性能优化就像蒙着眼睛修车全凭感觉有了它你就能精准地知道“发动机”哪里在空转哪里阻力过大。掌握查看和分析执行计划是每一位后端开发、DBA乃至数据工程师的必备技能。无论你是刚接触PostgreSQL的新手还是已经写过无数SQL的老手深入理解执行计划都能让你从“会写SQL”进阶到“懂SQL”从而写出更高效、更稳定的数据库查询从容应对各种性能挑战。接下来我将结合十多年的实战经验带你从零开始彻底搞懂PostgreSQL执行计划的查看、解读与实战优化。2. 执行计划的核心原理与获取方式在深入实操之前我们必须先理解执行计划是什么以及PostgreSQL是如何生成它的。这能帮助你在后续分析时不仅知道“是什么”更明白“为什么”。2.1 执行计划的本质查询优化器的“决策报告”当你向PostgreSQL提交一条SQL语句时它并不会立刻吭哧吭哧地去磁盘上捞数据。相反它会先经过一个非常复杂的组件——查询优化器。优化器的任务是在所有可能的执行路径中选择它认为成本最低的那一条。这个过程包括解析SQL语法、检查表结构和索引、收集数据库的统计信息比如表有多少行、数据分布如何然后基于一套成本模型进行估算。这个成本模型综合考虑了CPU处理、磁盘I/O、内存使用等多个因素。最终优化器输出的“最优”执行方案就是我们要看的执行计划。所以执行计划本质上是一份预测报告。它展示的是PostgreSQL“打算”怎么执行而不是实际执行后的结果。这一点至关重要因为优化器基于的统计信息可能过时或者成本模型在某些复杂场景下估算不准导致“计划很美好现实很骨感”。这也是为什么我们有时需要用到实际执行分析的原因。2.2 获取执行计划的三种武器EXPLAIN, EXPLAIN ANALYZE, EXPLAIN (BUFFERS, VERBOSE)PostgreSQL提供了功能强大的EXPLAIN命令来获取执行计划。根据你想了解的深度和细节主要有三种用法1. EXPLAIN基础计划查看这是最常用的命令它展示优化器生成的计划但不真正执行SQL。EXPLAIN SELECT * FROM users WHERE age 30;你会得到一个树形结构的文本输出描述了执行的步骤。它的优点是零成本、速度快适合在开发环境反复调整SQL时查看。但请注意它给出的行数rows和成本cost都是估算值。2. EXPLAIN ANALYZE实际执行分析这个命令会真正执行后面的SQL语句然后返回执行计划并附上每一步实际花费的时间、实际返回的行数。EXPLAIN ANALYZE SELECT * FROM users WHERE age 30;这是性能调优的“金标准”。通过对比EXPLAIN的估算值和EXPLAIN ANALYZE的实际值你可以发现统计信息是否准确、成本估算是否偏差。警告由于它会真实执行SQL在生产环境对写操作INSERT, UPDATE, DELETE或大数据量查询使用前务必谨慎最好在测试库或通过事务回滚来操作。3. EXPLAIN (OPTION, ...)高级诊断模式这是EXPLAIN的增强版通过添加选项来获取更详细的信息。最常用的组合是EXPLAIN (ANALYZE, BUFFERS, VERBOSE) SELECT * FROM orders o JOIN users u ON o.user_id u.id;ANALYZE同EXPLAIN ANALYZE实际执行。BUFFERS显示缓存命中的详细信息。这是分析I/O性能的关键。你会看到shared hit从共享缓存读取、read从磁盘读取等数据。如果read很多说明查询可能缺乏有效缓存或者需要优化索引减少磁盘访问。VERBOSE显示额外的信息如输出列的详细信息。实操心得命令使用场景选择在日常工作中我通常遵循这个流程在开发环境先用EXPLAIN快速验证索引是否生效、连接顺序是否合理。当需要深度分析一个已知的慢查询时则在测试环境使用EXPLAIN (ANALYZE, BUFFERS)。BUFFERS信息对于判断查询是“CPU密集型”还是“I/O密集型”极其有用能帮你决定优化方向是加索引、调内存还是优化SQL逻辑。3. 执行计划节点深度解析从Scan到Join读懂执行计划关键在于理解其中每一个“节点”Node。每个节点代表一个基础操作计划树就是这些节点的组合。下面我们来拆解最常见的核心节点。3.1 数据扫描节点数据从哪里来这是执行计划的起点决定了数据获取的方式和效率。Seq Scan顺序扫描最基础也通常是最慢的方式。它从表的第一行开始逐行读取整个表或查询中指定的部分。当你看到这个并且rows值很大时就要警惕了——这通常是性能问题的信号。但并非所有顺序扫描都是坏事对于小表或需要读取大部分数据的情况它可能比用索引更快。-- 典型的无索引或查询条件选择性低的顺序扫描 EXPLAIN SELECT * FROM log_table WHERE status INFO; -- 如果statusINFO的记录占90%Seq Scan是合理的Index Scan索引扫描通过索引来定位数据。它先读取索引条目然后根据索引中的指针通常是TID去表中取出对应的完整行。适用于通过索引能过滤掉大量数据的查询。-- 假设在age字段上有索引 EXPLAIN SELECT * FROM users WHERE age 25;Index Only Scan仅索引扫描这是性能最好的扫描方式之一。当查询所需的所有列都包含在索引中时PostgreSQL可以只扫描索引而完全不需要去访问表数据Heap。这能极大减少I/O。-- 假设存在索引 (age, name)而查询只取这两个字段 EXPLAIN SELECT age, name FROM users WHERE age BETWEEN 20 AND 30;注意事项为了实现Index Only Scan你需要确保索引类型是B-tree默认并且表的visibility map信息足够新由VACUUM维护。如果执行计划显示Index Scan后面跟着Heap Fetches说明未能完全实现仅索引扫描仍需回表。Bitmap Index Scan Bitmap Heap Scan这是一种折中方案。首先通过Bitmap Index Scan快速扫描索引在内存中创建一个位图Bitmap标记哪些表数据页包含目标行。然后通过Bitmap Heap Scan按照数据页的物理顺序去读取这些页。它特别适合多条件OR查询或者条件选择性不高不低的情况能避免随机I/O将其转换为更高效的顺序I/O。EXPLAIN SELECT * FROM users WHERE age 20 OR city_id 1;3.2 连接节点数据如何合并当查询涉及多张表时就需要连接操作。PostgreSQL主要有三种连接策略选择哪种对性能影响巨大。Nested Loop嵌套循环连接工作原理像双重for循环。对外表驱动表的每一行都去内表被驱动表中扫描一遍寻找匹配的行。适用场景其中一张表非常小例如在内存中或者内表上有高效的索引针对连接条件的索引。当数据量一大它的性能是O(N*M)级别的会急剧下降。计划显示它会包含两个子计划一个Outer一个Inner。Hash Join哈希连接工作原理先读取较小的那张表构建表在内存中为其建立一个哈希表以连接键为Key。然后全量扫描较大的那张表探测表对每一行计算连接键的哈希值去哈希表中查找匹配项。适用场景最适合处理没有索引的大表等值连接。它需要足够的内存work_mem来存放哈希表。如果内存不足会溢出到磁盘性能大打折扣。如何判断计划中会明确显示Hash和Hash Join节点。Merge Join归并连接工作原理要求两张表的数据都按照连接键预先排序。然后像合并两个有序链表一样同时遍历两张表一次性完成匹配。适用场景当连接条件是非等值操作如,,,或者两张表都已经在连接键上有索引索引本身是有序的时它可能比Hash Join更高效。前提条件执行计划中在Merge Join节点之上你一定会看到对两个输入进行Sort的节点除非数据本身已有序如索引扫描。核心避坑技巧连接顺序的选择PostgreSQL优化器会自动决定连接顺序。但你可以通过观察执行计划来判断其选择是否合理。一个基本原则是让中间结果集最小的连接优先执行。你可以通过EXPLAIN输出的rows估算值来评估。如果发现优化器选择的顺序不佳可以考虑使用/* Leading(table1 table2) */风格的提示需安装pg_hint_plan扩展或调整join_collapse_limit等参数来影响优化器但这是高级技巧需谨慎使用。3.3 其他关键节点排序、聚合与物化Sort排序当查询包含ORDER BY、GROUP BY非哈希聚合时或DISTINCT时可能出现。排序是非常消耗CPU和内存的操作。如果Sort节点出现在计划顶部且涉及大量数据是主要的性能瓶颈。优化思路是为ORDER BY/GROUP BY的字段建立索引让数据“天生有序”从而避免排序操作。Aggregate聚合处理SUM,COUNT,AVG等聚合函数。分为HashAggregate在内存中建哈希表分组和GroupAggregate要求输入数据已按分组键排序。前者快但耗内存后者在有序数据上效率高。Materialize物化优化器会将一个子查询或CTECommon Table Expression的结果具体化到一个临时存储中供后续多次使用。对于会被多次引用的复杂子查询这能避免重复计算。但物化本身有开销且占用临时空间。Limit限制对应LIMIT子句。一个好的执行计划会尽早应用Limit以减少上游需要处理的数据量。例如如果能在索引扫描后立刻Limit就能避免对大量数据进行不必要的排序或聚合。4. 实战一步步解读与分析一个复杂执行计划理论说再多不如看一个真实的例子。假设我们有一个电商数据库现在要分析一条“查询用户最近订单详情”的慢SQL。-- 示例查询查找用户‘张三’在过去一个月内的所有订单并按订单金额降序排列 EXPLAIN (ANALYZE, BUFFERS, VERBOSE) SELECT u.name, o.order_id, o.order_date, o.amount, p.product_name FROM users u JOIN orders o ON u.user_id o.user_id JOIN order_items oi ON o.order_id oi.order_id JOIN products p ON oi.product_id p.product_id WHERE u.name ‘张三‘ AND o.order_date CURRENT_DATE - INTERVAL ‘30 day‘ ORDER BY o.amount DESC LIMIT 50;假设我们得到的执行计划文本如下为简洁已做精简和注释Limit (cost12784.33..12784.46 rows50 width72) (actual time350.122..350.135 rows50 loops1) Output: u.name, o.order_id, o.order_date, o.amount, p.product_name Buffers: shared hit1520 read2850 - Sort (cost12784.33..12859.33 rows30000 width72) (actual time350.120..350.128 rows50 loops1) Output: u.name, o.order_id, o.order_date, o.amount, p.product_name Sort Key: o.amount DESC Sort Method: top-N heapsort Memory: 34kB Buffers: shared hit1520 read2850 - Hash Join (cost445.20..11959.20 rows30000 width72) (actual time12.345..280.654 rows30012 loops1) Output: u.name, o.order_id, o.order_date, o.amount, p.product_name Hash Cond: (o.user_id u.user_id) Buffers: shared hit1200 read2200 - Nested Loop (cost220.15..11200.15 rows50000 width40) (actual time6.789..200.123 rows50015 loops1) Output: o.order_id, o.order_date, o.amount, o.user_id, p.product_name Buffers: shared hit800 read1800 - Hash Join (cost219.70..8500.70 rows50000 width36) (actual time6.760..150.456 rows50015 loops1) Output: o.order_id, o.order_date, o.amount, o.user_id, oi.product_id Hash Cond: (oi.order_id o.order_id) Buffers: shared hit600 read1500 - Seq Scan on order_items oi (cost0.00..7000.00 rows200000 width8) (actual time0.010..80.123 rows199850 loops1) Output: oi.order_id, oi.product_id Buffers: shared hit500 read1000 - Hash (cost200.00..200.00 rows50000 width32) (actual time6.740..6.740 rows50015 loops1) Output: o.order_id, o.order_date, o.amount, o.user_id Buckets: 65536 Batches: 1 Memory Usage: 4200kB Buffers: shared hit100 read500 - Index Scan using idx_orders_date on orders o (cost0.00..200.00 rows50000 width32) (actual time0.020..3.500 rows50015 loops1) Output: o.order_id, o.order_date, o.amount, o.user_id Index Cond: (o.order_date (CURRENT_DATE - ‘30 days‘::interval)) Buffers: shared hit100 read500 - Index Scan using products_pkey on products p (cost0.45..0.05 rows1 width36) (actual time0.001..0.001 rows1 loops50015) Output: p.product_name, p.product_id Index Cond: (p.product_id oi.product_id) Buffers: shared hit200 read300 - Hash (cost224.00..224.00 rows1 width36) (actual time5.550..5.550 rows1 loops1) Output: u.name, u.user_id Buckets: 1024 Batches: 1 Memory Usage: 1kB Buffers: shared hit400 read0 - Index Scan using idx_users_name on users u (cost0.00..224.00 rows1 width36) (actual time5.545..5.547 rows1 loops1) Output: u.name, u.user_id Index Cond: (u.name ‘张三‘::text) Buffers: shared hit400让我们逐层拆解这个计划并分析潜在问题整体流程从最内层往外看首先通过idx_users_name索引快速找到用户“张三”rows1非常高效。同时通过idx_orders_date索引扫描获取最近30天的所有订单rows50015。orders的结果被构建成哈希表Hash节点内存使用4.2MB。对order_items表进行全表顺序扫描Seq Scanrows199850这是一个危险信号用order_items的数据去探测第2步建立的订单哈希表完成第一次Hash Join。将上一步的结果每个订单项与products表通过主键进行Nested Loop连接因为products表有主键索引每次循环成本极低。将第1步找到的“张三”用户也构建成一个小哈希表。将第5步的结果所有订单项及商品信息与“张三”的哈希表进行第二次Hash Join过滤出只属于张三的订单。对最终结果rows30012进行排序Sort以满足ORDER BY amount DESC。最后从排序结果中取前50条Limit。关键性能问题诊断最大瓶颈对order_items表的全表顺序扫描。计划显示它扫描了近20万行rows199850并且产生了大量的缓冲读取Buffers: read1000。这是整个查询耗时350ms的主要贡献者。为什么优化器不走索引很可能是因为order_items表在连接条件(oi.order_id o.order_id)上没有高效的索引或者优化器估算走索引的成本比全表扫描更高可能统计信息有误。排序开销虽然排序只用了34KB内存top-N heapsort并且因为Limit的存在排序量不大。但如果取消LIMIT 50这个Sort节点需要对3万行数据进行全排序开销会显著增加。连接顺序评估计划选择先连接orders和order_items产生一个5万行的中间结果然后再用users表过滤。由于users的过滤条件非常强rows1也许让users表更早参与连接能极大减少中间结果集的大小。这取决于数据分布需要进一步分析。优化建议首要优化为order_items.order_id添加索引。这是最立竿见影的优化。创建索引CREATE INDEX idx_order_items_order_id ON order_items(order_id);。创建后对order_items的扫描很可能会从Seq Scan变为Index Scan或Bitmap Index Scan性能将有数量级的提升。考虑复合索引如果orders表上经常按order_date和user_id查询可以考虑创建复合索引(user_id, order_date)或(order_date, user_id)具体顺序取决于哪个字段的选择性更高。更新统计信息在创建新索引后运行ANALYZE table_name;来更新表的统计信息帮助优化器做出更准确的判断。验证连接顺序可以通过临时调整join_collapse_limit参数设为1强制优化器按SQL中写的顺序users - orders - ...进行连接测试性能是否有提升。但这只是诊断手段长期方案还是确保统计信息准确和索引合理。通过这个案例你可以看到分析执行计划是一个“找茬”的过程寻找那些rows估算值与实际值偏差大的节点、寻找代价cost最高的节点、寻找Seq Scan和巨大的Hash或Sort操作。找到它们就找到了优化的突破口。5. 高级技巧与性能调优实战掌握了基础解读我们来看看一些能让你在性能调优中游刃有余的高级技巧和实战场景。5.1 利用可视化工具pgAdmin, DBeaver, 与 explain.dalibo.com长时间阅读文本格式的执行计划容易疲劳且不直观。幸运的是我们有强大的可视化工具。pgAdminPostgreSQL官方生态工具。在查询工具中执行EXPLAIN (ANALYZE, BUFFERS)后点击工具栏的**“执行计划”**按钮闪电图标旁边会生成一个树形图。图形中节点的宽度代表其相对成本颜色深浅可能代表实际耗时一目了然地看到瓶颈所在。DBeaver通用的数据库客户端。同样支持图形化显示执行计划界面友好。explain.dalibo.com一个在线的、功能极其强大的免费工具。你将文本格式的执行计划粘贴进去它能生成交互式图表。我最喜欢它的三个功能节点耗时占比图用色块大小清晰展示每个节点的实际时间占比。估算 vs 实际对比用图表对比每个节点的估算行数和实际行数快速定位统计信息失准的问题。悬停提示鼠标悬停在节点上显示详细信息无需阅读冗长文本。实操心得可视化工具的选择对于日常快速检查我使用pgAdmin的图形化功能。当需要进行深度性能分析或向团队分享时我必定使用explain.dalibo.com。它的可视化报告非常专业能让人在几秒钟内抓住核心问题。记得在分享或存档时将分析链接或截图附上比大段文本更有说服力。5.2 精准控制使用执行计划提示HintsPostgreSQL的优化器很强大但并非万能。有时它会因为统计信息偏差或成本模型限制选择一个次优的计划。与其他数据库如Oracle、MySQL不同PostgreSQL核心没有内置的提示语法如/* INDEX(table_name index_name) */。但社区提供了强大的扩展——pg_hint_plan。安装与使用简要步骤在数据库中安装扩展CREATE EXTENSION pg_hint_plan;在你的SQL注释中使用特殊格式的提示/* IndexScan(orders idx_orders_date) HashJoin(orders order_items) Leading((users (orders order_items))) */ EXPLAIN ANALYZE SELECT ... FROM users u JOIN orders o ...;上面的提示告诉优化器对orders表强制使用idx_orders_date索引扫描强制使用HashJoin方式连接orders和order_items并强制指定连接顺序。重要警告提示是最后的手段使用提示就像给数据库引擎做“手动挡”驾驶。它绕过了优化器的自主决策。一旦数据分布发生变化例如原本很小的表变得巨大强制使用的提示可能让性能变得更糟。我的原则是首先尝试通过更新统计信息ANALYZE、创建合适的索引、调整SQL写法来引导优化器。只有在确信优化器持续选择错误计划且无法通过其他方式纠正时才考虑使用pg_hint_plan并且必须详细记录原因。5.3 系统级调优影响执行计划的关键参数PostgreSQL的优化器行为受一系列配置参数影响。理解它们可以在系统层面进行调优。shared_buffers数据库使用的共享内存缓冲区大小。这是最重要的参数之一。更大的shared_buffers意味着更多数据可以缓存在内存中从而将昂贵的磁盘I/OBuffers: read转换为快速的内存访问Buffers: shared hit。通常建议设置为系统内存的25%-40%。work_mem每个排序、哈希操作等可以使用的内存量。如果执行计划中Sort或Hash节点的Memory Usage接近或超过work_mem就会发生磁盘临时文件写入Disk: xxx kB性能急剧下降。适当增加work_mem可以避免此类问题。但注意这个参数是按操作分配的一个复杂查询可能并发多个排序/哈希总内存消耗是work_mem * 并发操作数设置过高可能导致内存溢出。random_page_costvsseq_page_cost这两个参数定义了优化器对随机I/O和顺序I/O的成本估算。默认random_page_cost4.0,seq_page_cost1.0意味着随机读取的成本是顺序读取的4倍。如果你的数据完全在SSD上随机访问和顺序访问的延迟差异很小可以将random_page_cost降低到1.1或1.5。这会使优化器更倾向于使用索引扫描产生随机I/O从而在SSD环境下获得更好的性能。effective_cache_size告诉优化器操作系统和PostgreSQL缓存的数据总量。优化器会假设查询需要的数据有一部分已经在文件系统缓存里。设置一个接近系统总内存的值例如8GB可以让优化器更偏好使用索引因为它认为索引块很可能在缓存中随机I/O成本不高。调整示例假设你的服务器有32GB内存主要使用SSD硬盘可以这样调整postgresql.confshared_buffers 8GB # 32GB的25% work_mem 64MB # 根据并发查询数调整中等负载 maintenance_work_mem 1GB # 用于VACUUM, CREATE INDEX等操作 effective_cache_size 24GB # 估算的系统总缓存 random_page_cost 1.1 # SSD环境 seq_page_cost 1.0修改后务必重启PostgreSQL服务并对关键表执行ANALYZE让优化器基于新的成本模型重新评估。6. 常见问题排查与避坑指南在实际工作中你会遇到各种千奇百怪的执行计划问题。这里我总结了一份“避坑指南”收录了最常见的问题和排查思路。6.1 为什么索引没被使用这是最常被问到的问题。看到Seq Scan而期望的Index Scan没出现可以从以下方面排查问题现象可能原因排查方法与解决方案查询条件选择性太低WHERE status ‘active‘但表中95%的记录都是active。使用索引回表反而比直接全表扫描更慢。检查字段值的分布。如果确实选择性低索引可能无用。考虑使用部分索引CREATE INDEX idx_partial ON orders(status) WHERE status ‘inactive‘;只为少数值创建索引。使用了函数或表达式WHERE UPPER(name) ‘ALICE‘或WHERE order_date INTERVAL ‘1 day‘ NOW()。索引是在原字段上对字段进行运算后索引无法生效。解决方案1) 改写查询为WHERE name ‘alice‘应用层控制大小写。2) 创建表达式索引CREATE INDEX idx_upper_name ON users(UPPER(name));隐式类型转换WHERE user_id ‘12345‘user_id是整数类型但传入的是字符串。PostgreSQL会进行类型转换导致索引失效。确保应用层传入的数据类型与列定义严格一致。统计信息过时表经过大量增删改后优化器仍用旧的统计信息估算认为全表扫描成本更低。运行ANALYZE table_name;手动更新统计信息。可以设置autovacuum更激进一些确保统计信息及时更新。索引损坏极少数情况索引本身可能损坏。使用REINDEX INDEX index_name;重建索引。6.2 为什么估算行数rows和实际行数差那么多这是导致优化器选择错误执行计划的根本原因之一。巨大的偏差通常意味着统计信息不准确。根本原因PostgreSQL的ANALYZE操作会随机采样表中的数据页来生成统计信息。如果数据分布极度不均匀例如新插入的数据集中在某个特定范围或者采样率不够估算就会失准。解决方案提高统计信息质量增加表的统计信息目标。默认是100。对于非常大的表或数据倾斜严重的列可以增加ALTER TABLE table_name ALTER COLUMN column_name SET STATISTICS 1000;然后再次运行ANALYZE table_name;。更高的值会让ANALYZE收集更多信息但也会消耗更多时间和资源。使用扩展统计信息对于多列关联性很强的查询如WHERE state ‘CA‘ AND city ‘San Francisco‘如果两列单独统计优化器会低估组合条件的选择性。可以创建扩展统计CREATE STATISTICS stats_name (dependencies) ON state, city FROM table_name;然后运行ANALYZE。手动直方图调整这是高级技巧。在某些极端情况下你可以使用pg_stats系统视图查看列的直方图边界但这通常由DBA处理。6.3 如何优化深度分页查询OFFSET ... LIMIT查询SELECT * FROM table ORDER BY id OFFSET 1000000 LIMIT 20;会非常慢因为执行计划需要先排序并跳过前100万行。问题分析即使使用索引OFFSET也意味着数据库必须访问并丢弃所有跳过的行成本随OFFSET值线性增长。优化方案使用“游标”或“基于键值的分页”。-- 低效 SELECT * FROM posts ORDER BY created_at DESC, id DESC OFFSET 10000 LIMIT 20; -- 高效假设上次获取的最后一条记录的created_at和id已知 SELECT * FROM posts WHERE (created_at, id) (‘2023-10-01 12:00:00‘, 12345) -- 传入上一页最后一条的这两个值 ORDER BY created_at DESC, id DESC LIMIT 20;这需要你在ORDER BY的字段上建立索引例如(created_at DESC, id DESC)并且应用层需要记录并传递“上一页最后一条”的边界值。这种模式几乎可以实现常数时间的翻页。6.4 执行计划因参数不同而突变Parameter Sniffing在存储过程或参数化查询中PostgreSQL会在第一次执行时根据传入的参数值生成并缓存一个通用的执行计划。如果后续调用参数的数据分布差异巨大这个缓存的计划可能对新的参数值非常糟糕。现象同一个查询有时快如闪电有时慢如蜗牛。解决方案使用LOCAL计划对于特别易变的查询在查询前使用SET LOCAL plan_cache_mode force_generic_plan;或force_custom_plan来临时改变计划缓存行为。但这需要深入理解业务。拆分查询为数据分布差异巨大的不同参数值编写不同的SQL语句。禁用特定查询的计划缓存在PostgreSQL 12可以对特定语句使用DISCARD PLANS命令但需谨慎。最实用的方法确保你的索引能够覆盖各种数据分布的查询。对于条件索引通常都能很好工作。对于范围查询确保索引的聚类因子良好。性能调优是一个持续观察、假设、验证、调整的循环过程。执行计划是你最重要的观察窗口。养成对核心业务查询定期查看执行计划的习惯尤其是在数据量增长或应用更新之后。将EXPLAIN (ANALYZE, BUFFERS)的结果与查询耗时监控结合起来你就能在性能问题影响用户之前主动发现并解决它们。记住没有一劳永逸的优化只有对数据和数据库行为持续的理解与调整。
RELATED — 相关阅读

相关资讯

LATEST — 最新资讯

最新发布

TODAY — 本日精选

新闻

WEEKLY — 本周精选

新闻

MONTHLY — 本月精选

新闻