【问题标题】:Can't improve SQL join speed with indexes无法使用索引提高 SQL 连接速度
【发布时间】:2020-02-20 22:01:55
【问题描述】:

我对 SQL 完全陌生,我正在尝试加快对非常大的数据的联接查询。我开始添加索引(但说实话,我对它们没有深入的了解)并且没有看到太大的变化,我决定在一个更简单的模拟示例上进行基准测试。我在 MacOS 10.14.6 上使用 PostgreSQL 11.5 的 psql 接口。数据服务器本地托管在我的计算机上。第一次发布有关 SQL 的信息,我很抱歉缺少相关信息。

数据库的结构

我创建了两个最初相同的数据库,db 和 db_idx。我从来没有在 db 中的表上放置任何索引或键,而我尝试在 db_idx 中的表上放置索引和键。然后我分别在 db 和 db_idx 中运行简单的连接查询并比较性能。具体来说,db_idx 由两个表组成:

  • 具有 100,000 行的客户端表和以下结构:
                   Table "public.client"
       Column    |  Type   | Collation | Nullable | Default
    -------------+---------+-----------+----------+---------
     client_id   | integer |           | not null |
     client_name | text    |           |          |
    Indexes:
        "pkey_c" PRIMARY KEY, btree (client_id)
  • 具有 70,000 行和以下结构的 client_additional 表:
             Table "public.client_additional"
       Column   |  Type   | Collation | Nullable | Default
    ------------+---------+-----------+----------+---------
     client_id  | integer |           | not null |
     client_age | integer |           |          |
    Indexes:
        "pkey_ca" PRIMARY KEY, btree (client_id)
        "cov_idx" btree (client_id, client_age)

client_additional 表中的 client_id 列包含客户端的 client_id 值的子集。请注意主键和我在 client_additional 上创建的其他索引。我认为这些会提高基准查询速度(见下文),但事实并非如此。

重要的是,db 数据库完全相同(相同的结构,相同的值),只是它没有索引或键

旁注: client 和 client_additional 表可能应该是一个表,因为它们在完全相同的级别(客户端级别)提供信息。然而,我在现实生活中使用的数据库的结构是这样的:一些表被“主题”分成几个表,尽管它们提供相同级别的信息。我不知道这对我的问题是否重要。

基准查询

我正在使用以下查询,它模仿了很多我需要对真实数据执行的操作:

    SELECT 
      client_additional.client_id, 
      client_additional.client_age,
      client.client_name
    FROM client
    INNER JOIN client_additional 
    ON client.client_id = client_additional.client_id;

基准测试结果

在这两个数据库上,基准查询大约需要 630 毫秒。删除 db_idx 中的键和/或索引不会改变任何内容。这些基准测试结果适用于更大的数据量:索引和非索引情况下的速度相同。

这就是我所在的地方。我如何解释这些结果?我可以提高加入速度吗?如何提高?

【问题讨论】:

    标签: sql postgresql performance join


    【解决方案1】:

    设置一个小型测试数据库,添加一些行并运行您的查询:

    CREATE TABLE client
    (
       client_id integer PRIMARY KEY,
       client_name text
    );
    
    CREATE TABLE client_additional
    (
       client_id integer PRIMARY KEY,
       client_age integer
    );
    
    INSERT INTO client (client_id, client_name) VALUES (generate_series(1,100000),'Phil');
    INSERT INTO client_additional (client_id, client_age) VALUES (generate_series(1,70000),21);
    
    ANALYZE;
    
    EXPLAIN ANALYZE SELECT 
       client_additional.client_id, 
       client_additional.client_age,
       client.client_name
    FROM
       client
    INNER JOIN
       client_additional 
    ON
       client.client_id = client_additional.client_id;
    

    给了我这个计划:

     Hash Join  (cost=1885.00..3590.51 rows=70000 width=11) (actual time=158.958..44 1.222 rows=70000 loops=1)
       Hash Cond: (client.client_id = client_additional.client_id)
       ->  Seq Scan on client  (cost=0.00..1443.00 rows=100000 width=7) (actual time =0.019..100.318 rows=100000 loops=1)
       ->  Hash  (cost=1010.00..1010.00 rows=70000 width=8) (actual time=158.785..15 8.786 rows=70000 loops=1)
             Buckets: 131072  Batches: 1  Memory Usage: 3759kB
             ->  Seq Scan on client_additional  (cost=0.00..1010.00 rows=70000 width =8) (actual time=0.016..76.507 rows=70000 loops=1)
     Planning Time: 0.357 ms
     Execution Time: 506.739 ms
    

    您可以从中看到,两个表都被顺序扫描,每个表的值都经过哈希处理,并完成了哈希连接。 Postgres 确定这是执行此查询的最佳方式。

    如果您要重新创建没有主键的表(并因此删除每个表的 PK 列上的隐式索引),您将获得完全相同的计划,因为 Postgres 已确定执行此查询的最快方法是忽略索引并对表的值进行哈希处理,然后对两组哈希值进行哈希连接以获得结果。

    像这样改变客户表中的行数后:

    TRUNCATE Client;
    
    INSERT INTO client (client_id, client_name) VALUES (generate_series(1,200000),'phil');
    
    ANALYZE;
    

    然后我重新运行相同的查询,我看到了这个计划:

    Merge Join  (cost=1.04..5388.45 rows=70000 width=13) (actual time=0.050..415.50
    3 rows=70000 loops=1)
       Merge Cond: (client.client_id = client_additional.client_id)
       ->  Index Scan using client_pkey on client  (cost=0.42..6289.42 rows=200000 width=9) (actual time=0.022..86.897 rows=70001 loops=1)
       ->  Index Scan using client_additional_pkey on client_additional  (cost=0.29..2139.29 rows=70000 width=8) (actual time=0.016..86.818 rows=70000 loops=1)
     Planning Time: 0.517 ms
     Execution Time: 484.264 ms
    

    在这里您可以看到索引扫描已完成,因为 Postgres 根据表中的当前行数确定此计划是一个更好的计划。

    关键是 Postgres 会在感觉索引会产生更快的结果时使用索引,但使用它们之前的阈值比您预期的要高一些。

    一切顺利,

    菲尔

    【讨论】:

      【解决方案2】:

      使用EXPLAIN 动词查看 SQL 引擎打算如何解析查询。 (不同的 SQL 引擎以不同的方式呈现这一点。)您可以最终确定是否会使用索引。

      此外,您首先需要使用 大量 测试数据加载表,因为EXPLAIN 会告诉您 SQL 引擎现在打算做什么, em> 这个决定部分基于表的大小和其他各种统计数据。如果表实际上是空的,SQL 引擎可能会认为索引现在没有用。

      SQL 引擎使用各种非常巧妙的技巧来优化性能,因此实际上很难获得有用的时序测试。但是,如果 EXPLAIN 告诉您正在使用该索引,那么这几乎就是您要寻找的答案。

      【讨论】:

      • 谢谢。当您说大量数据时,很多行是否足够(1000 万?)?还是也取决于表格的宽度?
      • 我检查了查询计划,确实,查询只使用顺序扫描。我设法通过在 client_id 上添加 WHERE 子句来使其使用索引(主键)。总的来说,我只是想确保让 SQL 尽可能快。我真正的问题非常相似:我需要在它们的主键列上加入表(两边相同)。有什么结论性的建议吗?
      【解决方案3】:

      您在两个表上有一个主键,将用于joins。如果您想真正看到查询变慢,请删除主键。

      发生了什么?好吧,我的猜测是,无论有没有二级索引,执行计划都是一样的。您需要自己查看计划。

      与大多数其他数据库不同,Postgres 不会从覆盖索引中受益,因为锁定信息仅存储在数据页中。所以,数据页总是需要被访问的。

      【讨论】:

      • 谢谢。但是db数据库中绝对没有主键,join的时间和db_idx一样,有主键。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-01-05
      • 1970-01-01
      • 1970-01-01
      • 2011-10-16
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多