【发布时间】:2012-09-14 16:27:48
【问题描述】:
问题:
我的数据库中有与时间相关的数据,我正在努力以某种方式组织、构造和索引这些数据,以便用户可以有效地检索它;即使是简单的数据库查询也需要更长的时间。
项目背景:
虽然这是一个纯粹的数据库问题,但某些上下文可能有助于理解数据模型:
该项目的中心是对大型复杂机器进行研究。我对机器本身了解不多,但实验室里有传言说在某处有一个flux capacitor - 我想昨天,我发现Schrödinger's cat 的尾巴挂在它旁边;- )
我们在机器运行时使用传感器测量许多不同的参数的时间。我们不仅使用一个设备来测量这些参数,而且还使用整个范围的参数;它们的测量数据质量不同(我认为这涉及采样率、传感器质量、价格和我不关心的许多其他方面);该项目的一个目标实际上是在这些设备之间进行比较。您可以将这些测量设备想象成一堆实验室手推车,每个手推车都有许多连接到机器的电缆,每个都提供测量数据。
数据模型:
每个参数都有来自每个地点和每个设备的测量数据,例如,每分钟一次,为期 6 天。我的工作是将这些数据存储在数据库中并提供对它的有效访问。
简而言之:
- 设备具有唯一的名称
- 参数也有名称;它们不是唯一的,所以它也有一个 ID
- 一个地点有一个 ID
项目数据库当然更复杂,但这些细节似乎与问题无关。
- 测量数据索引具有 ID、测量完成时间的时间戳以及对设备的引用以及进行测量的地点
- 测量数据值具有对参数和实际测量值的引用
最初,我将测量数据值建模为具有自己的 ID 作为主键;测量数据索引和值之间的n:m 关系是一个单独的表,只存储了index:value ID 对,但是由于该表本身占用了相当多的硬盘空间,我们将其删除并将值 ID 更改为一个简单的整数存储其所属的测量数据索引的ID;测量数据值的主键现在由该 ID 和参数 ID 组成。
附带说明:当我创建数据模型时,我仔细遵循了常见的设计准则,例如 3NF 和适当的表约束(例如唯一键);另一个经验法则是为每个外键创建一个索引。我怀疑测量数据索引/值表与“严格”3NF 的偏差可能是我现在正在查看的性能问题的原因之一,但是将数据模型改回来并没有解决问题。
DDL 中的数据模型:
注意:下面有此代码的更新。
下面的脚本创建数据库和所有相关的表。请注意,目前还没有明确的索引。在运行此程序之前,请确保您没有碰巧拥有一个名为 so_test 的数据库,其中包含任何有价值的数据...
\c postgres
DROP DATABASE IF EXISTS so_test;
CREATE DATABASE so_test;
\c so_test
CREATE TABLE device
(
name VARCHAR(16) NOT NULL,
CONSTRAINT device_pk PRIMARY KEY (name)
);
CREATE TABLE parameter
(
-- must have ID as names are not unique
id SERIAL,
name VARCHAR(64) NOT NULL,
CONSTRAINT parameter_pk PRIMARY KEY (id)
);
CREATE TABLE spot
(
id SERIAL,
CONSTRAINT spot_pk PRIMARY KEY (id)
);
CREATE TABLE measurement_data_index
(
id SERIAL,
fk_device_name VARCHAR(16) NOT NULL,
fk_spot_id INTEGER NOT NULL,
t_stamp TIMESTAMP NOT NULL,
CONSTRAINT measurement_pk PRIMARY KEY (id),
CONSTRAINT measurement_data_index_fk_2_device FOREIGN KEY (fk_device_name)
REFERENCES device (name) MATCH FULL
ON UPDATE NO ACTION ON DELETE NO ACTION,
CONSTRAINT measurement_data_index_fk_2_spot FOREIGN KEY (fk_spot_id)
REFERENCES spot (id) MATCH FULL
ON UPDATE NO ACTION ON DELETE NO ACTION,
CONSTRAINT measurement_data_index_uk_all_cols UNIQUE (fk_device_name, fk_spot_id, t_stamp)
);
CREATE TABLE measurement_data_value
(
id INTEGER NOT NULL,
fk_parameter_id INTEGER NOT NULL,
value VARCHAR(16) NOT NULL,
CONSTRAINT measurement_data_value_pk PRIMARY KEY (id, fk_parameter_id),
CONSTRAINT measurement_data_value_fk_2_parameter FOREIGN KEY (fk_parameter_id)
REFERENCES parameter (id) MATCH FULL
ON UPDATE NO ACTION ON DELETE NO ACTION
);
我还创建了一个脚本来用一些测试数据填充表格:
CREATE OR REPLACE FUNCTION insert_data()
RETURNS VOID
LANGUAGE plpgsql
AS
$BODY$
DECLARE
t_stamp TIMESTAMP := '2012-01-01 00:00:00';
index_id INTEGER;
param_id INTEGER;
dev_name VARCHAR(16);
value VARCHAR(16);
BEGIN
FOR dev IN 1..5
LOOP
INSERT INTO device (name) VALUES ('dev_' || to_char(dev, 'FM00'));
END LOOP;
FOR param IN 1..20
LOOP
INSERT INTO parameter (name) VALUES ('param_' || to_char(param, 'FM00'));
END LOOP;
FOR spot IN 1..10
LOOP
INSERT INTO spot (id) VALUES (spot);
END LOOP;
WHILE t_stamp < '2012-01-07 00:00:00'
LOOP
FOR dev IN 1..5
LOOP
dev_name := 'dev_' || to_char(dev, 'FM00');
FOR spot IN 1..10
LOOP
INSERT INTO measurement_data_index
(fk_device_name, fk_spot_id, t_stamp)
VALUES (dev_name, spot, t_stamp) RETURNING id INTO index_id;
FOR param IN 1..20
LOOP
SELECT id INTO param_id FROM parameter
WHERE name = 'param_' || to_char(param, 'FM00');
value := 'd' || to_char(dev, 'FM00')
|| '_s' || to_char(spot, 'FM00')
|| '_p' || to_char(param, 'FM00');
INSERT INTO measurement_data_value (id, fk_parameter_id, value)
VALUES (index_id, param_id, value);
END LOOP;
END LOOP;
END LOOP;
t_stamp := t_stamp + '1 minute'::INTERVAL;
END LOOP;
END;
$BODY$;
SELECT insert_data();
PostgreSQL 查询计划器需要最新的统计信息,因此请分析所有表。可能不需要吸尘,但无论如何都要这样做:
VACUUM ANALYZE device;
VACUUM ANALYZE measurement_data_index;
VACUUM ANALYZE measurement_data_value;
VACUUM ANALYZE parameter;
VACUUM ANALYZE spot;
示例查询:
如果我现在运行一个非常简单的查询,例如获取某个参数的所有值,已经花费了几秒钟,虽然数据库还不是很大:
EXPLAIN (ANALYZE ON, BUFFERS ON)
SELECT measurement_data_value.value
FROM measurement_data_value, parameter
WHERE measurement_data_value.fk_parameter_id = parameter.id
AND parameter.name = 'param_01';
我的开发机器上的示例结果(有关我的环境的一些详细信息,请参见下文):
QUERY PLAN
------------------------------------------------------------------------------------------------------------------------------------------
Hash Join (cost=1.26..178153.26 rows=432000 width=12) (actual time=0.046..2281.281 rows=432000 loops=1)
Hash Cond: (measurement_data_value.fk_parameter_id = parameter.id)
Buffers: shared hit=55035
-> Seq Scan on measurement_data_value (cost=0.00..141432.00 rows=8640000 width=16) (actual time=0.004..963.999 rows=8640000 loops=1)
Buffers: shared hit=55032
-> Hash (cost=1.25..1.25 rows=1 width=4) (actual time=0.010..0.010 rows=1 loops=1)
Buckets: 1024 Batches: 1 Memory Usage: 1kB
Buffers: shared hit=1
-> Seq Scan on parameter (cost=0.00..1.25 rows=1 width=4) (actual time=0.004..0.008 rows=1 loops=1)
Filter: ((name)::text = 'param_01'::text)
Buffers: shared hit=1
Total runtime: 2313.615 ms
(12 rows)
除了隐式索引之外,数据库中没有索引,因此规划器只进行顺序扫描也就不足为奇了。如果我遵循似乎是经验法则并为每个外键添加 btree 索引,例如
CREATE INDEX measurement_data_index_idx_fk_device_name
ON measurement_data_index (fk_device_name);
CREATE INDEX measurement_data_index_idx_fk_spot_id
ON measurement_data_index (fk_spot_id);
CREATE INDEX measurement_data_value_idx_fk_parameter_id
ON measurement_data_value (fk_parameter_id);
然后再做一次真空分析(为了安全起见)并重新运行查询,规划器使用位图堆和位图索引扫描,总查询时间有所改善:
QUERY PLAN
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Nested Loop (cost=8089.19..72842.42 rows=431999 width=12) (actual time=66.773..1336.517 rows=432000 loops=1)
Buffers: shared hit=55033 read=1184
-> Seq Scan on parameter (cost=0.00..1.25 rows=1 width=4) (actual time=0.005..0.012 rows=1 loops=1)
Filter: ((name)::text = 'param_01'::text)
Buffers: shared hit=1
-> Bitmap Heap Scan on measurement_data_value (cost=8089.19..67441.18 rows=431999 width=16) (actual time=66.762..1237.488 rows=432000 loops=1)
Recheck Cond: (fk_parameter_id = parameter.id)
Buffers: shared hit=55032 read=1184
-> Bitmap Index Scan on measurement_data_value_idx_fk_parameter_id (cost=0.00..7981.19 rows=431999 width=0) (actual time=65.222..65.222 rows=432000 loops=1)
Index Cond: (fk_parameter_id = parameter.id)
Buffers: shared read=1184
Total runtime: 1371.716 ms
(12 rows)
但是,对于一个非常简单的查询,这仍然需要一秒以上的执行时间。
到目前为止我做了什么:
- 给自己买了一份PostgreSQL 9.0 High Performance - 好书!
- 做了一些基本的 PostgreSQL 服务器配置,参见下面的环境
- 创建了一个框架来使用来自项目的真实查询运行一系列性能测试并以图形方式显示结果;这些查询使用设备、点、参数和时间间隔作为输入参数,并且测试系列运行在例如5、10 个设备、5、10 个点、5、10、15、20 个参数和 1..7 天。基本结果是它们都太慢了,但它们的查询计划太复杂了,我无法理解,所以我回到上面使用的非常简单的查询。
我查看了partitioning 的值表。数据与时间相关,分区似乎是组织此类数据的合适方法;甚至 PostgreSQL 文档中的 examples 也使用了类似的东西。不过,我在same article读到:
这些好处通常只有在表格非常大的情况下才值得。表从分区中受益的确切时间取决于应用程序,但经验法则是表的大小应该超过数据库服务器的物理内存。
整个测试数据库的大小小于 1GB,我在具有 8GB RAM 的开发机器和具有 1GB 的虚拟机上运行测试(另请参见下面的环境),因此该表远非很大甚至超过物理内存。无论如何,我可能会在某个阶段实现分区,但我觉得这种方法并不针对性能问题本身。
此外,我正在考虑cluster 值表。我不喜欢每次插入新数据时都必须重新进行集群并且它还需要独占读/写锁这一事实,但看看this SO 问题,它似乎无论如何都有它的好处并且可能是一个选项.但是,集群是在索引上完成的,由于查询中最多有 4 个选择条件(设备、点、参数和时间),我必须为所有这些条件创建集群——这反过来给我的印象是我只是没有创建正确的索引...
我的环境:
- 正在开发具有双核 CPU 和 8GB RAM 的 MacBook Pro(2009 年中)
- 我正在 MBP 上托管的具有 1GB RAM 的虚拟 Debian 6.0 机器上运行数据库性能测试
- PostgreSQL 版本是 9.1,因为这是我安装时的最新版本,可以升级到 9.2
- 按照PostgreSQL docs 中的建议,我已将两台机器上的
shared_buffers从默认的1600kB 更改为25% 的RAM(这涉及扩大kernel settings,如SHMALL、SHMMAX 等) - 同样,我已将 effective_cache_size 从默认的 128MB 更改为可用 RAM 的 50%
- 我使用不同的work_mem 设置进行了性能测试,但没有发现任何主要的性能差异
注意:我认为重要的一个方面是,具有来自项目的真实查询的性能测试系列在 8GB 的 MacBook 和 1GB 的虚拟机之间在性能方面没有差异;也就是说,如果在 MacBook 上查询需要 10 秒,那么在 VM 上也需要 10 秒。另外,我在更改shared_buffers、effective_cache_size 和work_mem 前后进行了相同的性能测试,但配置更改并没有将性能提高10% 以上;实际上,有些结果甚至变得更糟,因此似乎任何差异都是由测试变化而不是由配置更改引起的。这些观察让我相信 RAM 和 postgres.conf 设置还不是这里的限制因素。
我的问题:
我不知道不同的或额外的索引是否会加快查询速度,如果可以,我不知道要创建哪些索引。查看数据库的大小和查询的简单程度,我觉得我的数据模型或到目前为止我选择索引的方式存在根本性错误。
有人对我如何构建和索引时间相关的 my 以提高查询性能有什么建议吗?
问得更广泛,是调优查询性能
- 通常是“在事件基础上”完成的,即一旦查询不能令人满意地执行?看来所有我的查询都太慢了...
- 主要是查看(和理解)查询计划的问题,然后添加索引并衡量情况是否有所改善,可能会通过应用自己的经验来加速流程?
如何让这个数据库运行起来?
更新 01:
看看到目前为止的回复,我想我没有正确解释测量数据索引/值表的必要性,所以让我再试一次。 存储空间是这里的问题。
注意:
- 此处使用的数字更多地用于说明目的,仅用于比较,即数字本身并不相关,重要的是使用单个表与使用索引和值表之间存储要求的百分比差异
- PostgreSQL 数据类型存储大小记录在this 章节中
- 这并没有声称在科学上是正确的,例如这些单位可能在数学上是伪造的;这些数字应该加起来
假设
- 1 天测量
- 每分钟 1 组测量值
- 10 台设备
- 10个参数
- 10 点
这加起来
1 测量/分钟 x 60 分钟/小时 x 24 小时/天 = 1440 测量/天每次测量都有来自每个地点和每个设备的每个参数的数据,所以
10 个点 x 10 个设备 x 10 个参数 = 1000 个数据集/测量总之
1440 个测量值/天 x 1000 个数据集/测量值 = 1 440 000 个数据集/天如果我们将所有测量值存储在单个表中为Catcall suggested,例如
CREATE TABLE measurement_data
(
device_name character varying(16) NOT NULL,
spot_id integer NOT NULL,
parameter_id integer NOT NULL,
t_stamp timestamp without time zone NOT NULL,
value character varying(16) NOT NULL,
-- constraints...
);
一行会加起来
17 + 4 + 4 + 8 + 17 = 50 字节/行在最坏的情况下,所有 varchar 字段都已完全填充。这相当于
50 字节/行 x 1 440 000 行/天 = 72 000 000 字节/天或每天约 69 MB。
虽然这听起来不是很多,但实际数据库中的存储空间要求会令人望而却步(再次重申,此处使用的数字仅用于说明)。因此,我们将测量数据拆分为索引和值表,如问题前面所述:
CREATE TABLE measurement_data_index
(
id SERIAL,
fk_device_name VARCHAR(16) NOT NULL,
fk_spot_id INTEGER NOT NULL,
t_stamp TIMESTAMP NOT NULL,
-- constraints...
);
CREATE TABLE measurement_data_value
(
id INTEGER NOT NULL,
fk_parameter_id INTEGER NOT NULL,
value VARCHAR(16) NOT NULL,
-- constraints...
);
其中值行的 ID 等于其所属索引的 ID。
索引表和值表中一行的大小是
索引:4 + 17 + 4 + 8 = 33 字节 值:4 + 4 + 17 = 25 个字节(同样,最坏的情况)。总行数为
索引:10 个设备 x 10 个点 x 1440 次测量/天 = 144 000 行/天 值:10 个参数 x 144 000 行/天 = 1 440 000 行/天所以总数是
索引:33 字节/行 x 144 000 行/天 = 4 752 000 字节/天 值:25 字节/行 x 1 440 000 行/天 = 36 000 000 字节/天 总计:= 40 752 000 字节/天或每天约 39 MB - 而单表解决方案约 69 MB。
更新 02(回复:wildplassers response):
这个问题已经变得很长了,所以我正在考虑更新上面原始问题中的代码,但我认为这里有第一个和改进的解决方案可能有助于更好地看到差异.
与原始方法相比的变化(按重要性排序):
- 交换时间戳和参数,即将
t_stamp字段从measurement_data_index表移动到measurement_data_value,将fk_parameter_id字段从值移动到索引表:通过这个改变,索引表中的所有字段都是不变的并且是新的度量数据仅写入值表。我没想到这会带来任何重大的查询性能改进(我错了),但我觉得它使测量数据索引的概念更加清晰。虽然它需要更多的存储空间(根据一些相当粗略的估计),但当 tablespaces 根据其读/写要求移动到不同的硬盘驱动器时,拥有一个“静态”索引表也可能有助于部署。 - 在设备表中使用代理键:据我了解,代理键是从数据库设计的角度来看并非严格要求的主键(例如设备名称已经是唯一的,因此它也可以用作 PK ),但可能有助于提高查询性能。我再次添加它是因为,如果索引表仅引用 ID(而不是某些名称和某些 ID),我觉得它会使概念更清晰。
- 重写
insert_data():使用generate_series()而不是嵌套的FOR循环;使代码更加“简洁”。 - 作为这些更改的副作用,插入测试数据只需要第一个解决方案所需时间的大约 50%。
- 我没有按照 wildplasser 的建议添加视图;不需要向后兼容。
- 查询计划器似乎忽略了索引表中 FK 的其他索引,并且对查询计划或性能没有影响。
(好像没有这行,下面的代码没有正确显示为SO页面上的代码……)
\c postgres
DROP DATABASE IF EXISTS so_test_03;
CREATE DATABASE so_test_03;
\c so_test_03
CREATE TABLE device
(
id SERIAL,
name VARCHAR(16) NOT NULL,
CONSTRAINT device_pk PRIMARY KEY (id),
CONSTRAINT device_uk_name UNIQUE (name)
);
CREATE TABLE parameter
(
id SERIAL,
name VARCHAR(64) NOT NULL,
CONSTRAINT parameter_pk PRIMARY KEY (id)
);
CREATE TABLE spot
(
id SERIAL,
name VARCHAR(16) NOT NULL,
CONSTRAINT spot_pk PRIMARY KEY (id)
);
CREATE TABLE measurement_data_index
(
id SERIAL,
fk_device_id INTEGER NOT NULL,
fk_parameter_id INTEGER NOT NULL,
fk_spot_id INTEGER NOT NULL,
CONSTRAINT measurement_pk PRIMARY KEY (id),
CONSTRAINT measurement_data_index_fk_2_device FOREIGN KEY (fk_device_id)
REFERENCES device (id) MATCH FULL
ON UPDATE NO ACTION ON DELETE NO ACTION,
CONSTRAINT measurement_data_index_fk_2_parameter FOREIGN KEY (fk_parameter_id)
REFERENCES parameter (id) MATCH FULL
ON UPDATE NO ACTION ON DELETE NO ACTION,
CONSTRAINT measurement_data_index_fk_2_spot FOREIGN KEY (fk_spot_id)
REFERENCES spot (id) MATCH FULL
ON UPDATE NO ACTION ON DELETE NO ACTION,
CONSTRAINT measurement_data_index_uk_all_cols UNIQUE (fk_device_id, fk_parameter_id, fk_spot_id)
);
CREATE TABLE measurement_data_value
(
id INTEGER NOT NULL,
t_stamp TIMESTAMP NOT NULL,
value VARCHAR(16) NOT NULL,
-- NOTE: inverse field order compared to wildplassers version
CONSTRAINT measurement_data_value_pk PRIMARY KEY (id, t_stamp),
CONSTRAINT measurement_data_value_fk_2_index FOREIGN KEY (id)
REFERENCES measurement_data_index (id) MATCH FULL
ON UPDATE NO ACTION ON DELETE NO ACTION
);
CREATE OR REPLACE FUNCTION insert_data()
RETURNS VOID
LANGUAGE plpgsql
AS
$BODY$
BEGIN
INSERT INTO device (name)
SELECT 'dev_' || to_char(item, 'FM00')
FROM generate_series(1, 5) item;
INSERT INTO parameter (name)
SELECT 'param_' || to_char(item, 'FM00')
FROM generate_series(1, 20) item;
INSERT INTO spot (name)
SELECT 'spot_' || to_char(item, 'FM00')
FROM generate_series(1, 10) item;
INSERT INTO measurement_data_index (fk_device_id, fk_parameter_id, fk_spot_id)
SELECT device.id, parameter.id, spot.id
FROM device, parameter, spot;
INSERT INTO measurement_data_value(id, t_stamp, value)
SELECT index.id,
item,
'd' || to_char(index.fk_device_id, 'FM00') ||
'_s' || to_char(index.fk_spot_id, 'FM00') ||
'_p' || to_char(index.fk_parameter_id, 'FM00')
FROM measurement_data_index index,
generate_series('2012-01-01 00:00:00', '2012-01-06 23:59:59', interval '1 min') item;
END;
$BODY$;
SELECT insert_data();
在某个阶段,我会将自己的约定更改为使用内联 PRIMARY KEY 和 REFERENCES 语句,而不是显式的 CONSTRAINTs;目前,我认为保持这种方式更容易比较两种解决方案。
不要忘记更新查询计划器的统计信息:
VACUUM ANALYZE device;
VACUUM ANALYZE measurement_data_index;
VACUUM ANALYZE measurement_data_value;
VACUUM ANALYZE parameter;
VACUUM ANALYZE spot;
运行一个应该产生与第一种方法相同的结果的查询:
EXPLAIN (ANALYZE ON, BUFFERS ON)
SELECT measurement_data_value.value
FROM measurement_data_index,
measurement_data_value,
parameter
WHERE measurement_data_index.fk_parameter_id = parameter.id
AND measurement_data_index.id = measurement_data_value.id
AND parameter.name = 'param_01';
结果:
Nested Loop (cost=0.00..34218.28 rows=431998 width=12) (actual time=0.026..696.349 rows=432000 loops=1)
Buffers: shared hit=435332
-> Nested Loop (cost=0.00..29.75 rows=50 width=4) (actual time=0.012..0.453 rows=50 loops=1)
Join Filter: (measurement_data_index.fk_parameter_id = parameter.id)
Buffers: shared hit=7
-> Seq Scan on parameter (cost=0.00..1.25 rows=1 width=4) (actual time=0.005..0.010 rows=1 loops=1)
Filter: ((name)::text = 'param_01'::text)
Buffers: shared hit=1
-> Seq Scan on measurement_data_index (cost=0.00..16.00 rows=1000 width=8) (actual time=0.003..0.187 rows=1000 loops=1)
Buffers: shared hit=6
-> Index Scan using measurement_data_value_pk on measurement_data_value (cost=0.00..575.77 rows=8640 width=16) (actual time=0.013..12.157 rows=8640 loops=50)
Index Cond: (id = measurement_data_index.id)
Buffers: shared hit=435325
Total runtime: 726.125 ms
这几乎是第一种方法所需的约 1.3 秒的一半;考虑到我正在加载 432K 行,这是我目前可以接受的结果。
注意:值表PK中的字段顺序为id, t_stamp; wildplassers 响应中的顺序是t_stamp, whw_id。我这样做是因为我觉得“常规”字段顺序是在表声明中列出字段的顺序(而“反向”则是另一种方式),但这只是我自己的约定,让我无法获得使困惑。无论哪种方式,正如Erwin Brandstetter 指出的那样,这个顺序对于性能提升绝对是关键;如果方法错误(并且缺少 wildplassers 解决方案中的反向索引),查询计划如下所示,性能会差 3 倍以上:
Hash Join (cost=22.14..186671.54 rows=431998 width=12) (actual time=0.460..2570.941 rows=432000 loops=1)
Hash Cond: (measurement_data_value.id = measurement_data_index.id)
Buffers: shared hit=63537
-> Seq Scan on measurement_data_value (cost=0.00..149929.58 rows=8639958 width=16) (actual time=0.004..1095.606 rows=8640000 loops=1)
Buffers: shared hit=63530
-> Hash (cost=21.51..21.51 rows=50 width=4) (actual time=0.446..0.446 rows=50 loops=1)
Buckets: 1024 Batches: 1 Memory Usage: 2kB
Buffers: shared hit=7
-> Hash Join (cost=1.26..21.51 rows=50 width=4) (actual time=0.015..0.359 rows=50 loops=1)
Hash Cond: (measurement_data_index.fk_parameter_id = parameter.id)
Buffers: shared hit=7
-> Seq Scan on measurement_data_index (cost=0.00..16.00 rows=1000 width=8) (actual time=0.002..0.135 rows=1000 loops=1)
Buffers: shared hit=6
-> Hash (cost=1.25..1.25 rows=1 width=4) (actual time=0.008..0.008 rows=1 loops=1)
Buckets: 1024 Batches: 1 Memory Usage: 1kB
Buffers: shared hit=1
-> Seq Scan on parameter (cost=0.00..1.25 rows=1 width=4) (actual time=0.004..0.007 rows=1 loops=1)
Filter: ((name)::text = 'param_01'::text)
Buffers: shared hit=1
Total runtime: 2605.277 ms
【问题讨论】:
-
三件事不是你的问题,但我不明白:(1)如果
parameter.id的原因是parameter.name不是唯一的,那你怎么会有@987654377 @? (2)measurement_data_index和measurement_data_value之间有什么关系(如果有的话)? (3) 隐式/逗号/非 ANSI 连接是什么? :-P -
EXPLAIN (ANALYZE ON, BUFFERS ON) SELECT measurement_data_value.value FROM measurement_data_value JOIN parameter ON measurement_data_value.fk_parameter_id = parameter.id WHERE parameter.name = 'param_01';是否给出相同的解释结果?对我来说,循环内参数的顺序扫描看起来有点奇怪。 -
(1) 有效点。当我将原始数据模型简化为此处显示的形式时,这是一个遗漏。我已经更新了问题并删除了唯一约束。 (2) 如问题所述,一个 measure_data_index 行与许多 measure_data_value 行具有相同的 ID (3) ANSI 一致性在项目中并不重要,根据 postgres 文档 (postgresql.org/docs/9.1/static/tutorial-join.html),“逗号”形式 I' m using 等效于显式连接并且“不常用”。对我来说,它更容易理解。
-
@Joachim:相同的结果,相同的性能(在测试变体中)。顺便说一句,作为另一个 SO 问题 (stackoverflow.com/a/11523894/217844) 的一部分,我已经开始对 JOIN 的“逗号”/隐式和“ANSI”/显式版本进行基准测试,到目前为止还没有发现任何显着差异
-
1) 我认为不需要单独的measurement_data_index 和measurement_data_value 表。对我来说,测量项目的 natural 键是 {spot,device,timestamp},直接导致 -> 值。因为你有一个有效的自然键,2)我也会省略序列号(任何其他表都没有引用)3)我会为复合自然 PK 制作measurement_item.device_id
integer NOT NULL references device(id)4),额外的索引(以不同的顺序)可能是必要的,具体取决于您的特定查询需求。
标签: sql performance postgresql database-design indexing