背景
1、产品的问题点
- PG GiST距离排序操作符和过滤无法同时使用索引
2、问题点背后涉及的技术原理
- PG GiST索引支持空间距离排序, 例如按距离某个经纬度点的距离排序, 返回表里面的经纬度点.
- PG 支持距离操作符, 支持排序功能, 同时支持返回距离值.
-
- 但是距离过滤不能使用索引
例子:
create extension btree_gist;
postgres=# \do
List of operators
Schema | Name | Left arg type | Right arg type | Result type | Description
--------+------+-----------------------------+-----------------------------+------------------+-------------
public | <-> | bigint | bigint | bigint |
public | <-> | date | date | integer |
public | <-> | double precision | double precision | double precision |
public | <-> | integer | integer | integer |
public | <-> | interval | interval | interval |
public | <-> | money | money | money |
public | <-> | oid | oid | oid |
public | <-> | real | real | real |
public | <-> | smallint | smallint | smallint |
public | <-> | time without time zone | time without time zone | interval |
public | <-> | timestamp with time zone | timestamp with time zone | interval |
public | <-> | timestamp without time zone | timestamp without time zone | interval |
create table t_age(id int, age int);
insert into t_age select generate_series(1,10000000), random()*120;
create index idx_t_age_1 on t_age using gist (age);
select * from t_age
where
(age <-> 25) < 1
order by age <-> 25
limit 100000;
Limit (cost=0.42..10245.61 rows=100000 width=12) (actual time=0.161..8126.988 rows=83248 loops=1)
Output: id, age, ((age <-> 25))
Buffers: shared hit=9523157
-> Index Scan using idx_t_age_1 on public.t_age (cost=0.42..341506.11 rows=3333326 width=12) (actual time=0.160..8115.150 rows=83248 loops=1)
Output: id, age, (age <-> 25)
Order By: (t_age.age <-> 25)
Filter: ((t_age.age <-> 25) < 1)
Rows Removed by Filter: 9916752
Buffers: shared hit=9523157
Planning Time: 0.077 ms
Execution Time: 8133.808 ms
(11 rows)
postgres=# set enable_seqscan=off;
SET
postgres=# explain select * from t_age where (age <-> 25) <1 limit 100000;
QUERY PLAN
-------------------------------------------------------------------------------------
Limit (cost=10000000000.00..10000005827.44 rows=100000 width=8)
-> Seq Scan on t_age (cost=10000000000.00..10000194247.66 rows=3333326 width=8)
Filter: ((age <-> 25) < 1)
(3 rows)
3、这个问题将影响哪些行业以及业务场景
- 影响最大的时基于地理位置的互联网业务, 例如社交、O2O、出行等
4、会导致什么问题?
- 当需要搜索附近的N个点, 并且距离M以内的双重需求时, 不能同时使用1个索引来满足. (要么只能用于排序, 要么只能用于where过滤)
- 需要额外的filter计算, 如果满足条件(距离M以内)的记录数不足N条, 则导致扫描整个索引, 性能急剧下降.
5、业务上应该如何避免这个坑
- 可以使用function来解决这个问题, 每次filter判定是否已越界.
6、业务上避免这个坑牺牲了什么, 会引入什么新的问题
- 需要自定义函数, 开发成本增加.
7、数据库未来产品迭代如何修复这个坑
- 希望能直接在内核层面支持, 同一个index既能支持按距离过滤又能支持按距离排序输出.