此页面包含来自 TimescaleDB 社区 关于如何解决常见问题的建议。使用这些代码示例作为指导来处理您自己的数据。

要遵循此页面中的示例,您需要一个

本节包含关于超表的实践。

想要从现有超表中删除重复项吗?一种方法是运行 PARTITION BY 查询以获取 ROW_NUMBER(),然后获取 row_number>1 的行的 ctid。然后删除这些行。但是,您需要检查 tableoidctid。这是因为 ctid 不是唯一的,并且可能在不同的块中重复。以下代码示例花了 17 个小时处理一个包含 4000 万行的表

CREATE OR REPLACE FUNCTION deduplicate_chunks(ht_name TEXT, partition_columns TEXT, bot_id INT DEFAULT NULL)
RETURNS TABLE
(
chunk_schema name,
chunk_name name,
deleted_count INT
)
AS
$$
DECLARE
chunk RECORD;
where_clause TEXT := '';
deleted_count INT;
BEGIN
IF bot_id IS NOT NULL THEN
where_clause := FORMAT('WHERE bot_id = %s', bot_id);
END IF;
FOR chunk IN
SELECT c.chunk_schema, c.chunk_name
FROM timescaledb_information.chunks c
WHERE c.hypertable_name = ht_name
LOOP
EXECUTE FORMAT('
WITH cte AS (
SELECT ctid,
ROW_NUMBER() OVER (PARTITION BY %s ORDER BY %s ASC) AS row_num,
*
FROM %I.%I
%s
)
DELETE FROM %I.%I
WHERE ctid IN (
SELECT ctid
FROM cte
WHERE row_num > 1
)
RETURNING 1;
', partition_columns, partition_columns, chunk.chunk_schema, chunk.chunk_name, where_clause, chunk.chunk_schema,
chunk.chunk_name)
INTO deleted_count;
RETURN QUERY SELECT chunk.chunk_schema, chunk.chunk_name, COALESCE(deleted_count, 0);
END LOOP;
END
$$ LANGUAGE plpgsql;
SELECT *
FROM deduplicate_chunks('nudge_events', 'bot_id, session_id, nudge_id, time', 2540);

感谢 Mathias OseChristopher Piggott 提供的这个实践。

假设有一个查询将超表连接到另一个表,它们共享一个键

SELECT timestamp,
FROM hypertable as h
JOIN related_table as rt
ON rt.id = h.related_table_id
WHERE h.timestamp BETWEEN '2024-10-10 00:00:00' AND '2024-10-17 00:00:00'

如果您在此查询上运行 EXPLAIN,您会看到查询计划器在这两个表之间执行 NestedJoin,这意味着多次查询超表。即使超表已建立索引,但如果它也很大,则查询将很慢。如何强制只查找一次?使用物化公共表表达式 (CTE)。

如果您使用 CTE 将查询拆分为两个部分,则可以物化超表查找并强制 PostgreSQL 仅执行一次。

WITH cached_query AS materialized (
SELECT *
FROM hypertable
WHERE BETWEEN '2024-10-10 00:00:00' AND '2024-10-17 00:00:00'
)
SELECT *
FROM cached_query as c
JOIN related_table as rt
ON rt.id = h.related_table_id

现在,如果您再次运行 EXPLAIN,您会看到此查询仅执行一次查找。根据您的超表的大小,这可能会使一个耗时数小时的查询只需几秒钟即可完成。

感谢 Rowan Molony 提供的这个实践。

本节包含关于物联网问题的实践

窄表和中等宽度表是存储物联网数据的好方法。在 设计数据库模式:宽表与窄表 Postgres 表 中概述了很多原因。

窄表的关键优势之一是当您添加新传感器时,模式不必更改。另一个巨大的优势是每个传感器可以以不同的速率和时间进行采样。这有助于支持诸如滞后之类的功能,即除非值发生一定量的变化,否则新值很少写入。

使用窄表数据结构会带来一些挑战。在物联网世界中,一个令人担忧的问题是,许多数据分析方法(包括机器学习以及更传统的数据分析)都要求您的数据被重新采样并同步到共同的时间基准。幸运的是,TimescaleDB 为您提供了 hyperfunctions 和其他工具来帮助您处理这些数据。

窄表格式的一个例子是

tssensor_idvalue
2024-10-31 11:17:30.000100723.45

通常,您会将其与传感器表结合使用

sensor_idsensor_nameunits
1007temperaturedegreesC
1012heat_modeon/off
1013cooling_modeon/off
1041occupancy房间内人数

中等表保留了通用结构,但添加了各种类型的列,以便您可以使用同一个表来存储 float、int、bool 甚至 JSON (jsonb) 数据

tssensor_iddibtj
2024-10-31 11:17:30.000100723.45nullnullnullnull
2024-10-31 11:17:47.0001012nullnullTRUEnullnull
2024-10-31 11:18:01.0001041null4nullnullnull

要删除所有空条目,请使用可选约束,例如

CONSTRAINT at_least_one_not_null
CHECK ((d IS NOT NULL) OR (i IS NOT NULL) OR (b IS NOT NULL) OR (j IS NOT NULL) OR (t IS NOT NULL))

有几种方法可以获取每个传感器的最新值。以下示例使用 窄表格式示例 中定义的结构作为参考

SELECT DISTINCT ON

如果您有一个传感器列表,则获取每个传感器的最新值的简单方法是使用 SELECT DISTINCT ON

WITH latest_data AS (
SELECT DISTINCT ON (sensor_id) ts, sensor_id, d
FROM iot_data
WHERE d is not null
AND ts > CURRENT_TIMESTAMP - INTERVAL '1 week' -- important
ORDER BY sensor_id, ts DESC
)
SELECT
sensor_id, sensors.name, ts, d
FROM latest_data
LEFT OUTER JOIN sensors ON latest_data.sensor_id = sensors.id
WHERE latest_data.d is not null
ORDER BY sensor_id, ts; -- Optional, for displaying results ordered by sensor_id

上面使用的公共表表达式 (CTE) 不是绝对必要的。然而,这是一种优雅的方式来连接到传感器列表以在输出中获取传感器名称。如果您不关心这一点,可以将其省略

SELECT DISTINCT ON (sensor_id) ts, sensor_id, d
FROM iot_data
WHERE d is not null
AND ts > CURRENT_TIMESTAMP - INTERVAL '1 week' -- important
ORDER BY sensor_id, ts DESC

在向下选择此数据时,务必小心。在前面的示例中,查询将回溯扫描的时间受到限制。但是,如果任何传感器长时间未报告,或者在最坏的情况下从未报告,则此查询将演变为全表扫描。
在具有 1000 多个传感器和 4100 万行的数据库中,不受约束的查询需要一个多小时。

SELECT DISTINCT ON 的替代方法是使用 JOIN LATERAL。通过从传感器表中选择您的整个传感器列表,而不是使用 SELECT DISTINCT 拉出 ID,JOIN LATERAL 可以提供一些性能改进

SELECT sensor_list.id, latest_data.ts, latest_data.d
FROM sensors sensor_list
-- Add a WHERE clause here to downselect the sensor list, if you wish
LEFT JOIN LATERAL (
SELECT ts, d
FROM iot_data raw_data
WHERE sensor_id = sensor_list.id
ORDER BY ts DESC
LIMIT 1
) latest_data ON true
WHERE latest_data.d is not null -- only pulling out float values ("d" column) in this example
AND latest_data.ts > CURRENT_TIMESTAMP - interval '1 week' -- important
ORDER BY sensor_list.id, latest_data.ts;

限制时间范围非常重要,尤其是在您有大量数据的情况下。最佳实践是将此类查询用于仪表板和快速状态检查。要查询更大的时间范围,请将前面的示例封装到不经常刷新的物化查询中,也许每天一次。

感谢 Christopher Piggott 提供的这个实践。

在此页面上发现问题?报告问题 或 在 GitHub 上编辑此页面