具有偏移限制的选择查询太慢

Sab*_*san 12 postgresql postgresql-9.1

我从互联网资源中读到,当偏移量增加时,查询会很慢.但就我而言,我觉得它太慢了.我在用postgres 9.3

这是查询(id是主键):

select * from test_table offset 3900000 limit 100;
Run Code Online (Sandbox Code Playgroud)

它返回我周围的数据10 seconds.我觉得它太慢了.我4 million在桌子上有记录.数据库的总体大小是23GB.

机器配置:

RAM: 12 GB
CPU: 2.30 GHz
Core: 10
Run Code Online (Sandbox Code Playgroud)

postgresql.conf我更改的文件中的几个值如下所示.其他人是默认的.

shared_buffers = 2048MB
temp_buffers = 512MB
work_mem = 1024MB
maintenance_work_mem = 256MB
dynamic_shared_memory_type = posix
default_statistics_target = 10000
autovacuum = on
enable_seqscan = off   ## its not making any effect as I can see from Analyze doing seq-scan
Run Code Online (Sandbox Code Playgroud)

除了这些,我也通过改变的值试过random_page_cost = 2.0cpu_index_tuple_cost = 0.0005和结果是一样的.

Explain (analyze, buffers) 查询结果如下:

"Limit  (cost=10000443876.02..10000443887.40 rows=100 width=1034) (actual time=12793.975..12794.292 rows=100 loops=1)"
"  Buffers: shared hit=26820 read=378984"
"  ->  Seq Scan on test_table  (cost=10000000000.00..10000467477.70 rows=4107370 width=1034) (actual time=0.008..9036.776 rows=3900100 loops=1)"
"        Buffers: shared hit=26820 read=378984"
"Planning time: 0.136 ms"
"Execution time: 12794.461 ms"
Run Code Online (Sandbox Code Playgroud)

postgres中世界各地的人们如何与这个问题进行谈判?任何替代解决方案对我也有帮助.

更新::添加order by id(尝试使用其他索引列),这里是解释:

"Limit  (cost=506165.06..506178.04 rows=100 width=1034) (actual time=15691.132..15691.494 rows=100 loops=1)"
"  Buffers: shared hit=110813 read=415344"
"  ->  Index Scan using test_table_pkey on test_table  (cost=0.43..533078.74 rows=4107370 width=1034) (actual time=38.264..11535.005 rows=3900100 loops=1)"
"        Buffers: shared hit=110813 read=415344"
"Planning time: 0.219 ms"
"Execution time: 15691.660 ms"
Run Code Online (Sandbox Code Playgroud)

Den*_*rdy 41

它很慢,因为它需要找到顶offset行并扫描下一行.当你处理巨大的偏移时,没有多少优化会改变.

这是因为您的查询从字面上指示数据库引擎通过使用offset 3900000- 这是3.9M行来访问大量行.加速这种情况的选择并不多.

超高速RAM,SSD等将有所帮助.但是你只会在这样做时获得一个恒定的因素,这意味着它只是在你的道路上踢,直到你达到足够大的偏移量.

确保桌子适合记忆,除了第一次以外,还有更多的备用因素同样有助于更大的常数因素.但是,对于足够大的表或索引,这可能是不可能的.

确保您进行仅索引扫描将在一定程度上起作用.(请参阅velis的回答;它有很多优点.)这里的问题是,出于所有实际目的,您可以将索引视为存储磁盘位置和索引字段的表.(它比这更优化,但它是一个合理的第一近似值.)如果有足够的行,你仍然会遇到更大偏移量的问题.

试图存储和维护行的精确位置也是一种昂贵的方法.(这是由benjist建议的.)虽然技术上可行,但它受到类似于使用具有树结构的MPTT的限制的限制:你会在读取时获得显着的收益,但是当插入,更新或删除节点时,最终需要过多的写入时间,以便需要同时更新大块数据.

正如希望更清楚的那样,当你处理这么大的补偿时,没有任何真正的神奇子弹.看待替代方法通常会更好.

如果您基于ID(或日期字段或任何其他可索引字段集)进行分页,则潜在的技巧(例如,blogspot使用)将使您的查询从索引中的任意点开始.

换句话说,而不是:

example.com?page_number=[huge]
Run Code Online (Sandbox Code Playgroud)

做类似的事情:

example.com?page_following=[huge]
Run Code Online (Sandbox Code Playgroud)

这样,您可以跟踪索引中的位置,并且查询变得非常快,因为它可以直接到达正确的起点,而无需翻阅大量的行:

select * from foo where ID > [huge] order by ID limit 100
Run Code Online (Sandbox Code Playgroud)

当然,你失去了跳到例如第3000页的能力.但是请给出一些诚实的想法:你最后一次跳到网站上的大页码而不是直接进行月度档案或使用其搜索框时是什么时候?

如果你是分页但想要以任何方式保持页面偏移,另一种方法是禁止使用更大的页码.这并不愚蠢:谷歌正在对搜索结果做些什么.在运行搜索查询时,Google会为您提供估计的结果数量(您可以使用合理的数字explain),然后将允许您显示前几千个结果 - 仅此而已.除此之外,他们出于性能原因这样做 - 恰恰是你遇到的那个.

  • 这应该是最快的解决方案,但是值得一提的是页面的随机访问不能用它实现,只有一个简单的*previous*&*next*页面(用于分页). (2认同)
  • @SabujHassan:对于那种用例,你真的没有太多可以做的......一个巨大的偏移将使数据库引擎在开始输出你正在寻找的那些之前犁过几十亿行,而不是希望,祈祷或希望的数量将使数据库引擎神奇地读取更少的行:你实际上*指示*它阅读大量的行.超快速的硬件,或者确保你只进行索引扫描,可能会有所改善; 但不是物质上的.当然,确保桌子适合记忆,还有更多的空间,也会改善. (2认同)

vel*_*lis 6

我赞成了Denis的答案,但我会自己添加一个建议,也许它可以为您的特定用例带来一些性能上的好处:

假设你的实际表不是test_table,但是一些巨大的复合查询,可能有多个连接.您可以先确定所需的起始ID:

select id from test_table order by id offset 3900000 limit 1
Run Code Online (Sandbox Code Playgroud)

这应该比原始查询快得多,因为它只需要扫描索引与整个表.获得此ID然后打开快速索引搜索选项以进行完全提取:

select * from test_table where id >= (what I got from previous query) order by id limit 100
Run Code Online (Sandbox Code Playgroud)