按查询对时间戳列排序非常慢

Posted

技术标签:

【中文标题】按查询对时间戳列排序非常慢【英文标题】:Order by query on timestamp column is very slow 【发布时间】:2015-09-09 12:28:47 【问题描述】:

下面的查询需要大约 15 秒才能返回数据,尽管有索引,并且 id 作为主键。

select id from my_table order by insert_date offset 0 limit 1

解释分析如下

"Limit  (cost=1766417.72..1766417.72 rows=1 width=12) (actual time=32479.440..32479.441 rows=1 loops=1)"
"  ->  Sort  (cost=1766417.72..1797117.34 rows=12279848 width=12) (actual time=32479.437..32479.437 rows=1 loops=1)"
"        Sort Key: insert_date"
"        Sort Method: top-N heapsort  Memory: 25kB"
"        ->  Seq Scan on my_table  (cost=0.00..1705018.48 rows=12279848 width=12) (actual time=0.006..21338.401 rows=12108916 loops=1)"
"Total runtime: 32479.476 ms"

我的表几乎没有其他列。但是insert_date 的类型是

insert_date timestamp without time zone NOT NULL DEFAULT now(),

我在那个特定的日期列上有一个索引

CREATE INDEX my_table_insert_date_indx
  ON my_table
  USING btree
  (insert_date)
TABLESPACE somexyz_idx_ts;

postgresql.conf 文件中的几个值:

shared_buffers = more than 1GB    ## just for an example
temp_buffers = more than 1GB
work_mem = more than 1GB
maintenance_work_mem = more than 1GB
dynamic_shared_memory_type = posix
default_statistics_target = 10000
autovacuum = on
random_page_cost = 2.0
cpu_index_tuple_cost = 0.0005

我现在正在使用 postgres 9.3。

更新::

我刚刚运行了以下查询:

select insert_date, count(*) from my_table group by insert_date

结果中的前几个是:

"2015-04-02 00:00:00";3718104
"2015-04-03 00:00:00";6410253
"2015-04-04 00:00:00";538247
"2015-04-05 00:00:00";1228877
"2015-04-06 00:00:00";131248

我在该表上有大约 1200 万条记录。上面的数字几乎接近这个总数。

不确定,但是否会在具有大量重复值的列上创建索引?如果是真的,那我们有什么办法吗?

【问题讨论】:

也许是一个更好的地方问:dba.stackexchange.com 最近在 SO 上有一个类似的问题,我认为结论可能是 9.4 更擅长按索引列排序以避免排序。可能想搜索那个问题。 请使用set enable_seqscan = off; 测试相同的查询并显示解释分析输出。你的索引和表有多大? psql 中的\di+ my_table_insert_date_indx\dt+ my_table 命令将显示大小 @alexius 禁用 seqscan 不会对分析结果进行任何更改。但是,在实际环境中,我不能仅为了改进此特定查询而禁用 seqscan。我的表大小是23 GB,索引大小是293 MB 【参考方案1】:

在我使用 PostgreSQL 9.3 和 9.4 的机器上,您的查询运行速度快了大约 160000 倍。我的机器没什么特别的。

-- From PostgreSQL 9.4; 9.3 is similar.
show shared_buffers; -- 128MB
show temp_buffers; -- 8MB
show work_mem; -- 4MB
show maintenance_work_mem; -- 64MB
show dynamic_shared_memory_type; -- posix
show default_statistics_target; -- 100
show autovacuum; -- on
show random_page_cost; -- 4
show cpu_index_tuple_cost; -- 0.005

准备

让我们建一个表。 (你应该在你的问题中做到这一点。)

create table my_table (
  id serial primary key,
  insert_date timestamp not null
);

-- Round numbers of rows.
insert into my_table(insert_date)
select timestamp '2015-04-02 00:00:00'
from generate_series(1, 3000000) n;

insert into my_table(insert_date)
select timestamp '2015-04-03 00:00:00'
from generate_series(1, 6000000) n;

insert into my_table(insert_date)
select timestamp '2015-04-04 00:00:00'
from generate_series(1, 500000) n;

insert into my_table(insert_date)
select timestamp '2015-04-05 00:00:00'
from generate_series(1, 1200000) n;

insert into my_table(insert_date)
select timestamp '2015-04-06 00:00:00'
from generate_series(1, 131000) n;

创建索引并更新统计信息。

create index on my_table (insert_date);
analyze my_table;

PostgreSQL 9.4

现在,我们从您的第一个查询中得到什么样的执行计划?

explain analyze 
select id from my_table order by insert_date offset 0 limit 1;
“限制(成本=0.43..0.48 行=1 宽度=12)(实际时间=0.014..0.014 行=1 循环=1)” “ -> 使用 my_table 上的 my_table_insert_date_idx 进行索引扫描(成本=0.43..540656.27 行=11200977 宽度=12)(实际时间=0.012..0.012 行=1 循环=1)” “计划时间:0.195 毫秒” “执行时间:0.032 毫秒”

PostgreSQL 9.3

explain analyze 
select id from my_table order by insert_date offset 0 limit 1;
“限制(成本=0.43..0.47 行=1 宽度=12)(实际时间=0.058..0.059 行=1 循环=1)” “ -> 使用 my_table 上的 my_table_insert_date_idx 进行索引扫描(成本=0.43..339814.36 行=10830995 宽度=12)(实际时间=0.057..0.057 行=1 循环=1)” “总运行时间:0.098 毫秒”

您的查询

select id from my_table order by insert_date offset 0 limit 1;

不确定。有 300 万行具有最低的 insert_date(根据 ORDER BY 子句将首先出现的日期)。你从这 300 万个中选择一个。 PostgreSQL 不保证你每次都会得到相同的 id。

如果您不关心它返回的 300 万个 id 中的哪一个,您可以用不同的方式表达查询。但我不认为以不同的方式表达它会给你 160k 倍的加速。

您包含的某些设置可以针对特定查询进行更改。所以你可以做这样的事情。

-- Don't commit or rollback . . . 
begin transaction;
set local work_mem = '8 MB';

explain analyze 
select id from my_table order by insert_date offset 0 limit 1;
-- Displays the result. 

手动提交或回滚。

commit;

您的 work_mem 设置返回到服务器启动时设置的值。

show work_mem; -- 4MB

【讨论】:

以上是关于按查询对时间戳列排序非常慢的主要内容,如果未能解决你的问题,请参考以下文章

在时间戳列上为使用年份函数的查询创建索引

SQL 查询:如何在排名列值中使用时间戳列

按时间戳列过滤/选择熊猫数据帧的行

在mysql中按查询排序变得非常慢

Oracle在另一个表中获取按时间排序的前行和后行

按 id 排序的复合索引上的并发查询非常慢