索引大型静态数据集

gak*_*era 6 postgresql index-tuning postgresql-9.3

我目前的情况是这样的:我在一个平面csv文件中有 3.28 亿行数据。不是最优的。我希望能够查询这些数据(我会更详细地解释)。Grep越来越累。这些数据是静态的,不会改变。

我是 PostgreSQL 的新手,正在我的 2010 MacBook Pro、2.4 GHz Intel Core 2 Duo、4 GB 1067 MHz DDR3 上处理这些数据。存储大小不是真正的问题,1TB 磁盘上的数据大约为 65GB,但读/写速度不是很好(不幸的是它不是 SSD)。我记得几年前我在 CS 研究中,索引可以帮助我提高查询速度,我正在阅读如何最好地做到这一点。

我为什么要使用 PostgreSQL?同辈压力。不是真的,但让我们继续吧。我在 OSX 10.9.2 上运行 PostgreSQL 9.3.4。

现在,我正在尝试做的事情。数据包括日期和时间信息,跨越大约 10 个月。例如,我希望能够:

  1. 查找并汇总特定用户和/或多个用户的数据集中所有星期一的所有活动
  2. 看看夏季几周与冬季几周之间的差异
  3. 几个月之间;和
  4. 一天中的小时数等。

我感兴趣的主要是与时间戳相关的摘要。

除了timestamp、auserID和 a之外,每个数据记录还包括servID。在另一个文件(存储在另一个表中)中,每个文件servID都作为 X 和 Y 值链接到一个位置。我有兴趣在地图上显示这些位置,因此我将使用 将这些位置导出到绘图matlab,为许多用户汇总或一次关注特定用户。

所有值都是重复的,即大约有 700.000 uniqueuserIDs和 1500 unique servIDs。时间戳也不是唯一的,分辨率只有秒,所以有重复的时间戳。

到目前为止,我已经创建了两个表,一个包含一小部分测试数据和 XY 位置表:

create table test (userID varchar, junk1 varchar, 
     date varchar, time varchar, junk2 varchar, 
     junk3 varchar, servID varchar, junk4 varchar, junk5 varchar);
copy test from '/Users/path/someuser.csv' delimiter ';' CSV;
create table locations (col1 int, servID_DEC int, 
     Col2 varchar, servID_HEX varchar, Locname varchar, 
     Locname2 varchar, SERV_LOCY float8, SERV_LOCX float8, junk1 float8, junk2 float8);
copy locations from '/Users/path/locations.csv' delimiter ',' header CSV;             
Run Code Online (Sandbox Code Playgroud)

我不得不一次修剪一列数据,这很烦人,但我为每个值重复了这一点:

update test set userID=trim(userID);
Run Code Online (Sandbox Code Playgroud)

然后我为我必须构建的实际时间戳添加了一列:

alter table test add column timestamp timestamptz;
update test set timestamp=to_timestamp(date || '-' || time, 'DD.MM.YYYY-HH24:MI:SS');
Run Code Online (Sandbox Code Playgroud)

现在我的问题(终于!抱歉):如何在 Postgres 中为数据创建索引,以便与此类似的查询速度很快:

select userID, date, time, servID, timestamp, servID_HEX, SERV_LOCY, SERV_LOCX 
     from test, locations 
     where servID=servID_HEX 
     and userID='<someusers>' 
     and extract(dow from timestamp)=2;
Run Code Online (Sandbox Code Playgroud)

我还希望能够快速总结servIDs一个userID在指定时间范围内使用的频率,即他使用 servID=1 1000 次,servID=2 600 次,依此类推(类似于 matlab 中的 tabulate 命令)

(出于某些原因,我重命名了一些变量和路径名,希望我没有搞砸)。

Cra*_*ger 9

查询清理

首先,让我们通过使用表别名、限定字段名称和使用 ANSI 连接来重写您的查询以使其可读:

select t.userID, t.date, t.time, t.servID, t.timestamp, 
       l.servID_HEX, l.SERV_LOCY, l.SERV_LOCX 
from test t
     inner join locations l on (t.servID=l.servID_HEX)
where t.userID='<someusers>' 
  and extract(dow from t.timestamp)=2;
Run Code Online (Sandbox Code Playgroud)

查询需要什么

要开始分解这个问题,您需要查看使用的字段及其使用位置:

表格: locations, test

字段输出: test.userID, test.date, test.time, test.servID, test.timestamp, locations.servID_HEX, locations.SERV_LOCY, locations.SERV_LOCX

过滤谓词中使用的术语: test.servID, locations.servID_HEX, test.userID, extract(dow from test.timestamp)=2

索引

现在,最重要的是确保您拥有针对谓词中使用的高选择性列的索引。

“高选择性”只是意味着没有大量相同的值 - 如果 50% 的时间是 'a' 并且 50% 的时间是 'b',那么索引列是没有意义的,因为你只是不从指数中获益良多。

假设所有这些列都有许多分布良好的不同值,因此它们具有高度选择性并且没有任何非常常见的值,您将需要创建一个普通的 b 树索引。这足够了,locations因为在谓词中只使用了一个值:

CREATE INDEX locations_servid_idx ON locations(servID_HEX);
Run Code Online (Sandbox Code Playgroud)

因为test它更复杂。

您可以为每一列单独创建索引,但创建一个复合索引更有效,该索引包含您在此查询的给定表的谓词中使用的所有列。所以你想要servId,userIDtimestamp。但是,您不timestamp直接使用- 您使用它来获取星期几,并且timestamp不能使用索引来查找extract(dow from timestamp).

这就是表达式索引的用武之地。

索引不仅仅涵盖列。它们还可以涵盖任意表达式。在这种情况下,我们将创建一个包含extract(dow from timestamp).

所以索引test将如下所示:

CREATE INDEX test_custom_idx ON test (
    servID,
    extract(dow from timestamp),
    userID
);
Run Code Online (Sandbox Code Playgroud)

由于您将执行不同类型的基于时间戳的查询,因此我会考虑为不同的时间戳表达式创建多个索引。

放置列的顺序取决于每列的选择性。尽管我确定星期几会更有选择性,但我还是将服务器 ID 放在首位,因为它用于连接条件,使我们可以完全忽略其他表的行。

由于您没有提供示例表或示例数据,我无法真正测试它,而且我也不热衷于模拟一些。可能需要进行调整。

部分索引

部分索引也可以是一个巨大的胜利,其中索引仅包含数据子集的数据,例如:

CREATE INDEX test_custom_idx ON test (
    servID, userID
) WHERE (extract(dow from timestamp) = 2);
Run Code Online (Sandbox Code Playgroud)

仅包含当天的数据,所以这是一个很大更快的搜索只是那天涉及的查询,但不能在所有的查询中使用可能涉及不同的日子,或者这一天是在运行时从另一部分的输出来确定的查询。

仅索引扫描和覆盖索引

您可以考虑的另一件事是,仍然必须从磁盘读取所有未使用的列以获取行。在 PostgreSQL 9.2 和更新版本中,如果您要获取的所有列都在 index 中,则有一种方法可以避免使用仅索引扫描。

因此,您可以创建一个覆盖索引,您可以在其中包含实际搜索中并不真正需要的列,但它们可以在不从主表中读取的情况下输出。为此,首先要做的是SELECT删除列表中无用的值,因此我们将从您的查询中删除多余的t.datet.time。然后创建两个新索引:

CREATE INDEX locations_covering_idx 
ON locations(
    servID_HEX,
    SERV_LOCY,
    SERV_LOCX
);

CREATE INDEX test_covering_idx ON test (
    servID,
    extract(dow from timestamp),
    userID,
    timestamp
);
Run Code Online (Sandbox Code Playgroud)

减少重复

由于您的数据集是静态的,因此拥有大量索引并不是什么大问题。

每个索引都有插入/更新/删除的成本。因此,对于非静态数据,您希望通过删除那些不能提供足够改进而值得付出代价的索引来最小化它。

一种方法是拆分复合索引。例如,在您的情况下,我可能会从位置的主索引中取出时间戳内容,并将其放入一堆新索引中。这样,所有不同的时间戳表达式索引都可以更新,而无需为 userid 和 serverid 重写大量不相关的数据。PostgreSQL 仍然可以经常一起使用索引,将它们与位图索引扫描结合起来。

与您当前的数据集无关,但值得以后学习。

数据清理

这是一个静态数据集。因此,如果您不需要列,请将其删除。或者更好的是,创建一个只包含您需要的数据子集的新表,pre-joined,使用以下内容:

CREATE TABLE reporting AS
select t.userID, t.servID, t.timestamp, l.SERV_LOCY, l.SERV_LOCX 
from test t
     inner join locations l on (t.servID=l.servID_HEX)
ORDER BY t.userID, t.servID, t.timestamp;
Run Code Online (Sandbox Code Playgroud)

这将创建一个新表,该表将数据预先连接并排序,以实现最有用的访问分组。

然后,您可以编写更简单的查询就可以了,快,对新表,你的指标将是一个简单得多了。