无法使用索引提高SQL连接速度

时间:2019-10-24 14:24:55

标签: sql postgresql performance join

我对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上创建的其他索引。我以为这些可以提高基准查询速度(请参见下文),但并没有。

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

附带说明: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中的键和/或索引不会更改任何内容。这些基准测试结果会延续到更大的数据量:在建立索引和未建立索引的情况下,速度是相同的。

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

3 个答案:

答案 0 :(得分:1)

使用EXPLAIN动词查看SQL引擎打算如何解析查询。 (不同的SQL引擎以不同的方式显示此信息。)您可以最终确定是否使用索引。

此外,您首先需要用很多测试数据加载表,因为EXPLAIN会告诉您SQL引擎现在打算做什么,此决定部分取决于表格的大小和其他各种统计信息。如果该表实际上为空,则SQL引擎可能会确定现在。

对索引无用。

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

答案 1 :(得分:0)

在两个表上有一个主键,将用于join。如果您确实希望查询速度变慢,请删除主键。

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

与大多数其他数据库不同,Postgres不能从覆盖索引中受益,因为锁信息仅存储在数据页中。因此,始终需要访问数据页。

答案 2 :(得分:0)

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

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会在感觉到索引产生更快的结果时使用索引,但是使用它们之前的阈值比您预期的要高。

祝一切顺利

菲尔