建索引,并确认计划有没有变
目标
亲手创建多种索引,并直接观察执行计划如何变化。本实验的目的不是只学会创建索引,而是理解索引何时会被使用,何时会被忽略。
为什么重要
创建索引后执行计划仍未改变时,最常听到的建议就是强制使用索引。这不是诊断,而是在压制症状;实际上,强制使用索引反而变慢的情况非常多。因为索引扫描会为每一条匹配记录随机访问堆页面,当返回行很多时,就相当于以随机顺序读取整张表。
因此,索引未被使用时,需要区分两个问题:**是优化器的判断正确,所以没有使用索引;还是查询的形式根本无法使用索引。**前者需要更换或放弃索引,后者则需要修改查询。两者的应对方式完全相反,所以必须先做区分。
本实验还会创建部分索引、表达式索引和覆盖索引,帮助你形成‘索引并非只有一种,而是有多种类型’的直观认识。
步骤
- 不要把
EXPLAIN SELECT count(*) FROM orders WHERE channel = 'partner';的输出保存到/root/exp,而要保存到/root/plan_seq.txt。结果应为顺序扫描。 - 在
orders (ordered_at)上创建名为idx_orders_ordered_at的 B 树索引。 - 将对
ordered_at使用范围条件的查询的 EXPLAIN 输出保存到/root/plan_idx.txt。计划中必须出现idx_orders_ordered_at。 - 在
order_items上创建名为idx_order_items_order_product的复合索引,列顺序为(order_id, product_id)。 - 在
orders (ordered_at)上创建带有status = 'pending'条件的部分索引idx_orders_pending。 - 在
customers上为lower(email)创建表达式索引idx_customers_email_lower。 - 在
products上创建覆盖索引:键列为(category, price),INCLUDE 列为name,索引名为idx_products_cat_price。 - 禁用顺序扫描后,将根据
ordered_at条件计算count(*)的 EXPLAIN 输出保存到/root/plan_only.txt。结果应出现Index Only Scan。
提示
- 查看索引定义:
\d orders或SELECT indexdef FROM pg_indexes WHERE tablename='orders'; - 比较索引大小:
SELECT pg_size_pretty(pg_relation_size('인덱스이름')); - 暂时禁用顺序扫描:
SET enable_seqscan = off;——**仅用于诊断。**不能作为生产环境设置。 - 常见错误 1:使用不同的索引名称将无法通过评分。请严格使用题目给出的名称。
- 常见错误 2:表达式索引只有在查询使用完全相同的表达式时才能生效。
lower(email)索引不会用于lower(trim(email))条件。
保存没有索引时的计划
不要把 EXPLAIN SELECT count(*) FROM orders WHERE channel = 'partner'; 的输出保存到 /root/exp,而要保存到 /root/plan_seq.txt。结果应为顺序扫描。
只需把 EXPLAIN 放在查询前面即可。必须使用尚无索引的列作为条件,才会出现顺序扫描。
创建基本 B 树索引
在 orders (ordered_at) 上创建名为 idx_orders_ordered_at 的 B 树索引。
形式为 CREATE INDEX 名称 ON 表 (列)。索引名称必须完全一致才能通过评分。
保存使用索引的计划
将对 ordered_at 使用范围条件的查询的 EXPLAIN 输出保存到 /root/plan_idx.txt。计划中必须出现 idx_orders_ordered_at。
范围条件必须足够窄,优化器才会选择索引。如果对列应用函数,就无法使用这个索引。
确定复合索引的列顺序
在 order_items 上创建名为 idx_order_items_order_product 的复合索引,列顺序为 (order_id, product_id)。
经常用于等值条件的列应放在前面。顺序改变后就是另一个索引。
用部分索引减小体积
在 orders (ordered_at) 上创建带有 status = 'pending' 条件的部分索引 idx_orders_pending。
使用 CREATE INDEX ... WHERE 条件这种形式,可以只在索引中保存部分行。请比较索引大小。
创建表达式索引
在 customers 上为 lower(email) 创建表达式索引 idx_customers_email_lower。
可以根据计算结果而不是列本身创建索引。表达式必须用括号括住,查询也必须使用完全相同的表达式,索引才会生效。
用覆盖索引减少堆访问
在 products 上创建覆盖索引:键列为 (category, price),INCLUDE 列为 name,索引名为 idx_products_cat_price。
有一种子句可以分别定义用于查找的键列和只在结果中需要的列。
观察 Index Only Scan
禁用顺序扫描后,将根据 ordered_at 条件计算 count(*) 的 EXPLAIN 输出保存到 /root/plan_only.txt。结果应出现 Index Only Scan。
表很小时,优化器会选择顺序扫描。请仅为诊断暂时禁用顺序扫描,然后重新生成计划。