针对 HTTP 服务优化数据库分页

问题描述 投票:0回答:1

对于任何深入了解后端的人来说,这是一个回归基础的问题。

问题很简单我有http api 例如:

v1/foo?filter=F1&offset=100&limit=10
带有以下查询参数

  1. 限制
  2. 偏移
  3. 过滤器

此 API 回调到具有以下列的独立表上的 PG 数据库

  1. PK(串行整数)
  2. 描述(文本,不为空)
  3. 标志(文本,不为空)

我假设上述 api 的最优化查询是(在标志 col 上也有索引)

select * from table 
where Flags=${Filter}
offset ${offset}
limit ${limit}
order by PK

但是如果你从数据库引擎方面来看,这是有史以来最糟糕的查询,数据库引擎必须在整个表/索引上运行where子句(假设行数超过1000万),然后按PK对其进行排序,然后限制和偏移它..这是最低效的事情...它消除了在 API 上进行分页的原因,你不觉得吗?

我的意思是它必须扫描整个表/索引过滤所有内容的结果,然后只返回其中的一部分并对所有页面重复此操作(虽然我们认为我们已经实现了分页以减少资源的负载和压力)

那么分页的正确答案是什么样的呢? 首先我们问 PG 这个表分布在多少个块/OS 文件上,然后我们每个 api 请求取一个块,使用 where 过滤条件扫描它,然后返回结果? 它有一些副作用,例如空页面响应(其中块针对过滤条件返回零结果,即使可能有更多块需要扫描,其中可能有结果)它更像是一个迭代器接口,服务器决定页面大小和偏移量应该是多少在屈服之前。

这似乎是管理资源的最佳方式,并且真正符合分页的定义(粗略地类比扫描一本书), 但这是问题,我看到没有数据库支持这样的东西,为什么? 限制和偏移总是感觉像是经过深思熟虑后,结果被完全编译然后应用......

或者还有其他运算符/方法来实现这个简单的需求? 或者我在这里遗漏了什么?

更新:

人们在评论中谈论Keyset分页,这是削减偏移子句的好方法,但仍然无法对页面进行结束停止,这意味着服务器可以长时间工作直到满足条件..

例如:1....10000000 我可以说 offset 100 limit 20000 这意味着 >100 limit 20000 所以直到它找到 20000 行它将继续工作..

加上我的理解是 pg 首先创建整个结果集,然后应用限制,这意味着它仍然会扫描整个索引/表

这与仅扫描一个文件并返回结果不同,我认为这使得服务器只做有限的工作,因此更可预测?

postgresql database-design pagination
1个回答
0
投票

4类型的分页可供选择

  1. 简单的限制和偏移
  2. 使用 HOLD 光标分页
  3. 按键设置分页

优点和缺点都在 https://www.cybertec-postgresql.com/en/pagination-problem-total-result-count/ 有详细记录,所以不会再这样做了。

  1. 有 CTID

专业人士:

  1. 您不需要任何特殊的列或索引即可工作
  2. 数据库引擎将始终使用有限的资源完成有限的工作,这对于巨大的表和繁忙的服务器来说最重要,因为上述大多数方法都会不断地进出内存页面,这更像是检索页面的非有限工作。(如果您需要限时执行,此方法很适合)
  3. 利用数据库引擎完成的现有分页来实现高效的数据保存。

缺点:

  1. 它使用称为 CTID 的系统列,因此如果 pg 的未来版本发生变化,此方法可能不起作用。
  2. 更新时分页不稳定,但以上都不是删除时的(例如:如果第 1 页中已读取的行被更新,我会再次使用新值出现在最后一页中)
  3. 可传递的空页面,即:页面可能返回空记录,但下一页可以有更多记录。

如何: Step1:通过

从系统表中获取总页数
    Select relpages,relfilenode
FROM
    pg_class c
WHERE
    relname = 'Your_table_name'
ORDER BY relfilenode

Step2:通过

进行有限搜索
SELECT *
    FROM Your_table_name
    WHERE ctid BETWEEN '(PAGE_START,0)'::tid AND '(PAGE_END,65535)'::tid
    AND 'Add any more condition required'

瞧!!

这个想法是利用 PG 如何管理 8KB 的块/页面中的文件数据,每个页面已经定义,这是内存映射的,我们总是可以计算我们的查询将占用多少资源,因为我们知道我们有多少页面要求它读取,这也带来了查询时间的可预测性,最后节省了我们在其他列上建立大量索引的时间,因为我们处于有限的表扫描中,因此我们可以将任何条件与其组合,并且不需要单独对列进行排序或排序-ing等

示例:

创建一个表“hugedata”

CREATE TABLE hugedata (
pk SERIAL not null,
description text,
flags varchar(5) not null
);

插入一些巨大的数据:

INSERT INTO hugedata (flags)
SELECT substring((d::TEXT || '_' || (random() * 1000)::TEXT),0,5)
FROM generate_series(0,1000000,1) AS d(d);

询问表格有多少页和文件

Select relpages,relfilenode
FROM pg_class c
WHERE relname = 'hugedata'
ORDER BY relfilenode

根据任何额外条件选择要扫描的页面数并开始分页:)

SELECT *
FROM hugedata
WHERE ctid BETWEEN '(0,0)'::tid AND '(1000,65535)'::tid 
AND pk>5
© www.soinside.com 2019 - 2024. All rights reserved.