Postgres Large table Select Query With In 子句的最佳索引选择
Postgres Large table Optimal index selection for Select Query With In Clause
我们有一个非常大的 table,总行数约为 ~4 billion
,每天的吞吐量约为 ~20-25 million
。
下面是示例 Table 定义。
table_name (
id bigint,
user_id bigint,
status smallint,
date timestamp,
.
.
some more columns
);
NOTE: status is an enum and can have 6-7 possible values.
下面是我们要优化的查询:
SELECT *
FROM table_name
WHERE user_id = $user_id
AND status in (1, 2, 3, 6, 7, 10)
ORDER BY date
DESC LIMIT 20;
最初,我们 index1
在 table_name (user_id, status)
。
因为这个索引没有提供最佳性能。我们也考虑过在索引中包含日期。
现在,我们已经尝试在 table 上创建一堆不同的索引,但是解释计划总是选择初始索引,即:index1.
下面是我们试过的索引:
index2: table_name (user_id, status, date)
index3: table_name (user_id, status, date) where (status = ANY (ARRAY [1, 2, 3, 6, 7, 10]))
index4: table_name (user_id, status) where (status = ANY (ARRAY [1, 2, 3, 6, 7, 10]));
下面是解释分析输出:
Limit (cost=27029.07..27029.12 rows=20 width=251) (actual time=32.466..32.470 rows=20 loops=1)
-> Sort (cost=27029.07..27049.17 rows=8041 width=251) (actual time=32.465..32.467 rows=20 loops=1)
Sort Key: id DESC
Sort Method: top-N heapsort Memory: 38kB
-> Index Scan using index1 on table_name wt (cost=0.58..26815.10 rows=8041 width=251) (actual time=0.027..26.991 rows=37362 loops=1)
Index Cond: ((user_id = 111111) AND (status = ANY ('{1,3,2,7,6,10,8}'::integer[])))
Planning Time: 0.320 ms
Execution Time: 32.498 ms
我们的数据库 postgres 版本是:12.7
我们 运行 vaccuming
定期。
我们想了解为什么其他 索引 没有被用于我们的 查询.
另外考虑到我们的用例,是否有更好的方法来创建 index 以便我们可以为 查询在acceptable响应时间?
CREATE INDEX table_name_desc_index ON table_name (userid,_date DESC NULLS LAST);
然后尝试以下操作:
SELECT *
FROM table_name
inner join (values (1),(2),(3),(6),(7),(10)) val(v) ON (table_name.status = v )
WHERE user_id = $user_id
ORDER BY date
DESC LIMIT 20;
您正在选择 *
,因此您将无法进行 index-only 扫描,因为 *
拖入的列多于索引中的列。您显示的那些其他索引(我可以看到)的唯一优点是启用 index-only 扫描,因此如果这不起作用,那么这些索引未被选择使用也就不足为奇了。您可以通过将 *
更改为仅出现在索引中的列来测试这个理论,看看会发生什么。
关于您的其中一个索引:
(user_id, status, date) where (status = ANY (ARRAY [1, 2, 3, 6, 7, 10]))
这个索引似乎毫无意义。 WHERE 子句的好处是它将“状态”的不同限定值减少到单个值 ('true')。但是然后将“状态”放入索引主体只会再次将它们分解。更好的索引是:
(user_id, date) where (status = ANY (ARRAY [1, 2, 3, 6, 7, 10]))
这个可以跳转到特定user_id的末尾,向后扫描(以完成order by date desc
)并在找到20行后停止。当你有“身份”作为闯入者时,它会阻止它这样做。
我们有一个非常大的 table,总行数约为 ~4 billion
,每天的吞吐量约为 ~20-25 million
。
下面是示例 Table 定义。
table_name (
id bigint,
user_id bigint,
status smallint,
date timestamp,
.
.
some more columns
);
NOTE: status is an enum and can have 6-7 possible values.
下面是我们要优化的查询:
SELECT *
FROM table_name
WHERE user_id = $user_id
AND status in (1, 2, 3, 6, 7, 10)
ORDER BY date
DESC LIMIT 20;
最初,我们 index1
在 table_name (user_id, status)
。
因为这个索引没有提供最佳性能。我们也考虑过在索引中包含日期。
现在,我们已经尝试在 table 上创建一堆不同的索引,但是解释计划总是选择初始索引,即:index1.
下面是我们试过的索引:
index2: table_name (user_id, status, date)
index3: table_name (user_id, status, date) where (status = ANY (ARRAY [1, 2, 3, 6, 7, 10]))
index4: table_name (user_id, status) where (status = ANY (ARRAY [1, 2, 3, 6, 7, 10]));
下面是解释分析输出:
Limit (cost=27029.07..27029.12 rows=20 width=251) (actual time=32.466..32.470 rows=20 loops=1)
-> Sort (cost=27029.07..27049.17 rows=8041 width=251) (actual time=32.465..32.467 rows=20 loops=1)
Sort Key: id DESC
Sort Method: top-N heapsort Memory: 38kB
-> Index Scan using index1 on table_name wt (cost=0.58..26815.10 rows=8041 width=251) (actual time=0.027..26.991 rows=37362 loops=1)
Index Cond: ((user_id = 111111) AND (status = ANY ('{1,3,2,7,6,10,8}'::integer[])))
Planning Time: 0.320 ms
Execution Time: 32.498 ms
我们的数据库 postgres 版本是:12.7
我们 运行 vaccuming
定期。
我们想了解为什么其他 索引 没有被用于我们的 查询.
另外考虑到我们的用例,是否有更好的方法来创建 index 以便我们可以为 查询在acceptable响应时间?
CREATE INDEX table_name_desc_index ON table_name (userid,_date DESC NULLS LAST);
然后尝试以下操作:
SELECT *
FROM table_name
inner join (values (1),(2),(3),(6),(7),(10)) val(v) ON (table_name.status = v )
WHERE user_id = $user_id
ORDER BY date
DESC LIMIT 20;
您正在选择 *
,因此您将无法进行 index-only 扫描,因为 *
拖入的列多于索引中的列。您显示的那些其他索引(我可以看到)的唯一优点是启用 index-only 扫描,因此如果这不起作用,那么这些索引未被选择使用也就不足为奇了。您可以通过将 *
更改为仅出现在索引中的列来测试这个理论,看看会发生什么。
关于您的其中一个索引:
(user_id, status, date) where (status = ANY (ARRAY [1, 2, 3, 6, 7, 10]))
这个索引似乎毫无意义。 WHERE 子句的好处是它将“状态”的不同限定值减少到单个值 ('true')。但是然后将“状态”放入索引主体只会再次将它们分解。更好的索引是:
(user_id, date) where (status = ANY (ARRAY [1, 2, 3, 6, 7, 10]))
这个可以跳转到特定user_id的末尾,向后扫描(以完成order by date desc
)并在找到20行后停止。当你有“身份”作为闯入者时,它会阻止它这样做。