mirror of
https://github.com/postgres/postgres.git
synced 2025-07-20 05:03:10 +03:00
Optimize btree insertions for common case of increasing values
Remember the last page of an index insert if it's the rightmost leaf page. If the next entry belongs on and can fit in the remembered page, insert the new entry there as long as we can get a lock on the page. Otherwise, fall back on the more expensive method of searching for the right place to insert the entry. This provides a performance improvement for the common case where an index entry is for monotonically increasing or nearly monotonically increasing value such as an identity field or a current timestamp. Pavan Deolasee Reviewed by Claudio Freire, Simon Riggs and Peter Geoghegan Discussion: https://postgr.es/m/CABOikdM9DrupjyKZZFM5k8-0RCDs1wk6JzEkg7UgSW6QzOwMZw@mail.gmail.com
This commit is contained in:
@ -1067,6 +1067,255 @@ select tableoid::regclass, * from idxpart order by a;
|
||||
(8 rows)
|
||||
|
||||
drop table idxpart;
|
||||
-- test fastpath mechanism for index insertion
|
||||
create table fastpath (a int, b text, c numeric);
|
||||
create unique index fpindex1 on fastpath(a);
|
||||
insert into fastpath values (1, 'b1', 100.00);
|
||||
insert into fastpath values (1, 'b1', 100.00); -- unique key check
|
||||
ERROR: duplicate key value violates unique constraint "fpindex1"
|
||||
DETAIL: Key (a)=(1) already exists.
|
||||
truncate fastpath;
|
||||
insert into fastpath select generate_series(1,10000), 'b', 100;
|
||||
-- vacuum the table so as to improve chances of index-only scans. we can't
|
||||
-- guarantee if index-only scans will be picked up in all cases or not, but
|
||||
-- that fuzziness actually helps the test.
|
||||
vacuum fastpath;
|
||||
set enable_seqscan to false;
|
||||
set enable_bitmapscan to false;
|
||||
explain select sum(a) from fastpath where a = 6456;
|
||||
QUERY PLAN
|
||||
------------------------------------------------------------------------------------
|
||||
Aggregate (cost=4.31..4.32 rows=1 width=8)
|
||||
-> Index Only Scan using fpindex1 on fastpath (cost=0.29..4.30 rows=1 width=4)
|
||||
Index Cond: (a = 6456)
|
||||
(3 rows)
|
||||
|
||||
explain select sum(a) from fastpath where a >= 5000 and a < 5700;
|
||||
QUERY PLAN
|
||||
-------------------------------------------------------------------------------------
|
||||
Aggregate (cost=5.41..5.42 rows=1 width=8)
|
||||
-> Index Only Scan using fpindex1 on fastpath (cost=0.29..5.29 rows=50 width=4)
|
||||
Index Cond: ((a >= 5000) AND (a < 5700))
|
||||
(3 rows)
|
||||
|
||||
select sum(a) from fastpath where a = 6456;
|
||||
sum
|
||||
------
|
||||
6456
|
||||
(1 row)
|
||||
|
||||
select sum(a) from fastpath where a >= 5000 and a < 5700;
|
||||
sum
|
||||
---------
|
||||
3744650
|
||||
(1 row)
|
||||
|
||||
-- drop the only index on the table and compute hashes for
|
||||
-- a few queries which orders the results in various different ways.
|
||||
drop index fpindex1;
|
||||
truncate fastpath;
|
||||
insert into fastpath select y.x, 'b' || (y.x/10)::text, 100 from (select generate_series(1,10000) as x) y;
|
||||
select md5(string_agg(a::text, b order by a, b asc)) from fastpath
|
||||
where a >= 1000 and a < 2000 and b > 'b1' and b < 'b3';
|
||||
md5
|
||||
----------------------------------
|
||||
2ca216010a558a52d7df12f76dfc77ab
|
||||
(1 row)
|
||||
|
||||
select md5(string_agg(a::text, b order by a desc, b desc)) from fastpath
|
||||
where a >= 1000 and a < 2000 and b > 'b1' and b < 'b3';
|
||||
md5
|
||||
----------------------------------
|
||||
6167a852b3e0679886b84a5405b5b53d
|
||||
(1 row)
|
||||
|
||||
select md5(string_agg(a::text, b order by b, a desc)) from fastpath
|
||||
where a >= 1000 and a < 2000 and b > 'b1' and b < 'b3';
|
||||
md5
|
||||
----------------------------------
|
||||
dfcf2bd5e5fea8397d47b2fd14618d31
|
||||
(1 row)
|
||||
|
||||
select md5(string_agg(a::text, b order by b, a asc)) from fastpath
|
||||
where a >= 1000 and a < 2000 and b > 'b1' and b < 'b3';
|
||||
md5
|
||||
----------------------------------
|
||||
2ca216010a558a52d7df12f76dfc77ab
|
||||
(1 row)
|
||||
|
||||
-- now create a multi-column index with both column asc
|
||||
create index fpindex2 on fastpath(a, b);
|
||||
truncate fastpath;
|
||||
insert into fastpath select y.x, 'b' || (y.x/10)::text, 100 from (select generate_series(1,10000) as x) y;
|
||||
-- again, vacuum here either forces index-only scans or creates fuzziness
|
||||
vacuum fastpath;
|
||||
select md5(string_agg(a::text, b order by a, b asc)) from fastpath
|
||||
where a >= 1000 and a < 2000 and b > 'b1' and b < 'b3';
|
||||
md5
|
||||
----------------------------------
|
||||
2ca216010a558a52d7df12f76dfc77ab
|
||||
(1 row)
|
||||
|
||||
select md5(string_agg(a::text, b order by a desc, b desc)) from fastpath
|
||||
where a >= 1000 and a < 2000 and b > 'b1' and b < 'b3';
|
||||
md5
|
||||
----------------------------------
|
||||
6167a852b3e0679886b84a5405b5b53d
|
||||
(1 row)
|
||||
|
||||
select md5(string_agg(a::text, b order by b, a desc)) from fastpath
|
||||
where a >= 1000 and a < 2000 and b > 'b1' and b < 'b3';
|
||||
md5
|
||||
----------------------------------
|
||||
dfcf2bd5e5fea8397d47b2fd14618d31
|
||||
(1 row)
|
||||
|
||||
select md5(string_agg(a::text, b order by b, a asc)) from fastpath
|
||||
where a >= 1000 and a < 2000 and b > 'b1' and b < 'b3';
|
||||
md5
|
||||
----------------------------------
|
||||
2ca216010a558a52d7df12f76dfc77ab
|
||||
(1 row)
|
||||
|
||||
-- same queries with a different kind of index now. the final result must not
|
||||
-- change irrespective of what kind of index we have.
|
||||
drop index fpindex2;
|
||||
create index fpindex3 on fastpath(a desc, b asc);
|
||||
truncate fastpath;
|
||||
insert into fastpath select y.x, 'b' || (y.x/10)::text, 100 from (select generate_series(1,10000) as x) y;
|
||||
vacuum fastpath;
|
||||
select md5(string_agg(a::text, b order by a, b asc)) from fastpath
|
||||
where a >= 1000 and a < 2000 and b > 'b1' and b < 'b3';
|
||||
md5
|
||||
----------------------------------
|
||||
2ca216010a558a52d7df12f76dfc77ab
|
||||
(1 row)
|
||||
|
||||
select md5(string_agg(a::text, b order by a desc, b desc)) from fastpath
|
||||
where a >= 1000 and a < 2000 and b > 'b1' and b < 'b3';
|
||||
md5
|
||||
----------------------------------
|
||||
6167a852b3e0679886b84a5405b5b53d
|
||||
(1 row)
|
||||
|
||||
select md5(string_agg(a::text, b order by b, a desc)) from fastpath
|
||||
where a >= 1000 and a < 2000 and b > 'b1' and b < 'b3';
|
||||
md5
|
||||
----------------------------------
|
||||
dfcf2bd5e5fea8397d47b2fd14618d31
|
||||
(1 row)
|
||||
|
||||
select md5(string_agg(a::text, b order by b, a asc)) from fastpath
|
||||
where a >= 1000 and a < 2000 and b > 'b1' and b < 'b3';
|
||||
md5
|
||||
----------------------------------
|
||||
2ca216010a558a52d7df12f76dfc77ab
|
||||
(1 row)
|
||||
|
||||
-- repeat again
|
||||
drop index fpindex3;
|
||||
create index fpindex4 on fastpath(a asc, b desc);
|
||||
truncate fastpath;
|
||||
insert into fastpath select y.x, 'b' || (y.x/10)::text, 100 from (select generate_series(1,10000) as x) y;
|
||||
vacuum fastpath;
|
||||
select md5(string_agg(a::text, b order by a, b asc)) from fastpath
|
||||
where a >= 1000 and a < 2000 and b > 'b1' and b < 'b3';
|
||||
md5
|
||||
----------------------------------
|
||||
2ca216010a558a52d7df12f76dfc77ab
|
||||
(1 row)
|
||||
|
||||
select md5(string_agg(a::text, b order by a desc, b desc)) from fastpath
|
||||
where a >= 1000 and a < 2000 and b > 'b1' and b < 'b3';
|
||||
md5
|
||||
----------------------------------
|
||||
6167a852b3e0679886b84a5405b5b53d
|
||||
(1 row)
|
||||
|
||||
select md5(string_agg(a::text, b order by b, a desc)) from fastpath
|
||||
where a >= 1000 and a < 2000 and b > 'b1' and b < 'b3';
|
||||
md5
|
||||
----------------------------------
|
||||
dfcf2bd5e5fea8397d47b2fd14618d31
|
||||
(1 row)
|
||||
|
||||
select md5(string_agg(a::text, b order by b, a asc)) from fastpath
|
||||
where a >= 1000 and a < 2000 and b > 'b1' and b < 'b3';
|
||||
md5
|
||||
----------------------------------
|
||||
2ca216010a558a52d7df12f76dfc77ab
|
||||
(1 row)
|
||||
|
||||
-- and again, this time indexing by (b, a). Note that column "b" has non-unique
|
||||
-- values.
|
||||
drop index fpindex4;
|
||||
create index fpindex5 on fastpath(b asc, a desc);
|
||||
truncate fastpath;
|
||||
insert into fastpath select y.x, 'b' || (y.x/10)::text, 100 from (select generate_series(1,10000) as x) y;
|
||||
vacuum fastpath;
|
||||
select md5(string_agg(a::text, b order by a, b asc)) from fastpath
|
||||
where a >= 1000 and a < 2000 and b > 'b1' and b < 'b3';
|
||||
md5
|
||||
----------------------------------
|
||||
2ca216010a558a52d7df12f76dfc77ab
|
||||
(1 row)
|
||||
|
||||
select md5(string_agg(a::text, b order by a desc, b desc)) from fastpath
|
||||
where a >= 1000 and a < 2000 and b > 'b1' and b < 'b3';
|
||||
md5
|
||||
----------------------------------
|
||||
6167a852b3e0679886b84a5405b5b53d
|
||||
(1 row)
|
||||
|
||||
select md5(string_agg(a::text, b order by b, a desc)) from fastpath
|
||||
where a >= 1000 and a < 2000 and b > 'b1' and b < 'b3';
|
||||
md5
|
||||
----------------------------------
|
||||
dfcf2bd5e5fea8397d47b2fd14618d31
|
||||
(1 row)
|
||||
|
||||
select md5(string_agg(a::text, b order by b, a asc)) from fastpath
|
||||
where a >= 1000 and a < 2000 and b > 'b1' and b < 'b3';
|
||||
md5
|
||||
----------------------------------
|
||||
2ca216010a558a52d7df12f76dfc77ab
|
||||
(1 row)
|
||||
|
||||
-- one last time
|
||||
drop index fpindex5;
|
||||
create index fpindex6 on fastpath(b desc, a desc);
|
||||
truncate fastpath;
|
||||
insert into fastpath select y.x, 'b' || (y.x/10)::text, 100 from (select generate_series(1,10000) as x) y;
|
||||
vacuum fastpath;
|
||||
select md5(string_agg(a::text, b order by a, b asc)) from fastpath
|
||||
where a >= 1000 and a < 2000 and b > 'b1' and b < 'b3';
|
||||
md5
|
||||
----------------------------------
|
||||
2ca216010a558a52d7df12f76dfc77ab
|
||||
(1 row)
|
||||
|
||||
select md5(string_agg(a::text, b order by a desc, b desc)) from fastpath
|
||||
where a >= 1000 and a < 2000 and b > 'b1' and b < 'b3';
|
||||
md5
|
||||
----------------------------------
|
||||
6167a852b3e0679886b84a5405b5b53d
|
||||
(1 row)
|
||||
|
||||
select md5(string_agg(a::text, b order by b, a desc)) from fastpath
|
||||
where a >= 1000 and a < 2000 and b > 'b1' and b < 'b3';
|
||||
md5
|
||||
----------------------------------
|
||||
dfcf2bd5e5fea8397d47b2fd14618d31
|
||||
(1 row)
|
||||
|
||||
select md5(string_agg(a::text, b order by b, a asc)) from fastpath
|
||||
where a >= 1000 and a < 2000 and b > 'b1' and b < 'b3';
|
||||
md5
|
||||
----------------------------------
|
||||
2ca216010a558a52d7df12f76dfc77ab
|
||||
(1 row)
|
||||
|
||||
drop table fastpath;
|
||||
-- intentionally leave some objects around
|
||||
create table idxpart (a int) partition by range (a);
|
||||
create table idxpart1 partition of idxpart for values from (0) to (100);
|
||||
|
@ -574,6 +574,122 @@ insert into idxpart values (857142, 'six');
|
||||
select tableoid::regclass, * from idxpart order by a;
|
||||
drop table idxpart;
|
||||
|
||||
-- test fastpath mechanism for index insertion
|
||||
create table fastpath (a int, b text, c numeric);
|
||||
create unique index fpindex1 on fastpath(a);
|
||||
|
||||
insert into fastpath values (1, 'b1', 100.00);
|
||||
insert into fastpath values (1, 'b1', 100.00); -- unique key check
|
||||
|
||||
truncate fastpath;
|
||||
insert into fastpath select generate_series(1,10000), 'b', 100;
|
||||
|
||||
-- vacuum the table so as to improve chances of index-only scans. we can't
|
||||
-- guarantee if index-only scans will be picked up in all cases or not, but
|
||||
-- that fuzziness actually helps the test.
|
||||
vacuum fastpath;
|
||||
|
||||
set enable_seqscan to false;
|
||||
set enable_bitmapscan to false;
|
||||
|
||||
explain select sum(a) from fastpath where a = 6456;
|
||||
explain select sum(a) from fastpath where a >= 5000 and a < 5700;
|
||||
select sum(a) from fastpath where a = 6456;
|
||||
select sum(a) from fastpath where a >= 5000 and a < 5700;
|
||||
|
||||
-- drop the only index on the table and compute hashes for
|
||||
-- a few queries which orders the results in various different ways.
|
||||
drop index fpindex1;
|
||||
truncate fastpath;
|
||||
insert into fastpath select y.x, 'b' || (y.x/10)::text, 100 from (select generate_series(1,10000) as x) y;
|
||||
select md5(string_agg(a::text, b order by a, b asc)) from fastpath
|
||||
where a >= 1000 and a < 2000 and b > 'b1' and b < 'b3';
|
||||
select md5(string_agg(a::text, b order by a desc, b desc)) from fastpath
|
||||
where a >= 1000 and a < 2000 and b > 'b1' and b < 'b3';
|
||||
select md5(string_agg(a::text, b order by b, a desc)) from fastpath
|
||||
where a >= 1000 and a < 2000 and b > 'b1' and b < 'b3';
|
||||
select md5(string_agg(a::text, b order by b, a asc)) from fastpath
|
||||
where a >= 1000 and a < 2000 and b > 'b1' and b < 'b3';
|
||||
|
||||
-- now create a multi-column index with both column asc
|
||||
create index fpindex2 on fastpath(a, b);
|
||||
truncate fastpath;
|
||||
insert into fastpath select y.x, 'b' || (y.x/10)::text, 100 from (select generate_series(1,10000) as x) y;
|
||||
-- again, vacuum here either forces index-only scans or creates fuzziness
|
||||
vacuum fastpath;
|
||||
select md5(string_agg(a::text, b order by a, b asc)) from fastpath
|
||||
where a >= 1000 and a < 2000 and b > 'b1' and b < 'b3';
|
||||
select md5(string_agg(a::text, b order by a desc, b desc)) from fastpath
|
||||
where a >= 1000 and a < 2000 and b > 'b1' and b < 'b3';
|
||||
select md5(string_agg(a::text, b order by b, a desc)) from fastpath
|
||||
where a >= 1000 and a < 2000 and b > 'b1' and b < 'b3';
|
||||
select md5(string_agg(a::text, b order by b, a asc)) from fastpath
|
||||
where a >= 1000 and a < 2000 and b > 'b1' and b < 'b3';
|
||||
|
||||
-- same queries with a different kind of index now. the final result must not
|
||||
-- change irrespective of what kind of index we have.
|
||||
drop index fpindex2;
|
||||
create index fpindex3 on fastpath(a desc, b asc);
|
||||
truncate fastpath;
|
||||
insert into fastpath select y.x, 'b' || (y.x/10)::text, 100 from (select generate_series(1,10000) as x) y;
|
||||
vacuum fastpath;
|
||||
select md5(string_agg(a::text, b order by a, b asc)) from fastpath
|
||||
where a >= 1000 and a < 2000 and b > 'b1' and b < 'b3';
|
||||
select md5(string_agg(a::text, b order by a desc, b desc)) from fastpath
|
||||
where a >= 1000 and a < 2000 and b > 'b1' and b < 'b3';
|
||||
select md5(string_agg(a::text, b order by b, a desc)) from fastpath
|
||||
where a >= 1000 and a < 2000 and b > 'b1' and b < 'b3';
|
||||
select md5(string_agg(a::text, b order by b, a asc)) from fastpath
|
||||
where a >= 1000 and a < 2000 and b > 'b1' and b < 'b3';
|
||||
|
||||
-- repeat again
|
||||
drop index fpindex3;
|
||||
create index fpindex4 on fastpath(a asc, b desc);
|
||||
truncate fastpath;
|
||||
insert into fastpath select y.x, 'b' || (y.x/10)::text, 100 from (select generate_series(1,10000) as x) y;
|
||||
vacuum fastpath;
|
||||
select md5(string_agg(a::text, b order by a, b asc)) from fastpath
|
||||
where a >= 1000 and a < 2000 and b > 'b1' and b < 'b3';
|
||||
select md5(string_agg(a::text, b order by a desc, b desc)) from fastpath
|
||||
where a >= 1000 and a < 2000 and b > 'b1' and b < 'b3';
|
||||
select md5(string_agg(a::text, b order by b, a desc)) from fastpath
|
||||
where a >= 1000 and a < 2000 and b > 'b1' and b < 'b3';
|
||||
select md5(string_agg(a::text, b order by b, a asc)) from fastpath
|
||||
where a >= 1000 and a < 2000 and b > 'b1' and b < 'b3';
|
||||
|
||||
-- and again, this time indexing by (b, a). Note that column "b" has non-unique
|
||||
-- values.
|
||||
drop index fpindex4;
|
||||
create index fpindex5 on fastpath(b asc, a desc);
|
||||
truncate fastpath;
|
||||
insert into fastpath select y.x, 'b' || (y.x/10)::text, 100 from (select generate_series(1,10000) as x) y;
|
||||
vacuum fastpath;
|
||||
select md5(string_agg(a::text, b order by a, b asc)) from fastpath
|
||||
where a >= 1000 and a < 2000 and b > 'b1' and b < 'b3';
|
||||
select md5(string_agg(a::text, b order by a desc, b desc)) from fastpath
|
||||
where a >= 1000 and a < 2000 and b > 'b1' and b < 'b3';
|
||||
select md5(string_agg(a::text, b order by b, a desc)) from fastpath
|
||||
where a >= 1000 and a < 2000 and b > 'b1' and b < 'b3';
|
||||
select md5(string_agg(a::text, b order by b, a asc)) from fastpath
|
||||
where a >= 1000 and a < 2000 and b > 'b1' and b < 'b3';
|
||||
|
||||
-- one last time
|
||||
drop index fpindex5;
|
||||
create index fpindex6 on fastpath(b desc, a desc);
|
||||
truncate fastpath;
|
||||
insert into fastpath select y.x, 'b' || (y.x/10)::text, 100 from (select generate_series(1,10000) as x) y;
|
||||
vacuum fastpath;
|
||||
select md5(string_agg(a::text, b order by a, b asc)) from fastpath
|
||||
where a >= 1000 and a < 2000 and b > 'b1' and b < 'b3';
|
||||
select md5(string_agg(a::text, b order by a desc, b desc)) from fastpath
|
||||
where a >= 1000 and a < 2000 and b > 'b1' and b < 'b3';
|
||||
select md5(string_agg(a::text, b order by b, a desc)) from fastpath
|
||||
where a >= 1000 and a < 2000 and b > 'b1' and b < 'b3';
|
||||
select md5(string_agg(a::text, b order by b, a asc)) from fastpath
|
||||
where a >= 1000 and a < 2000 and b > 'b1' and b < 'b3';
|
||||
|
||||
drop table fastpath;
|
||||
|
||||
-- intentionally leave some objects around
|
||||
create table idxpart (a int) partition by range (a);
|
||||
create table idxpart1 partition of idxpart for values from (0) to (100);
|
||||
|
Reference in New Issue
Block a user