这是在 Postgres 中创建部分索引的正确方法吗?
Is this the right way to create a partial index in Postgres?
我们有一个包含 400 万条记录的 table,对于一个特定的经常使用的用例,我们只对具有 'Standard' 的特定 salesforce 用户类型的记录感兴趣,这些记录只有大约 10,000 条400万。可能存在的其他用户类型是 'PowerPartner'、'CSPLitePortal'、'CustomerSuccess'、'PowerCustomerSuccess' 和 'CsnOnly'.
所以对于这个用例,我认为创建部分索引会更好,根据 the documentation。
所以我计划创建此部分索引以加快查询用户类型为 'Standard' 的记录并防止来自网络的请求超时:
CREATE INDEX user_type_idx ON user_table(userType)
WHERE userType NOT IN
('PowerPartner', 'CSPLitePortal', 'CustomerSuccess', 'PowerCustomerSuccess', 'CsnOnly');
查找查询将是
select * from user_table where userType='Standard';
您能否确认这是否是创建部分索引的正确方法?会有很大帮助。
对于要使用的索引,WHERE
条件必须在您编写的查询中使用。
PostgreSQL 有一定的推导能力,但它无法推断出 userType = 'Standard'
等同于索引中的条件。
使用EXPLAIN
查看您的索引是否可以使用。
Postgres 可以使用它,但这样做的方式比指定 where user_type = 'Standard'
.
的索引效率(略)低
我创建了一个包含 400 万行的小测试 table,其中 10.000 行具有 user_type 'Standard'
。其他值使用以下脚本 运行domly 分发:
create table user_table
(
id serial primary key,
some_date date not null,
user_type text not null,
some_ts timestamp not null,
some_number integer not null,
some_data text,
some_flag boolean
);
insert into user_table (some_date, user_type, some_ts, some_number, some_data, some_flag)
select current_date,
case (random() * 4 + 1)::int
when 1 then 'PowerPartner'
when 2 then 'CSPLitePortal'
when 3 then 'CustomerSuccess'
when 4 then 'PowerCustomerSuccess'
when 5 then 'CsnOnly'
end,
clock_timestamp(),
42,
rpad(md5(random()::text), (random() * 200 + 1)::int, md5(random()::text)),
(random() + 1)::int = 1
from generate_series(1,4e6 - 10000) as t(i)
union all
select current_date,
'Standard',
clock_timestamp(),
42,
rpad(md5(random()::text), (random() * 200 + 1)::int, md5(random()::text)),
(random() + 1)::int = 1
from generate_series(1,10000) as t(i);
(我创建的 table 不仅仅是几列,因为规划者的选择也受 table 的大小和宽度驱动)
第一次测试使用带有 NOT IN 的索引:
create index ix_not_in on user_table(user_type)
where user_type not in ('PowerPartner', 'CSPLitePortal', 'CustomerSuccess', 'PowerCustomerSuccess', 'CsnOnly');
explain (analyze true, verbose true, buffers true)
select *
from user_table
where user_type = 'Standard'
结果:
QUERY PLAN
--------------------------------------------------------------------------------------------------------------------------------
Bitmap Heap Scan on stuff.user_table (cost=139.68..14631.83 rows=11598 width=139) (actual time=1.035..2.171 rows=10000 loops=1)
Output: id, some_date, user_type, some_ts, some_number, some_data, some_flag
Recheck Cond: (user_table.user_type = 'Standard'::text)
Buffers: shared hit=262
-> Bitmap Index Scan on ix_not_in (cost=0.00..136.79 rows=11598 width=0) (actual time=1.007..1.007 rows=10000 loops=1)
Index Cond: (user_table.user_type = 'Standard'::text)
Buffers: shared hit=40
Total runtime: 2.506 ms
(以上是我运行语句10次左右消除缓存问题后的典型执行时间)
如您所见,规划器使用位图索引扫描,这是一种 "lossy" 扫描,需要额外的步骤来过滤掉误报。
使用以下索引时:
create index ix_standard on user_table(id)
where user_type = 'Standard';
这导致以下计划:
QUERY PLAN
----------------------------------------------------------------------------------------------------------------------------------------
Index Scan using ix_standard on stuff.user_table (cost=0.29..443.16 rows=10267 width=139) (actual time=0.011..1.498 rows=10000 loops=1)
Output: id, some_date, user_type, some_ts, some_number, some_data, some_flag
Buffers: shared hit=313
Total runtime: 1.815 ms
结论:
已使用您的索引,但仅针对您感兴趣的类型创建索引效率更高一些。
运行时并没有太大的不同。我对每个解释执行了大约 10 次,ix_standard
索引的平均值略低于 2 毫秒,ix_not_in
索引的平均值略高于 2 毫秒 - 所以不是真正的性能差异。
但一般来说,索引扫描会随着 table 大小的增加比位图索引扫描更好地缩放。这基本上是由于 "Recheck Condition" - 特别是如果没有足够的 work_mem 可用于将位图保存在内存中(对于更大的 tables)。
我们有一个包含 400 万条记录的 table,对于一个特定的经常使用的用例,我们只对具有 'Standard' 的特定 salesforce 用户类型的记录感兴趣,这些记录只有大约 10,000 条400万。可能存在的其他用户类型是 'PowerPartner'、'CSPLitePortal'、'CustomerSuccess'、'PowerCustomerSuccess' 和 'CsnOnly'.
所以对于这个用例,我认为创建部分索引会更好,根据 the documentation。
所以我计划创建此部分索引以加快查询用户类型为 'Standard' 的记录并防止来自网络的请求超时:
CREATE INDEX user_type_idx ON user_table(userType)
WHERE userType NOT IN
('PowerPartner', 'CSPLitePortal', 'CustomerSuccess', 'PowerCustomerSuccess', 'CsnOnly');
查找查询将是
select * from user_table where userType='Standard';
您能否确认这是否是创建部分索引的正确方法?会有很大帮助。
对于要使用的索引,WHERE
条件必须在您编写的查询中使用。
PostgreSQL 有一定的推导能力,但它无法推断出 userType = 'Standard'
等同于索引中的条件。
使用EXPLAIN
查看您的索引是否可以使用。
Postgres 可以使用它,但这样做的方式比指定 where user_type = 'Standard'
.
我创建了一个包含 400 万行的小测试 table,其中 10.000 行具有 user_type 'Standard'
。其他值使用以下脚本 运行domly 分发:
create table user_table
(
id serial primary key,
some_date date not null,
user_type text not null,
some_ts timestamp not null,
some_number integer not null,
some_data text,
some_flag boolean
);
insert into user_table (some_date, user_type, some_ts, some_number, some_data, some_flag)
select current_date,
case (random() * 4 + 1)::int
when 1 then 'PowerPartner'
when 2 then 'CSPLitePortal'
when 3 then 'CustomerSuccess'
when 4 then 'PowerCustomerSuccess'
when 5 then 'CsnOnly'
end,
clock_timestamp(),
42,
rpad(md5(random()::text), (random() * 200 + 1)::int, md5(random()::text)),
(random() + 1)::int = 1
from generate_series(1,4e6 - 10000) as t(i)
union all
select current_date,
'Standard',
clock_timestamp(),
42,
rpad(md5(random()::text), (random() * 200 + 1)::int, md5(random()::text)),
(random() + 1)::int = 1
from generate_series(1,10000) as t(i);
(我创建的 table 不仅仅是几列,因为规划者的选择也受 table 的大小和宽度驱动)
第一次测试使用带有 NOT IN 的索引:
create index ix_not_in on user_table(user_type)
where user_type not in ('PowerPartner', 'CSPLitePortal', 'CustomerSuccess', 'PowerCustomerSuccess', 'CsnOnly');
explain (analyze true, verbose true, buffers true)
select *
from user_table
where user_type = 'Standard'
结果:
QUERY PLAN
--------------------------------------------------------------------------------------------------------------------------------
Bitmap Heap Scan on stuff.user_table (cost=139.68..14631.83 rows=11598 width=139) (actual time=1.035..2.171 rows=10000 loops=1)
Output: id, some_date, user_type, some_ts, some_number, some_data, some_flag
Recheck Cond: (user_table.user_type = 'Standard'::text)
Buffers: shared hit=262
-> Bitmap Index Scan on ix_not_in (cost=0.00..136.79 rows=11598 width=0) (actual time=1.007..1.007 rows=10000 loops=1)
Index Cond: (user_table.user_type = 'Standard'::text)
Buffers: shared hit=40
Total runtime: 2.506 ms
(以上是我运行语句10次左右消除缓存问题后的典型执行时间)
如您所见,规划器使用位图索引扫描,这是一种 "lossy" 扫描,需要额外的步骤来过滤掉误报。
使用以下索引时:
create index ix_standard on user_table(id)
where user_type = 'Standard';
这导致以下计划:
QUERY PLAN
----------------------------------------------------------------------------------------------------------------------------------------
Index Scan using ix_standard on stuff.user_table (cost=0.29..443.16 rows=10267 width=139) (actual time=0.011..1.498 rows=10000 loops=1)
Output: id, some_date, user_type, some_ts, some_number, some_data, some_flag
Buffers: shared hit=313
Total runtime: 1.815 ms
结论:
已使用您的索引,但仅针对您感兴趣的类型创建索引效率更高一些。
运行时并没有太大的不同。我对每个解释执行了大约 10 次,ix_standard
索引的平均值略低于 2 毫秒,ix_not_in
索引的平均值略高于 2 毫秒 - 所以不是真正的性能差异。
但一般来说,索引扫描会随着 table 大小的增加比位图索引扫描更好地缩放。这基本上是由于 "Recheck Condition" - 特别是如果没有足够的 work_mem 可用于将位图保存在内存中(对于更大的 tables)。