【发布时间】: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