在具有许多共享值的非常大的表上创建索引

Gra*_*D71 4 postgresql index

我希望在具有许多非唯一值的字段上的大表(约 5000 万行)上创建索引。

表架构如下所示:

 Column |         Type          | Modifiers | Storage  | Stats target | Description 
--------+-----------------------+-----------+----------+--------------+-------------
 gid    | character varying(20) |           | extended |              | 
 word   | character varying(30) |           | extended |              | 
 stat   | double precision      |           | plain    |              | 
Has OIDs: no
Run Code Online (Sandbox Code Playgroud)

我想在 'word' 列上创建一个索引。有一个相当规律的模式,每个单词出现大约 1000 次。我需要做快速SELECT * FROM mytable WHERE word='something';查询。在这些表上创建常规 B 树索引需要大量时间,但确实可以显着提高性能。

由于几个原因,我现在对我的解决方案感到不舒服

(1) B-Tree 索引的选择不是特别积极。是否有替代索引方案在具有高度重复值的字段上表现更好?

(2) 我在一个生产环境中,这些表相当频繁地出现和消失。因为并非所有表都会被大量查询,所以我选择只在触发某些(数据库外)应用程序时在表上构建索引,这样我知道将在表+字段上执行 10k+ 次查询。然而,在创建索引时等待 20 分钟并不理想。情况很微妙;通过创建索引获得的优化与创建索引所需的初始 time-sink 竞争。是否有“更便宜”的索引可以创建?也许整体性能比 B-Tree 稍差,但初始创建成本更低?

Erw*_*ter 6

首先gid应该是数字类型integer应该足够好或者bigint密钥空间不应该足够大。占用空间更小,处理速度比字符数据更快,索引更快更小。

更重要的是,为了提高性能,我建议使用数据库规范化

引用:

有一个相当规律的模式,每个单词出现大约 1000 次。

为唯一词创建一个单独的表:

CREATE TABLE word (
   word_id serial
 , word    text
);
Run Code Online (Sandbox Code Playgroud)

word在您的big_tbl: 中使用独特的实例填充它:

INSERT INTO word (word)
SELECT DISTINCT word
FROM   big_tbl
ORDER  BY word;
Run Code Online (Sandbox Code Playgroud)

ORDER BY是可选的,手头的查询不需要。但它加快了索引的创建,而且总体上可能更便宜。

相比之下,该表应该很小:大表中的 50M 行只有 ~ 50k 行。填表
添加索引:

ALTER TABLE word
    ADD CONSTRAINT word_word_uni UNIQUE (word) -- essential
  , ADD CONSTRAINT word_word_id_pkey PRIMARY KEY (word_id);  -- expendable?
Run Code Online (Sandbox Code Playgroud)

如果这些是只读表,则无需 pk 即可。它与手头的操作无关。

用一张小得多的新桌子替换你的大桌子。您可能必须锁定大表以避免并发写入。并发读取不是问题。

CREATE TABLE big_tbl_new AS
SELECT b.gid      -- or the suggested smaller, faster numeric replacement
     , w.word_id, b.stat
FROM   big_tbl b
JOIN   word w USING (word)
ORDER  BY word;   -- sorting by word helps query at hand
Run Code Online (Sandbox Code Playgroud)

ORDER BY集群数据(一次)使手头的查询更快,因为必须读取的块要少得多(除非您的数据大部分已经集群)。排序携带成本,再次权衡成本和收益。

DROP big_tbl;     -- make sure your new table has all data!
ALTER big_tbl_new RENAME TO big_tbl;
Run Code Online (Sandbox Code Playgroud)

重新创建索引:

ALTER TABLE big_tbl ADD CONSTRAINT big_tbl_gid_pkey PRIMARY KEY (gid);  -- expendable?
CREATE INDEX big_tbl_word_id_idx ON big_tbl (word_id);  -- essential
Run Code Online (Sandbox Code Playgroud)

您的查询现在看起来像这样,应该更快:

SELECT b.*
FROM   word w
JOIN   big_tbl b USING (word_id)
WHERE  w.word = 'something';
Run Code Online (Sandbox Code Playgroud)

重组旨在成为重新组织数据的一次性操作。保留新形式并考虑永久保留索引。

所有这些(包括新索引)应该占用您以前在磁盘上的一半左右,同时将创建时间减少一半(至少)。索引创建应该快得多,查询也是如此。如果 RAM 是一个限制因素,那么这些修改将付出双倍的代价。

如果您还必须写入表格,它会变得更加昂贵(但您没有提及任何相关内容)。你需要调整你的逻辑DELETE/ UPDATE/ INSERT
示例INSERT:获取word_id现有的词或在插入新行word返回新word_id。详细信息:
如何插入包含外键的行?

  • @GrantD71:如果`gid` 只是一个*全局ID*,顾名思义,它不携带其他信息(您没有提供任何相反的信息)。它的唯一目的:成为*独特*。`integer` 覆盖 2G 正值,轻松覆盖 50M 行。如果这还不够,`bigint` 是。可能还有其他限制,但您的问题没有提到任何限制。`word_id`:如果你的数字成立,`word` 表小了 ~ 2000 倍,`word` 上的索引小了 ~ 1000。`big_tbl_word_id_idx` 是原始索引的 ~ 一半大小,整数更快。我添加了更多想法。 (2认同)