使用索引
索引是用于快速数据检索操作的结构。在数据库世界中,索引与表相关联并用于有效定位数据,而无需查询数据库表中的每一行。如果表没有索引,则需要全表扫描才能找到记录,这在磁盘 I/O 和 CPU 利用率方面非常昂贵。全表扫描是从磁盘顺序读取每条记录、检查搜索条件并构建结果集的过程。
目录在本文中,您将学习以下主题:
- 什么是索引
- 如何创建索引
- 索引有哪些不同类型
- 如何使用不同的索引方法
- 索引问题
什么是索引
索引以额外的数据副本为代价来改进数据库操作的性能。由于索引存储额外的数据副本以加快访问速度,因此索引是改进数据库性能的常规方法。简而言之,索引是对数据库中表的单行的快速访问路径。数据库索引类似于书籍索引,任何特定信息都可以通过查看索引页来定位,以避免对书籍进行全面搜索,这是一种穷举操作。类似地,创建数据库索引以最小化表遍历并最大化性能。
可以使用表的单列或多列创建索引。一旦创建了索引,就不需要进一步的干预;索引将在表上的每个 DML 操作上自动更新。创建索引后,规划器根据成本决定使用索引代替顺序扫描。
当 PostgreSQL 执行一个查询时,它必须选择一个执行策略。规划器是负责建立执行有效搜索的最佳策略的软件。
假设我们有一个名为item的表,其中包含item_id、 item_name、 item_price和item_data列,并且包含数百万行。让我们通过psql命令行实用程序连接到warehouse_db数据库,并按以下方式创建项目表:
db1=# CREATE TABLE item
(
item_id INTEGER NOT NULL,
item_name TEXT,
item_price NUMERIC,
item_data TEXT
);
可以使用以下语句查看结果:
db1=# SELECT item_name FROM item WHERE item_id = 100;
如果我们想通过前面的查询得到一个特定商品的详细信息,那么就需要扫描整个表来找到item_id为100的商品,这是一个严重的性能问题。这个问题可以使用索引来解决。
EXPLAIN QUERY命令可用于检查将用于查询的扫描。
这可以用下面的例子来解释:
db1=# EXPLAIN SELECT * FROM item;
QUERY PLAN
---------------------------------------------------------
Seq Scan on item (cost=0.00..16.30 rows=630 width=100)
(1 row)
如何创建索引
CREATE INDEX命令用于创建索引;在表上创建索引的基本语法如下:
CREATE INDEX index_name ON table_name (column_name);
下面是在item表的item_id列上创建索引的示例:
db1=# CREATE INDEX item_idx ON item (item_id);
CREATE INDEX
可以使用以下语句查看结果:
db1=# \di item_idx
List of relations
Schema | Name | Type | Owner | Table
--------+----------+-------+----------+-------
public | item_idx | index | postgres | item
(1 row)
索引名称是可选的;如果未指定索引名,PostgreSQL 将使用表名和列名生成索引名。在前面的示例中,我们指定了索引名称。在以下示例中,PostgreSQL 使用表名和列名生成索引名称,即item_item_id_idx:
db1=# CREATE INDEX ON item (item_id);
CREATE INDEX
可以使用以下语句查看结果:
db1=# \di item_item_id_idx
List of relations
Schema | Name | Type | Owner | Table
--------+------------------+-------+----------+-------
public | item_item_id_idx | index | postgres | item
(1 row)
在大表上创建索引可能需要很长时间;例如,在前面的示例中,查询必须扫描所有记录并生成索引数据。
索引需要额外的磁盘空间,因此在创建索引时要小心。此外,insert/delete/update 需要更多的处理来维护索引数据,索引的类型对性能有不同的影响。例如,B 树索引需要树重新平衡,这在计算成本方面非常繁重。
下面是创建索引的完整语法:
CREATE [ UNIQUE ] INDEX [ CONCURRENTLY ] [ [ IF NOT EXISTS ] name ] ON table_name [ USING method ]
( { column_name | ( expression ) } [ COLLATE collation ] [ opclass ] [ ASC | DESC ] [ NULLS { FIRST | LAST } ] [, ...] )
[ WITH ( storage_parameter [= value] [, ... ] ) ]
[ TABLESPACE tablespace_name ]
[ WHERE predicate ]
有关创建索引的完整语法的详细信息,请访问以下链接在线提供的官方 PostgreSQL 文档:
https://www.postgresql.org/docs/9.6/sql-createindex.html
如何删除索引
DROP INDEX命令用于删除现有索引。它的基本语法如下:
DROP INDEX index_name;
删除索引不会影响表的行,但要小心,因为它会影响数据库的性能。
索引类型
PostgreSQL 数据库支持单列索引、多列索引、部分索引、唯一索引、表达式索引、隐式索引和并发索引。
单列索引
当一个表主要代表一个单一类别的数据,或者查询仅围绕表中的一个类别时,使用单列索引。通常,在数据库设计中,表代表单一类别的数据,因此通常使用单列(类别)索引。它的语法如下:
CREATE INDEX index_name ON table_name (column);
看看下面的例子:
db1=# SELECT COUNT(*) FROM item WHERE item_id = 100;
count
-------
202
(1 row)
Time: 201.206 ms
在这个例子中,需要item_id为100的行。如果没有定义索引,则将扫描整个表以查找item_id为100 的行,这是一个昂贵的操作。如果仔细观察, WHERE子句中只使用了一个列,从而在单个列上创建了索引,在前面的查询中是item_id 。这优化了该查询。
现在,考虑以下示例:
db1=# CREATE INDEX item_index ON item (item_id);
可以使用以下语句查看结果:
db1=# \d item
Table "public.item"
Column | Type | Modifiers
------------+---------+-----------
item_id | integer | not null
item_name | text |
item_price | numeric |
item_data | text |
Indexes:
"item_idx" btree (item_id)
"item_index" btree (item_id)
"item_item_id_idx" btree (item_id)
现在,我们已经在表项的item_id列上创建了一个 B 树索引item_index ,所以现在我们再次尝试相同的SELECT查询,看看创建索引后需要多少时间。
在表上创建索引大大减少了SELECT查询时间,如以下输出所示:
db1=# SELECT COUNT(*) FROM item WHERE item_id = 100;
count
-------
202
(1 row)
Time: 1.140 ms
有索引和没有索引有明显的时间差异。没有索引的相同查询执行大约需要200 毫秒,而创建索引后的查询大约需要1 毫秒。
- 提示
\d table_name查询用于描述表。
\timing查询用于显示查询时间。
多列索引
在某些情况下,数据库中的表涉及多个类别的数据。在这种情况下,单列索引可能无法提供良好的性能。在这种情况下,就需要多列索引。 PostgreSQL 支持多列索引。它的语法如下:
CREATE INDEX index_name ON table_name (column1, column2);
多列索引,在一个查询中涉及多个列,有助于优化查询;让我们看一个例子。
在本例中,我们将获取item_id小于200且item_price也为100的记录总数;首先,我们在表上尝试不带索引的查询,并按以下方式记下查询时间:
db1=# SELECT COUNT(*) FROM item WHERE item_id < 200 AND item_price = 100;
count
-------
202
(1 row)
Time: 1675.783 ms
现在,我们将创建一个单列索引,按以下方式尝试相同的查询,并记下查询执行时间:
db1=# CREATE INDEX item_single_index ON item (item_id);
可以使用以下语句查看结果:
db1=# SELECT COUNT(*) FROM item WHERE item_id < 200 AND item_price = 100;
count
-------
202
(1 row)
Time: 1604.328 ms
最后,在表上创建一个多列索引,按以下方式尝试相同的查询,并记下查询的时间:
db1=# CREATE INDEX item_multi_index ON item (item_id, item_price);
可以使用以下语句查看结果:
db1=# SELECT COUNT(*) FROM item WHERE item_id < 200 AND item_price = 100;
count
-------
202
(1 row)
Time: 331.295 ms
查询时间如下:
无索引 | 单列索引 | 多列索引 |
---|---|---|
1675.783 ms | 1604.328 ms | 331.295 ms |
我们可以观察到前面查询的执行时间的差异。没有索引的查询耗时1675.783 毫秒,具有单列索引的查询运行速度稍快,耗时1604.328 毫秒,而具有多列索引的查询运行速度更快,仅耗时331.295 毫秒。因此,创建单列或多列索引取决于所用查询的性质,因此选择合适的索引可以真正提高性能。
- 提示
默认情况下,索引是按升序创建的;要按降序创建索引,请在列名后使用DESC 。
部分索引
部分索引是只适用于满足指定条件的行的索引。大多数时候,表的子集用于查询。在这种情况下,对整个表创建索引既浪费空间又耗时。在这种情况下,我们应该考虑在表的子集上创建索引。与在整个表上创建索引相比,使用部分索引的根本原因是通过使用最少的磁盘空间获得更好的性能。
要在表的子集上创建索引,我们使用部分索引。部分索引可以通过在创建索引时指定WHERE条件来创建,如下:
CREATE INDEX index_name ON table_name (column) WHERE (condition);
假设大部分查询使用item_id小于106的item表的子集,然后在整个item表上创建索引是浪费空间。最佳情况是仅在warehouse_tbl表的前 100 行上创建索引。在这种情况下,可以为小于 100 的行创建部分索引。这可以通过以下方式完成:
db1=# CREATE INDEX item_partial_index ON item (item_id) WHERE (item_id < 106);
可以使用以下语句查看结果:
CREATE INDEX
db1=# \d item
Table "public.item"
Column | Type | Modifiers
------------+---------+-----------
item_id | integer | not null
item_name | text |
item_price | numeric |
item_data | text |
Indexes:
"item_idx" btree (item_id)
"item_index" btree (item_id)
"item_item_id_idx" btree (item_id)
"item_partial_index" btree (item_id) WHERE item_id < 106
- 提示
B 树索引最多支持 32 列连接到一个索引中。
唯一索引
可以在任何列上创建唯一索引;它不仅创建索引,而且强制列的唯一性。这是数据库设计中最重要的索引,因为它确保数据完整性并提供性能增强。有多种创建唯一索引的方法:使用CREATE UNIQUE INDEX命令,通过在表上创建唯一约束,或通过创建主键。
这是由CREATE UNIQUE INDEX命令创建的唯一索引的示例:
db1=# CREATE UNIQUE INDEX item_unique_idx ON item (item_id);
CREATE INDEX
可以使用以下语句查看结果:
db1=# \d item_unique_idx
Index "public.item_unique_idx"
Column | Type | Definition
---------+---------+------------
item_id | integer | item_id
unique, btree, for table "public.item"
我们可以使用CREATE UNIQUE INDEX命令显式创建唯一索引,也可以通过在表上声明主键来隐式创建它。以下是通过在表上创建主键来隐式创建唯一索引的示例:
db1=# CREATE TABLE item
(
item_unique INTEGER PRIMARY KEY,
item_name TEXT,
item_price NUMERIC,
item_data TEXT
);
可以使用以下语句查看结果:
db1=# \d item
Table "public.item"
Column | Type | Modifiers
-------------+---------+-----------
item_unique | integer | not null
item_name | text |
item_price | numeric |
item_data | text |
Indexes:
"item_pkey" PRIMARY KEY, btree (item_unique)
以下是通过定义唯一约束隐式创建唯一索引的示例:
db1=# ALTER TABLE item ADD CONSTRAINT primary_key UNIQUE(item_unique);
前面的 SQL 语句更改了表并在item表的item_id列上添加了唯一约束,并且该语句还隐式创建了一个唯一索引(B 树)。
在这里,我们重新查看原始表:
db1=# \d item
Table "public.item"
Column | Type | Modifiers
-------------+---------+-----------
item_unique | integer | not null
item_name | text |
item_price | numeric |
item_data | text |
Indexes:
"item_pkey" PRIMARY KEY, btree (item_unique)
"primary_key" UNIQUE CONSTRAINT, btree (item_unique)
ALTER命令为item_id列添加了唯一约束,可以用作主键。
使用 CREATE 显式创建索引
我们已经讨论了如何在表上隐式创建唯一索引,但是有一种方法可以使用已经讨论过的CREATE INDEX命令显式创建唯一索引,如下所示:
db1=# CREATE TABLE item
(
item_unique INTEGER PRIMARY KEY,
item_name TEXT,
item_price NUMERIC,
item_data TEXT
);
我们使用以下语句创建唯一索引:
db1=# CREATE UNIQUE INDEX idx_unique_id ON item (item_id);
CREATE INDEX
可以使用以下语句查看结果:
db1=# \d item
Table "public.item"
Column | Type | Modifiers
------------+---------+-----------
item_id | integer | not null
item_name | text |
item_price | numeric |
item_data | text |
Indexes:
"item_pkey" PRIMARY KEY, btree (item_id)
"idx_unique_id" UNIQUE, btree (item_id)
在所有情况下,唯一索引可确保数据的完整性并在已存在的情况下抛出错误。这可以在以下示例中看到:
db1=# INSERT INTO item VALUES (1, 'boxing', 200, 'gloves');
INSERT 0 1
db1=# INSERT INTO item VALUES (1, 'hockey', 300, 'shoes');
ERROR: duplicate key value violates unique constraint "item_pkey"
DETAIL: Key (item_id)=(1) already exists.
- 提示
只有 B‑tree、GiST 和 GIN 索引支持唯一索引。
表达式索引
我们已经讨论了在表的列上创建索引,但在某些情况下,需要在表的一个或多个列上添加表达式。例如,如果我们要搜索不区分大小写的项目名称,那么正常的做法如下:
db1=# SELECT * FROM item WHERE UPPER(item_name) LIKE 'COFFEE';
前面的查询将扫描每一行或表并将item_name转换为大写并将其与COFFEE 进行比较;这真的很贵。以下是在item_name列上创建表达式索引的命令:
db1=# CREATE INDEX item_expression_index ON item(UPPER(item_name));
可以使用以下语句查看结果:
db1=# \d item
Table "public.item"
Column | Type | Modifiers
------------+---------+-----------
item_id | integer | not null
item_name | text |
item_price | numeric |
item_data | text |
Indexes:
"item_pkey" PRIMARY KEY, btree (item_id)
"idx_unique_id" UNIQUE, btree (item_id)
"item_expression_index" btree (upper(item_name))
仅当在定义中的查询中使用精确表达式时才使用表达式索引。在这个例子中,我们查询item表并没有使用表达式,所以规划器没有使用表达式索引。这可以看如下:
db1=# EXPLAIN SELECT item_name FROM item WHERE item_name = 'item-10';
QUERY PLAN
---------------------------------------------------------
Seq Scan on item (cost=0.00..22759.00 rows=1 width=11)
Filter: (item_name = 'item-10'::text)
(2 rows)
但是,在此示例中,我们使用了与索引定义中相同的表达式,因此规划器按以下方式选择该索引:
db1=# EXPLAIN SELECT item_name FROM item WHERE UPPER(item_name) = 'ITEM-10';
QUERY PLAN
-------------------------------------------------------------
Bitmap Heap Scan on item (cost=107.18..8714.04 rows=5000 width=11)
Recheck Cond: (upper(item_name) = 'ITEM-10'::text)
-> Bitmap Index Scan on item_expression_index
(cost=0.00..105.93 rows=5000 width=0)
Index Cond: (upper(item_name) = 'ITEM-10'::text)
(4 rows)
隐式索引
由数据库自动创建的索引称为隐式索引。主键或唯一约束隐式地在该列上创建索引。隐式索引已在本文前面的唯一索引部分讨论过。
并发索引
建立索引时会锁定表,使其无法在表中写入或插入任何内容。在创建过程中,可以毫无问题地读取表,但写入操作会阻塞,直到索引构建过程结束。我们已经讨论过,在表上创建索引是一项非常昂贵的操作,在相当大的表上,建立索引可能需要数小时。这可能会导致难以执行任何写入操作。为了解决这个问题,
PostgreSQL 有并发索引,当你需要为一个实时数据库中添加索引时,它很有用。
并发索引的语法如下:
CREATE INDEX CONCURRENTLY index_name ON table_name using btree(column);
- 提示
并发索引比普通索引慢,因为它分两部分完成索引构建。这可以借助以下示例进行解释:
使用CREATE INDEX创建普通索引idx_id所花费的时间:
db1=# CREATE INDEX idx_id ON item (item_id);
Time: 8265.473 ms
使用CREATE INDEX CONCURRENTLY创建并发索引idx_id所花费的时间:
db1=# CREATE INDEX CONCURRENTLY idx_id ON item (item_id);
Time: 51887.942 ms
索引类型
PostgreSQL 支持 B‑tree、hash、GiST 和 GIN 索引方法。可以通过USING方法选择索引方法或类型。不同类型的索引有不同的用途,例如,当查询涉及范围和相等运算符时有效使用B树索引,当查询中使用相等运算符时有效使用散列索引。
下面是一个如何使用索引类型的简单示例:
CREATE INDEX index_name ON table_name USING btree(column);
B树索引
当查询涉及相等运算符(=)和范围运算符(<、 <=、 >、 >=、 BETWEEN和IN)时,可以有效地使用 B 树索引。
哈希索引
当查询仅涉及简单的等效运算符时,将使用哈希索引。在这里,我们在item表上创建一个哈希索引。您可以在以下示例中看到,规划器在等效运算符的情况下选择哈希索引,而在范围运算符的情况下不使用哈希索引:
db1=# CREATE INDEX item_hash_index ON item USING HASH(item_id);
如前所述,散列索引最适合在WHERE子句中具有等效运算符的查询。这可以借助以下示例进行解释:
db1=# EXPLAIN SELECT COUNT(*) FROM item WHERE item_id = 100;
QUERY PLAN
------------------------------------------------------------------
Aggregate (cost=8.02..8.03 rows=1 width=0)
-> Index Scan using item_hash_index on item (cost=0.00..8.02 rows=1
width=0)
Index Cond: (item_id = 100)
(3 rows)
哈希索引方法不适用于范围操作符,因此规划器不会选择哈希索引进行范围查询:
db1=# EXPLAIN SELECT COUNT(*) FROM item WHERE item_id > 100;
QUERY PLAN
------------------------------------------------------------------
Aggregate (cost=25258.75..25258.76 rows=1 width=0)
-> Seq Scan on item (cost=0.00..22759.00 rows=999900 width=0)
Filter: (item_id > 100)
(3 rows)
- 提示
要获取表和索引的大小,我们可以使用以下命令:
SELECT pg_relation_size('table_name') AS table_size,pg_relation_size('index_name') index_size
FROM pg_tables WHERE table_name like 'table_name';
GiST 索引
通用搜索树(GiST)索引提供了使用索引访问方法创建自定义数据类型的可能性。它还提供了一组广泛的查询。
它可以用于等价和范围比较之外的操作。 GiST 索引是有损的,这意味着它可以创建不正确的匹配项。
GiST 索引的语法如下:
CREATE INDEX index_name ON table_name USING gist(column_name);
- 提示
使用 GiST 开发的模块和扩展有rtree_gist、 btree_gist、 intarray、 tsearch、 ltree和cube。其完整详情可在以下链接中找到:
http://www.postgresql.org/docs/9.6/static/gist-examples.html
GIN索引
可以在以下链接中找到的以下引用的帮助下引入 GIN 索引:
https://www.postgresql.org/docs/9.6/gin-intro.html
“GIN 代表广义倒排索引。 GIN 是为处理被索引的项是复合值的情况而设计的,索引要处理的查询需要搜索出现在复合项中的元素值。例如,项目可以是文档,查询可以是搜索包含特定单词的文档”
下面是创建 GIN 索引的语法:
db1=# CREATE INDEX index_name ON table_name USING gin(column_name);
- 提示
GIN 索引需要的空间是 GiST 的三倍,但速度是 GiST 的三倍。
让我们举一个例子,我们想要使用部分匹配从数百万个单词中搜索一个单词。 GIN 索引最适合这些类型的查询:
db1=# CREATE EXTENSION pg_trgm;
创建pg_trgm扩展后,我们使用以下语句创建单词表:
db1=# CREATE TABLE words(lineno INT, simple_words TEXT, special_words TEXT);
我们可以使用以下语句将数据插入到words表中:
db1=# INSERT INTO words VALUES (generate_series(1,2000000), md5(random()::TEXT), md5(random()::TEXT));
让我们尝试执行一个查询来搜索simple_words和special_words中具有a31的单词,并注意查询的执行时间如下:
db1=# SELECT count(*) FROM words WHERE simple_words LIKE '%a31%' AND special_words LIKE '%a31%';
count
-------
116
(1 row)
Time: 1081.796 ms
创建一个多列索引并尝试执行相同的查询并按以下方式记下查询的时间:
db1=# CREATE INDEX words_idx ON words (simple_words, special_words);
Time: 32822.055 ms
可以使用以下语句查看结果:
db1=# SELECT count(*) FROM words WHERE simple_words LIKE '%a31%' AND special_words LIKE '%a31%';
count
-------
116
(1 row)
Time: 1075.467 ms
现在,在表上创建一个 GIN 索引,再次执行相同的查询,并按以下方式记下时间:
db1=# CREATE INDEX words_idx ON words USING gin (simple_words gin_trgm_ops, special_words gin_trgm_ops);
CREATE INDEX
Time: 142836.712 ms
可以使用以下语句查看结果:
db1=# SELECT count(*) FROM words WHERE simple_words LIKE '%a31%' AND special_words LIKE '%a31%';
count
-------
116
(1 row)
Time: 7.105 ms
现在从下表中,我们可以清楚地看到使用GIN索引:
没有索引的时间 | 有多列索引的时间 | 有GIN索引的时间 |
---|---|---|
1081.796 ms | 1075.467 ms | 7.105 ms |
- 提示
有关 GIN 索引的更多详细信息,请访问http://www.sai.msu.su/~megera/wiki/Gin。
有关pg_trgm和gin_trgm_ops的更多详细信息,请访问http://www.postgresql.org/docs/9.6/static/pgtrgm.html。
索引膨胀
由于PostgreSQL的架构是基于MVCC的,所以表存在死行。对任何事务都不可见的行被视为死行。在连续表中,一些行被删除或更新。这些操作会导致表中出现死行。插入新数据时,可能会重复使用死行。由于死行很多,所以会出现膨胀。索引膨胀有多种原因,需要修复它才能获得更高的性能,因为它会损害数据库的性能。AUTO VACUUM是避免膨胀的最佳方法,但它是一个可配置的参数,可能会被禁用或错误配置。有多种方法可以修复索引膨胀;因此,我们将在以下部分讨论避免这种情况的方法及其功效。
要了解有关 MVCC 的更多信息,请查看http://www.postgresql.org/docs/current/static/mvcc-intro.html。
转储和恢复
在出现膨胀的情况下,最简单的预防方法是使用pg_dump备份表,删除表,并将数据重新加载到初始表中。这是一项昂贵的操作,有时似乎限制太多。
vacuum
使用VACUUM命令清空表是另一种可用于修复膨胀的解决方案。 VACUUM命令重新排列行以确保页面尽可能满,但数据库文件收缩仅在文件末尾有 100% 的空页时才会发生。这是VACUUM可用于减少膨胀的唯一情况。它的语法如下:
VACUUM table_name
以下示例显示了VACUUM在item表上的用法:
db1=# VACUUM item;
另一种使用VACUUM的方法如下:
db1=# VACUUM FULL item;
CLUSTER
正如我们之前所讨论的,重写和重新排序行可以解决可以使用转储/恢复间接实现的问题,但这是一项昂贵的操作。执行此操作的另一种方法是CLUSTER命令,它用于根据索引对行进行物理重新排序。 CLUSTER命令用于创建表的完整初始副本,并删除数据的旧副本。 CLUSTER命令需要足够的空间(实际上是磁盘空间的两倍)来保存数据的初始组织副本。它的语法如下:
CLUSTER table_name USING index_name
用于获取索引和表的大小/行的一些查询如下:
db1=# CREATE TABLE table_name AS SELECT * FROM generate_series(1,9999999) AS COLUMN_KEY;
SELECT 9999999
db1=# CREATE INDEX index_name ON table_name (column_key);
CREATE INDEX
db1=# SELECT table_len/(1024*1024) table_size, tuple_count total_rows FROM pgstattuple('table_name');
table_size | total_rows
------------+------------
345 | 9999999
(1 row)
db1=# SELECT table_len/(1024*1024) table_size, tuple_count total_rows FROM pgstattuple('index_name');
table_size | total_rows
------------+------------
214 | 9999999
(1 row)
重建索引
如果索引由于膨胀或数据变得随机分散而变得低效,则需要重建索引以从索引中获得最大性能。它的语法如下:
db1=# REINDEX TABLE item;
思考要点
使用索引时,需要注意以下几点:
- 当表中有大量行时,表列上的索引是有意义的。
- 检索数据时,您需要确保索引的良好候选者是外键和检索数据时可以使用min()和max()的键。这意味着列选择性对于有效索引非常重要。
- 不要忘记删除未使用的索引以获得更好的性能。此外,每月对所有索引执行一次REINDEX以清理死元组。
- 如果您有大量数据,请使用表分区和索引。
- 当您为具有空值的列索引时,请考虑使用WHERE column_name IS NOT NULL 的条件索引。