文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

基于主键的索引扫描,Postgres 和 Oracle 一定比 MySQL 差吗?

2024-11-29 19:52

关注

根据定义,利用只用索引的扫描的查询,表现比其他方法更快,但在关联到事务量时,其优势更加明显。您的系统产生的查询工作负载越多,在进行只用索引的扫描时获得的好处就越多。

业务负载测试

为了更清楚地看到这一点,我们继续使用文章中的同一个表,这次有 500K 行。我们将执行以下查询:

SELECT age, register_date, is_active
FROM person
WHERE age = ;

我们将首先使用 age 列上的常规索引运行它,它必须执行索引和表读取,以检索 register_date 和 is_active 列的数据。然后,我们将使用覆盖索引进行再次练习,该索引是在 age 列上声明的,但包括了其他列。

第一个索引如下所示:

CREATE INDEX idx_person_age ON person(age);

第二个索引(覆盖索引)定义如下:

CREATE INDEX idx_person_age_cover ON person(age) INCLUDE (register_date,is_active);

为了验证结果的一致性,我们将使用不同数量的并发用户:20、100 和 200,来运行负载。结果绘制在下图中:

图片

图片

图片

我们在所有三种情况下都得到了一致的结果。在所有这些结果中,利用只用索引的扫描的业务负载每秒执行的事务数(TPS),大约是使用索引扫描的业务负载执行的 2.25 倍。

只用索引扫描的要求

对大多数业务负载使用覆盖索引或复合索引,看起来是个好主意。但是,正如我们在文章中所看到的,必须满足一些条件才能获得这些好处:

可见性条件

前两个条件很容易理解;如果我们使用支持 Index-Only Scan 功能的索引类型,并且我们的查询只获取索引覆盖的列,那我们就已经准备好了。但是,第三个条件需要格外小心,以确保我们能获得好处。让我们展开来看下第三个条件。

只用索引的扫描的目标是,避免对表页(堆)的第二次读取访问,以节省该访问时间,并直接从索引中检索数据。尽管如此,PostgreSQL 必须确保索引数据与堆中的数据保持同步。

可见性信息不存储在索引中;它属于表页,因此乍一看表读取是不可避免的。但是,有一个解决方法:可见性映射表。如果给定页面中的所有行都足够老,对当前和未来的事务都可见,则会在可见性映射表中标记该页面的状态位。

从可见性映射表来验证行页面的可见性,比读取堆表的成本要低得多,因为它很小并且大部分时间都在缓存中。

换句话说,表中的数据更改越多,它在映射表中的可见性比率就越低。因此,为了确保我们能从只用索引的扫描功能中受益,我们需要验证该条件。

如何检查表的页面可见性

如 PostgreSQL 文档中所述,可见性映射表为每个表页仅存储两个位,一个用于指示可见性,另一个用于标记行是否已冻结。此文件不能作为一个传统文件进行检查;幸运的是,我们可以依靠 pg_visibility 扩展来实现此目的。

该扩展随任何基本 PostgreSQL 安装的 contrib 模块一起提供,因此要将其添加到数据库,直接执行下面语句即可:

CREATE EXTENSION pg_visibility;

使用与前面示例相同的表,我们可以按如下方式,来检查表 person 的页面:

SELECT * FROM pg_visibility_map('person');
 blkno | all_visible | all_frozen
-------+-------------+------------
     0 | t           | t
     1 | t           | t
     2 | t           | t
[truncated…]
 10202 | t           | t
 10203 | t           | t
 10204 | t           | t
(10205 rows)

因此,我们可以看到为每个表页面的两个位设置的值,可以是 false 或 true。

一旦表开始在其数据中执行更新,相应页面的可见性状态位就会发生变化。例如,通过以下内容,我们可以知道 id 为 10000 的行所在的页面(blkno):

SELECT ctid, id FROM person WHERE id = 10000;
  ctid   |  id
---------+-------
 (204,4) | 10000
(1 row)

因此,我们可以检查第 204 页的可见性映射信息,然后更新该行并再次检查,如下:

SELECT * FROM pg_visibility_map('person')
  WHERE blkno = 204;
 blkno | all_visible | all_frozen
-------+-------------+------------
   204 | t           | t
(1 row)

UPDATE person SET register_date = now() WHERE id = 10000;

SELECT * FROM pg_visibility_map('person')
  WHERE blkno = 204;
 blkno | all_visible | all_frozen
-------+-------------+------------
   204 | f           | f
(1 row)

更新 id 为 10000 的行中的值后,可见性映射表中相应页面的 all_visible 列显示为 false。

随着不断针对表进行更新操作,可见性映射表将发生变化,标记为 all_visible 的页面数量将减少。

检查可见性百分比

了解可见性映射表变化的原因以及如何验证其内容后,我们可以检查可见页面的百分比,从而保持只用索引的扫描功能正常工作。

以下查询将显示标记为 all_visible 或 no_visible 的页面的百分比:

SELECT
    x.pages::text,
    round(avg(x.percent), 1)::int AS percentage
FROM (
    SELECT
        CASE WHEN all_visible THEN 'visible'
        WHEN NOT all_visible THEN 'no_visible'
        END pages,
        100 * COUNT(all_visible) OVER (PARTITION BY all_visible) / COUNT(all_visible) OVER () AS percent
    FROM
        pg_visibility_map ('
')) x GROUP BY x.pages UNION ALL SELECT 'no_visible'::text, 0 WHERE NOT EXISTS (SELECT 1 FROM pg_visibility_map ('
') WHERE NOT all_visible);

在新加载的没有更新的表中,或者刚刚对表进行过 VACUUM 操作(VACUUM 过程会负责更新可见性映射表),我们可以获得类似于以下内容的输出:

pages    | percentage
------------+------------
 visible    |        100
 no_visible |          0
(2 rows)

当更新发生时,标记为可见的页面的百分比也会发生变化:

UPDATE person SET register_date = now() WHERE age < 15;

SELECT
    x.pages::text,
    round(avg(x.percent), 1)::int AS percentage
FROM (
    SELECT
        CASE WHEN all_visible THEN 'visible'
        WHEN NOT all_visible THEN 'no_visible'
        END pages,
        100 * COUNT(all_visible) OVER (PARTITION BY all_visible) / COUNT(all_visible) OVER () AS percent
    FROM
        pg_visibility_map ('person')) x
GROUP BY
    x.pages
UNION ALL
SELECT
    'no_visible'::text,
    0
WHERE NOT EXISTS (SELECT 1 FROM pg_visibility_map ('person') WHERE NOT all_visible);
   pages    | percentage
------------+------------
 visible    |         46
 no_visible |         53
(2 rows)

PostgreSQL 将继续尝试执行只用索引的扫描,但是当页面在可见性映射表中标记为 “不可见” 时,它将不得不切换成不同的扫描方法,并从堆表中读取,从而失去所有性能提升。而且,标记为“不可见”的页面越多,只用索引的扫描尝试成功的可能性就越小。

在这种情况下,在对表进行 VACUUM 清理后,我们将再次看到所有页面都标记为 “可见的”:

VACUUM person;

SELECT
    x.pages::text,
    round(avg(x.percent), 1)::int AS percentage
FROM (
    SELECT
        CASE WHEN all_visible THEN 'visible'
        WHEN NOT all_visible THEN 'no_visible'
        END pages,
        100 * COUNT(all_visible) OVER (PARTITION BY all_visible) / COUNT(all_visible) OVER () AS percent
    FROM
        pg_visibility_map ('person')) x
GROUP BY
    x.pages
UNION ALL
SELECT
    'no_visible'::text,
    0
WHERE NOT EXISTS (SELECT 1 FROM pg_visibility_map ('person') WHERE NOT all_visible);
   pages    | percentage
------------+------------
 visible    |        100
 no_visible |          0
(2 rows)

结论

只用索引的扫描无疑是为我们的查询获得最佳性能的绝佳方法;但是,在添加复合索引或覆盖索引之前,我们还需要注意表在更新和页面可见性方面的行为。

借助 pg_visibility 扩展和一些查询(如我们在本文中看到的查询),我们可以监控我们的表,并确定它们是否适合使用只用索引的扫描,或者是否可以针对 VACUUM 节奏或业务负载类型进行一些调整。

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 资料下载
  • 历年真题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容
咦!没有更多了?去看看其它编程学习网 内容吧