【发布时间】:2014-08-06 07:09:00
【问题描述】:
select *
from records
where id in ( select max(id) from records group by option_id )
即使在数百万行上,此查询也能正常工作。但是从解释语句的结果可以看出:
QUERY PLAN
-------------------------------------------------------------------------------------------------------------------------------------------
Nested Loop (cost=30218.84..31781.62 rows=620158 width=44) (actual time=1439.251..1443.458 rows=1057 loops=1)
-> HashAggregate (cost=30218.41..30220.41 rows=200 width=4) (actual time=1439.203..1439.503 rows=1057 loops=1)
-> HashAggregate (cost=30196.72..30206.36 rows=964 width=8) (actual time=1438.523..1438.807 rows=1057 loops=1)
-> Seq Scan on records records_1 (cost=0.00..23995.15 rows=1240315 width=8) (actual time=0.103..527.914 rows=1240315 loops=1)
-> Index Scan using records_pkey on records (cost=0.43..7.80 rows=1 width=44) (actual time=0.002..0.003 rows=1 loops=1057)
Index Cond: (id = (max(records_1.id)))
Total runtime: 1443.752 ms
(cost=0.00..23995.15 rows=1240315 width=8)
我也尝试重新排序查询:
select r.* from records r
inner join (select max(id) id from records group by option_id) r2 on r2.id= r.id;
QUERY PLAN
-------------------------------------------------------------------------------------------------------------------------------
Nested Loop (cost=30197.15..37741.04 rows=964 width=44) (actual time=835.519..840.452 rows=1057 loops=1)
-> HashAggregate (cost=30196.72..30206.36 rows=964 width=8) (actual time=835.471..835.836 rows=1057 loops=1)
-> Seq Scan on records (cost=0.00..23995.15 rows=1240315 width=8) (actual time=0.336..348.495 rows=1240315 loops=1)
-> Index Scan using records_pkey on records r (cost=0.43..7.80 rows=1 width=44) (actual time=0.003..0.003 rows=1 loops=1057)
Index Cond: (id = (max(records.id)))
Total runtime: 840.809 ms
(cost=0.00..23995.15 rows=1240315 width=8)
我在(option_id)、(option_id, id)、(option_id, id desc) 上尝试了有无索引,它们都对查询计划没有任何影响。
有没有办法在 Postgres 中执行分组最大查询而不扫描所有行?
我正在寻找的是以编程方式存储每个option_id 的最大ID 的索引,因为它们被插入到记录表中。这样,当我查询 option_id 的最大值时,我应该只需要扫描索引记录的次数与 option_id 不同。
我从高级用户那里看到select distinct on 的所有答案(感谢@Clodoaldo Neto 给了我要搜索的关键字)。这就是它不起作用的原因:
create index index_name on records(option_id, id desc)
select distinct on (option_id) *
from records
order by option_id, id desc
QUERY PLAN
------------------------------------------------------------------------------------------------------------------------------------------------------------
Unique (cost=0.43..76053.10 rows=964 width=44) (actual time=0.049..1668.545 rows=1056 loops=1)
-> Index Scan using records_option_id_id_idx on records (cost=0.43..73337.25 rows=1086342 width=44) (actual time=0.046..1368.300 rows=1086342 loops=1)
Total runtime: 1668.817 ms
太好了,它正在使用索引。然而,使用索引扫描所有 id 并没有多大意义。根据我的执行,它实际上比简单的顺序扫描要慢。
有趣的是,MySQL 5.5 能够简单地使用records(option_id, id) 上的索引来优化查询
mysql> select count(1) from records;
+----------+
| count(1) |
+----------+
| 1086342 |
+----------+
1 row in set (0.00 sec)
mysql> explain extended select * from records
inner join ( select max(id) max_id from records group by option_id ) mr
on mr.max_id= records.id;
+------+----------+--------------------------+
| rows | filtered | Extra |
+------+----------+--------------------------+
| 1056 | 100.00 | |
| 1 | 100.00 | |
| 201 | 100.00 | Using index for group-by |
+------+----------+--------------------------+
3 rows in set, 1 warning (0.02 sec)
【问题讨论】:
-
“但是使用索引扫描所有行并没有多大意义” --- 确实如此。索引小于整个数据集,它们更有可能在缓存中。它虽然不扫描实际行,但扫描索引。
-
创建索引的原始查询的计划是什么?
-
@zerkms indexing option_id 没有区别(正如我在问题中所述)索引 option_id_id_desc 或 option_id_id 在查询计划中也没有区别。
-
如果添加
(option_id, id desc)索引并针对给定表运行ANALYZE会怎样?顺便说一句,你运行的是什么 posgtresql 版本? -
“我尝试在 option_id 上放置和删除索引,这对查询计划没有影响。” --- 单个
option_id上的索引不太可能以任何方式影响它,因为您仍然需要检索MAX(id)因此遍历所有行。
标签: sql postgresql query-optimization greatest-n-per-group groupwise-maximum