【问题标题】:Slow query where index isn't used不使用索引的慢查询
【发布时间】:2014-06-30 18:58:34
【问题描述】:

概述

我一直在研究 Netdot,试图加快一些查询的速度。由于不需要的连接或广泛搜索,其中一些可以从 SQL 更改中受益,但有些已被证明更难追踪。

在这种特殊情况下,我有两张桌子。 fwtableentry 有 132,233,684 行。 fwttable 有 2,178,088 行。

硬件/软件版本

这是一个运行 debian_version 7.5 (wheezy) 的虚拟机。这些磁盘位于具有 RAID 0+1 的 SAN 上。这台机器有 4GB 的内存分配给它。 I/O 和内存似乎不是问题,但如果需要,我可以分配更多资源。

Linux netdot 3.2.0-4-amd64 #1 SMP Debian 3.2.57-3+deb7u2 x86_64 GNU/Linux

运行 Postgres 9.1.13-0wheezy1(debian 包版本)

Sysctl 选项

kernel.shmmax = 1500000000
vm.overcommit_memory = 2

Postgres 选项

我从默认值开始。我现在已经修改了它们,引用了我之前根据一个 postgres 调整文档调整过的另一台服务器。这些更改似乎对缓慢的查询没有帮助,但可能对其他事情有所帮助。

shared_buffers = 1GB            # min 128kB
work_mem = 64MB             # min 64kB
maintenance_work_mem = 256MB        # min 1MB
wal_buffers = 16MB          # min 32kB, -1 sets based on shared_buffers
checkpoint_segments = 32        # in logfile segments, min 1, 16MB each
checkpoint_timeout = 10min      # range 30s-1h
checkpoint_completion_target = 0.9  # checkpoint target duration, 0.0 - 1.0
random_page_cost = 2.5          # same scale as above
effective_cache_size = 2GB

说明问题

netdot=# explain analyze SELECT   ft.tstamp
              FROM     fwtableentry fte, fwtable ft
              WHERE    fte.physaddr=9115
                AND    fte.fwtable=ft.id
              GROUP BY ft.tstamp
              ORDER BY ft.tstamp DESC
              LIMIT 10
;
                                                                    QUERY PLAN
-----------------------------------------------------------------------------------------------------------------------------------------------------------
 Limit  (cost=53610.80..53610.82 rows=10 width=8) (actual time=27436.502..27436.631 rows=10 loops=1)
   ->  Sort  (cost=53610.80..53617.92 rows=2849 width=8) (actual time=27436.220..27436.258 rows=10 loops=1)
         Sort Key: ft.tstamp
         Sort Method: top-N heapsort  Memory: 25kB
         ->  HashAggregate  (cost=53520.74..53549.23 rows=2849 width=8) (actual time=27417.749..27425.805 rows=2876 loops=1)
                ->  Nested Loop  (cost=125.79..53500.91 rows=7933 width=8) (actual time=98.801..27367.988 rows=3562 loops=1)
                      ->  Bitmap Heap Scan on fwtableentry fte  (cost=125.79..18909.68 rows=7933 width=8) (actual time=97.718..26942.693 rows=3562 loops=1)
                            Recheck Cond: (physaddr = 9115)
                           ->  Bitmap Index Scan on "FWTableEntry3"  (cost=0.00..123.81 rows=7933 width=0) (actual time=86.433..86.433 rows=3562 loops=1)
                                 Index Cond: (physaddr = 9115)
                      ->  Index Scan using pk_fwtable on fwtable ft  (cost=0.00..4.35 rows=1 width=16) (actual time=0.069..0.077 rows=1 loops=3562)
                            Index Cond: (id = fte.fwtable)
 Total runtime: 27449.802 ms

这是两张表

fwttable

netdot=# \d fwtable
                                           Table "public.fwtable"
 Column |            Type             |                              Modifiers
 --------+-----------------------------+---------------------------------------------------------------------
  device | bigint                      | not null
  id     | bigint                      | not null default nextval('fwtable_id_seq'::regclass)
  tstamp | timestamp without time zone | not null default '1970-01-02 00:00:01'::timestamp without time zone
Indexes:
    "pk_fwtable" PRIMARY KEY, btree (id)
    "fwtable1" UNIQUE CONSTRAINT, btree (device, tstamp)
    "FWTable2" btree (device)
    "FWTable3" btree (tstamp)
Foreign-key constraints:
     "fk_device" FOREIGN KEY (device) REFERENCES device(id) DEFERRABLE
Referenced by:
     TABLE "fwtableentry" CONSTRAINT "fk_fwtable" FOREIGN KEY (fwtable) REFERENCES fwtable(id) DEFERRABLE

fwtableentry

netdot=# \d fwtableentry
                          Table "public.fwtableentry"
  Column   |  Type  |                         Modifiers
-----------+--------+-----------------------------------------------------------
 fwtable   | bigint | not null
 id        | bigint | not null default nextval('fwtableentry_id_seq'::regclass)
 interface | bigint | not null
 physaddr  | bigint | not null
Indexes:
    "pk_fwtableentry" PRIMARY KEY, btree (id)
    "FWTableEntry1" btree (fwtable)
    "FWTableEntry2" btree (interface)
    "FWTableEntry3" btree (physaddr)
Foreign-key constraints:
    "fk_fwtable" FOREIGN KEY (fwtable) REFERENCES fwtable(id) DEFERRABLE
    "fk_interface" FOREIGN KEY (interface) REFERENCES interface(id) DEFERRABLE
    "fk_physaddr" FOREIGN KEY (physaddr) REFERENCES physaddr(id) DEFERRABLE

这是两个表的示例

第一个 fwtableentry:

 fwtable |    id     | interface | physaddr
---------+-----------+-----------+----------
  675157 |  39733332 |     29577 |     9115
  674352 |  39686929 |     29577 |     9115
     344 |     19298 |     29577 |     9115
    1198 |     68328 |     29577 |     9115
    1542 |     88107 |     29577 |     9115
  675960 |  39779466 |     29577 |     9115
  675750 |  39766468 |     39946 |     9115
    2994 |    168721 |     29577 |     9115
    3895 |    218228 |     29577 |     9115
    4795 |    267949 |     29577 |     9115
    5695 |    324905 |     29577 |     9115
  674944 |  39720652 |     39946 |     9115
    6595 |    375149 |     29577 |     9115
    7501 |    425045 |     29577 |     9115
    8400 |    475265 |     29577 |     9115
    9298 |    524985 |     29577 |     9115
   10200 |    575136 |     29577 |     9115
   11104 |    626065 |     29577 |     9115
   12011 |    677963 |     29577 |     9115
  676580 |  39814792 |     39946 |     9115
   12914 |    731390 |     29577 |     9115
  677568 |  39871297 |     29577 |     9115
   13821 |    784435 |     29577 |     9115
  676760 |  39825496 |     29577 |     9115

fwtable(减去设备列):

   id    |       tstamp
---------+---------------------
 2178063 | 2014-06-10 17:00:13
 2177442 | 2014-06-10 16:00:06
 2176816 | 2014-06-10 15:00:07
 2176190 | 2014-06-10 14:00:09
 2175566 | 2014-06-10 13:00:07
 2174941 | 2014-06-10 12:00:07
 2174316 | 2014-06-10 11:00:07
 2173689 | 2014-06-10 10:00:06
 2173065 | 2014-06-10 09:00:06
 2172444 | 2014-06-10 08:00:06
(10 rows)

据我所知问题

所以,问题是您需要知道要发送到 fwtable 的 id,但您无法知道哪些与 10 个最新时间戳匹配,因此您需要将它们全部发送,然后让 fwtable 上的索引确定要发送哪些扔掉。

netdot=# select count(*) from fwtableentry where physaddr = 9115;
 count
-------
  3562
(1 row)

这也是缓存后查询速度很快的原因。连接的数据集并不庞大,因此一旦知道要做什么,它就可以缓存所需的一切。

您可能会问为什么不只选择最新的 10 个时间戳并与之匹配,但问题是这些时间戳可能与该 physaddr 没有任何结果,因此您需要检查连接的结果。

重写查询

select ft.tstamp from fwtable ft where ft.id in (select fwtable from fwtableentry where physaddr = 9115) order by ft.tstamp desc limit 10;

仍然提供相同的查询计划,但让我更容易将问题可视化。实际上,即使您放弃排序,以这种方式对查询进行排序也会强制使用计划。

您会认为 fwtable (id, tstamp DESC) 上的索引可能会有所帮助,但它似乎并没有被使用。我可以看到任何索引都会被混淆,因为它需要从各地获取大量结果。

我认为告诉数据库 id 和 tstamp 之间的关系是 1:1 可能会有所帮助,因此我为两者添加了唯一索引。没有。

取消限制不会影响计划。只有那种会扼杀性能。

缺少具有三个所需列的物化视图(由于我认为表大小,这是不切实际的。)我不确定解决此问题的方法,但我可能只是 SQL 不够聪明,无法实现真正的问题。

最后的笔记

您可以在第一个查询中删除 GROUP BY,它是不需要的。我已经从本示例不需要的连接中删除了一些表。它们不会像这个问题那样影响查询速度,并且通常不需要,所以我可能会重写代码以永久将它们排除在外。

我还应该提到,对架构进行剧烈更改并不是我真正能做的事情。如果它是问题的唯一解决方案,我也许可以将它作为最后的措施,但 Netdot 不是我的程序,所以我不知道我可以改变多少来解决可能比其他问题更困扰我的问题人。

【问题讨论】:

  • 尝试使用复合索引(fwtable, physaddr)(或相反)来避免大部分成本所在的位图堆扫描。不要忘记analyze;
  • 感谢您的建议。我可能忽略了提及它,但我尝试了各种字段的复合索引,但从未让它影响查询。我试过你的,它也没有用。

标签: postgresql indexing


【解决方案1】:

如果您也可以通过时间戳限制查询,那么您可能会发现 (tstamp, physaddr) 上的索引很有用*。问题是“最近30天前10”的查询是否可以接受。就目前而言,我不认为规划器可以做任何更聪明的事情,它没有理由期望 physaddr 值出现在特别的任何地方。

* 或者 (physaddr,tstamp) - 这取决于值的分布。

【讨论】:

  • 我可能会尝试更改代码以使用过去 30 天。我认为这将遵循查询的精神,即使它没有给出相同的结果。也许我可以使用新查询作为默认查询,如果他们想要更好/更慢的结果,我可以选择运行旧查询。
【解决方案2】:

尝试改写为exists(...) 而不是IN(...) 加上删除(不需要的)GROUP BY;这样可以避免聚合

SELECT ft.tstamp
FROM  fwtable ft
WHERE EXISTS(
    SELECT *
    FROM  fwtableentry fte
    WHERE fte.physaddr=9115
    AND   fte.fwtable=ft.id
    )
-- GROUP BY ft.tstamp -- you don't need this
ORDER BY ft.tstamp DESC
LIMIT 10 -- LIMIT could kill your performance...
;

【讨论】:

  • 我试过这个并得到了相同的查询计划。限制似乎也没有什么不同。不过感谢您的建议。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-10-13
  • 1970-01-01
  • 2021-09-19
  • 1970-01-01
  • 1970-01-01
  • 2015-05-19
  • 1970-01-01
相关资源
最近更新 更多