索引是加强数据库性能的经常使用方法。索引使得数据库在查找和检索数据库的特定行的时候比没有索引快的多。但索引也增长了整个数据库系统的开销,因此应该合理使用。html
假设咱们有一个相似这样的表:程序员
CREATE TABLE test1 ( id integer, content varchar );
应用程序发出许多相似如下的这种查询:web
SELECT content FROM test1 WHERE id = constant;
没有提早的准备,系统将不得不逐行扫描整个test1表,以查找全部匹配的条目。若是test1中有不少行,而且这样的查询仅仅返回几行(多是零或一行),这显然是一种低效的方法。可是若是系统已被指示在id列上维护索引,则可使用更有效的方法来定位匹配的行。例如,它可能只须要在搜索树中深刻几层。正则表达式
大多数非小说类书中都使用相似的方法:读者常常查阅的术语和概念在本书末尾以字母索引的形式收集。感兴趣的读者能够相对快速地扫描索引并翻转到适当的页面,而没必要阅读整本书以找到感兴趣的材料。正如做者的任务是预测读者可能会查找的项目,数据库程序员的任务是预见哪些索引将是有用的。算法
可使用下边的命令能够在id列上建立一个索引:sql
CREATE INDEX test1_id_index ON test1 (id);
名称test1_id_index能够自由选择,但您应该选择一些可让您记住索引的内容的名称。数据库
要删除索引,请使用DROP INDEX命令。索引能够随时添加到表中并从表中删除。数组
一旦建立了索引,就不须要进一步的干预:当表被修改时,系统将更新索引,当planner认为使用索引比顺序的表扫描更有效的时候,它会使用索引。可是,您可能须要按期运行ANALYZE命令来更新统计信息,以容许查询计划者作出有根据的决策。有关如何肯定索引是否被使用以及计划者什么时候以及为何选择不使用索引的信息,请参阅第14章。缓存
具备搜索条件的UPDATE和DELETE命令的查询条件也可使用索引来优化。索引也能够用于链接搜索。所以,在做为链接条件一部分的列上定义的索引也能够显著加快使用链接的查询。服务器
在大型表上建立索引可能须要很长时间。默认状况下,PostgreSQL容许读表(SELECT语句)与索引建立并行发生,但写入(INSERT,UPDATE,DELETE)将被阻止,直到索引生成完成。在生产环境中,这一般是不能接受的。能够设置容许写入与索引建立并行发生,但有几个须要注意的注意事项 - 有关更多信息,请参阅并发构建索引。
建立索引后,系统必须保持索引与数据表的同步。这增长了数据操做操做的开销。所以,查询中不多或从未使用的索引应该被删除。
PostgreSQL提供了几种索引类型:B-tree,Hash,GiST,SP-GiST,GIN和BRIN。每一个索引类型使用不一样的算法,适合不一样种类的查询。默认状况下,CREATE INDEX命令建立B-tree索引,这符合最多见的状况。
B-tree能够处理对能够排序成某些顺序的数据的等式和范围查询。特别地,当索引列参与使用如下运算符之一的比较时, PostgreSQL查询计划器将考虑使用B-tree索引:
< <= = >= >
也可使用B-tree索引搜索来实现与这些运算符的组合相同的构造,如BETWEEN和IN。此外,索引列上的IS NULL或IS NOT NULL条件能够与B-tree索引一块儿使用。
对于涉及模式匹配运算符LIKE的查询,优化器还可使用B-tree索引,若是模式是常量,而且锚定到字符串的开头,例如col LIKE 'foo%'或 col〜'^ foo',但不能是col LIKE'%bar'。可是,若是您的数据库不使用C语言环境,则须要使用特殊的运算符类建立索引,以支持对模式匹配查询的索引;见下文第11.9节。也能够对 ILIKE和〜*使用B-tree索引,但只有当模式以非字母字符(即不受大小写转换影响的字符)开始时才能够。
B-tree索引也可用于按排序顺序检索数据。这并不老是比简单的扫描和排序更快,但它一般是有益的。
Hash索引只能处理简单的等式比较。当使用=运算符进行比较时,查询计划器将考虑使用Hash索引。如下命令用于建立Hash索引:
CREATE INDEX name ON table USING HASH (column);
Hash索引操做目前不记录WAL-log,因此若是有没有写入的更改,Hash索引可能须要在数据库崩溃后用REINDEX重建。此外,在初始基本备份以后,不会经过流式或基于文件的复制来复制Hash索引的更改,所以它们对随后使用它们的查询给出错误的答案。因为这些缘由,目前不鼓励使用Hash索引。
GiST索引不是一种单一的索引,而是能够实现许多不一样索引策略的基础设施。所以,可使用GiST索引的特定运算符根据索引策略(运算符类)而变化。例如,PostgreSQL的标准发布版包括几个二维几何数据类型的GiST运算符类,它们支持使用这些运算符的索引查询:
<< &< &> >> <<| &<| |&> |>> @> <@ ~= &&
(有关这些操做符的含义,请参见第9.11节。)标准发布版中包含的GiST操做员类别见表61-1。许多其余GiST操做员类别能够在contrib集合中或单独的项目中使用。有关更多信息,请参阅第61章。
GiST索引还可以优化“nearest-neighbor”搜索,例如:
SELECT * FROM places ORDER BY location <-> point '(101,456)' LIMIT 10;
它找到最接近给定目标点的十个位置。这样作的能力又取决于所使用的特定操做符类。在表61-1中,能够以这种方式使用的操做员列在“Ordering Operators”一栏中。
SP-GiST索引(相似GiST索引)提供支持各类搜索的基础架构。 SP-GiST容许实现各类不一样的不平衡的基于磁盘的数据结构,例如四叉树,k-d树和基数树(尝试)。例如,PostgreSQL的标准发布版包括用于二维点的SP-GiST运算符类,它们支持使用这些运算符的索引查询:
<< >> ~= <@ <^ >^
(有关这些操做符的含义,请参见9.11节。)标准配置中包含的SP-GiST操做员类别见表62-1。有关更多信息,请参见第62章。
GIN索引是适用于包含多个组件值的数据值(如数组)的“反向索引”。反向索引包含每一个组件值的单独条目,而且能够有效地处理测试特定组件值的存在的查询。
像GiST和SP-GiST同样,GIN能够支持许多不一样的用户定义的索引策略,而且可使用GIN索引的特定运算符根据索引策略而变化。例如,PostgreSQL的标准发布版包括一维数组的GIN运算符类,它们支持使用这些运算符的索引查询:
<@ @> = &&
(有关这些操做符的含义,请参见第9.18节。)标准分配中包含的GIN运算符类别见表63-1。许多其余GIN操做员类能够在contrib集合中或单独的项目中使用。更多信息,请参见第63章。
BRIN索引(Block Range INdexes的缩写)存储关于存储在表的连续物理块范围内的值的摘要。像GiST,SP-GiST和GIN同样,BRIN能够支持许多不一样的索引策略,而且可使用BRIN索引的特定操做符根据索引策略而变化。对于具备线性排序顺序的数据类型,索引数据对应于每一个块范围的列中值的最小值和最大值。这支持使用这些运算符的索引查询:
< <= = >=
表64-1列出了标准配置中包含的BRIN运算符类。有关更多信息,请参见第64章。
能够在表的多个列上定义索引。例如,若是您有一张相似的数据表:
CREATE TABLE test2 ( major int, minor int, name varchar );
(好比,你的/ dev目录保存在数据库中...),你常常发出以下的查询:
SELECT name FROM test2 WHERE major = constant AND minor = constant;
那么在major和minor列上定义一个索引多是合适的:
CREATE INDEX test2_mm_idx ON test2 (major, minor);
目前,只有B-tree,GiST,GIN和BRIN索引类型支持多列索引。最多能够指定32列。 (构建PostgreSQL时能够更改此限制;请参阅pg_config_manual.h文件。)
多列B树索引能够用于涉及索引列的任何子集的查询条件,可是当前导(最左侧)列存在约束时,索引效率最高。确切的规则是,前导列上的等式约束以及不具备相等约束的第一列上的任何不等式约束将用于限制扫描的索引部分。在索引中检查这些列右侧的列的约束,所以它们保存对表的访问,但它们不会减小必须扫描的索引部分。例如,给定(a,b,c)上的索引和查询条件WHERE a = 5 AND b> = 42 AND c <77,必须从a = 5和b = 42经过最后一个条目,a = 5,c> = 77的索引条目将被跳过,但仍需扫描。这个索引原则上能够用于对b和/或c有约束的查询,而对b没有约束,可是整个索引必须被扫描,因此在大多数状况下,计划员会喜欢使用索引进行顺序表扫描。
多列GiST索引可用于涉及索引列的任何子集的查询条件。附加列上的条件限制索引返回的条目,但第一列中的条件是肯定须要扫描多少索引的最重要的条件。若是GiST索引的第一列只有几个不一样的值,即便附加列中有不少不一样的值,GiST索引将相对无效。
多列GIN索引可用于涉及索引列的任何子集的查询条件。与B-tree或GiST不一样,索引搜索有效性是相同的,不管查询条件使用哪一个索引列。
多列BRIN索引可用于涉及索引列的任何子集的查询条件。像GIN同样,与B-tree或GiST不一样,索引搜索的有效性是同样的,不管查询条件使用哪一个索引列。在单个表上具备多个BRIN索引而不是一个多列BRIN索引的惟一缘由是具备不一样的pages_per_range存储参数。
固然,每列必须与适用于索引类型的操做符一块儿使用;涉及其余操做符的状况将不会考虑使用索引。
多列索引应谨慎使用。在大多数状况下,单列上的索引就足以节省空间和时间。具备三列以上的索引不太多是有用的,除非表的使用很是风格化。有关不一样索引配置的优势的一些讨论,请参见第11.5节和第11.11节。
除了简单地查找要由查询返回的行以外,索引可能可以以特定的排序顺序传递它们。这容许在没有单独的排序步骤的状况下履行查询的ORDER BY规范。在PostgreSQL当前支持的索引类型中,只有B-tree能够产生排序的输出 - 其余索引类型以未指定的实现依赖顺序返回匹配的行。
Planner将经过扫描与规范相匹配的可用索引,或经过以物理顺序扫描表并进行明确排序来考虑知足ORDER BY规范。对于须要扫描表的大部分的查询,显式排序可能比使用索引更快,由于遵循顺序访问模式须要更少的磁盘I / O。当只须要读取几行时,索引更有用。一个重要的特殊状况是ORDER BY与LIMIT n组合:显式排序将必须处理全部数据以识别前n行,但若是存在与ORDER BY匹配的索引,则能够直接检索前n行,而不扫描其他部分。
默认状况下,B-tree索引按照升序存储其条目,最后为null。这意味着对列x上的索引的正向扫描产生知足ORDER BY x(或更详细地,ORDER BY x ASC NULLS LAST)的输出。索引也能够向后扫描,产生知足ORDER BY x DESC的输出(或更详细地,ORDER BY x DESC NULLS FIRST,由于NULLS FIRST是ORDER BY DESC的默认值)。
您能够经过在建立索引时包含ASC,DESC,NULLS FIRST和/或NULLS LAST选项来调整B树索引的排序;例如:
CREATE INDEX test2_info_nulls_low ON test2 (info NULLS FIRST); CREATE INDEX test3_desc_index ON test3 (id DESC NULLS LAST);
以null first存储的索引首先能够知足ORDER BY x ASC NULLS FIRST或ORDER BY x DESC NULLS LAST,具体取决于扫描的方向。
您可能会想,为何要提供全部四个选项,当两个选项以及反向扫描的可能性将涵盖ORDER BY的全部变体。在单列索引中,这些选项确实是多余的,可是在多列索引中它们可能颇有用。考虑(x,y)上的两列索引:若是咱们向前扫描,则能够知足ORDER BY x,y,或者若是咱们向后扫描,则能够知足ORDER BY x DESC,y DESC。可是可能应用程序常常须要使用ORDER BY x ASC,y DESC。没有办法从一个简单的索引中得到这个排序,但若是索引被定义为(x ASC,y DESC)或(x DESC,y ASC),这是可能的。
显然,具备非默认排序顺序的索引是一个至关专门的功能,但有时它们可觉得某些查询产生巨大的加速。是否值得维护这样的索引取决于使用须要特殊排序顺序的查询的频率。
单个索引扫描只能使用使用索引列的查询子句和运算符类的运算符,而且与AND结合。例如,给定(a,b)上的索引,如WHERE a = 5 AND b = 6的查询条件可使用索引,可是像WHERE a = 5 OR b = 6这样的查询没法直接使用索引。
幸运的是,PostgreSQL可以组合多个索引(包括相同索引的多个使用)来处理单次索引扫描没法实现的状况。系统能够跨多个索引扫描造成AND和OR条件。例如,像WHERE x = 42 OR x = 47 OR x = 53 OR x = 99这样的查询能够分解为x上的索引的四个独立扫描,每次使用查询子句之一进行扫描。而后将这些扫描的结果OR化在一块儿以产生结果。另外一个例子是,若是咱们在x和y上有单独的索引,那么WHERE x = 5 AND y = 6之类的查询的一个可能的实现是将每一个索引与适当的查询子句一块儿使用,而后将索引结果AND结合起来,以识别结果行。
要组合多个索引,系统将扫描每一个所需的索引,并准备内存中的位图,为匹配该索引条件的表行的位置提供报告。而后根据查询的须要将位图进行AND和OR操做。最后,访问并返回实际的表行。按照物理顺序访问表行,由于这是位图的布局方式;这意味着原始索引的任何排序都将丢失,所以若是查询具备ORDER BY子句,则将须要单独的排序步骤。由于这个缘由,而且由于每一个额外的索引扫描增长额外的时间,Planner有时会选择使用简单的索引扫描,即便可用的附加索引也可使用。
除了最简单的应用程序以外,索引的各类组合多是有用的,数据库开发人员必须进行权衡来决定要提供哪些索引。有时,多列索引是最好的,但有时最好建立单独的索引并依赖索引组合功能。例如,若是您的工做负载包含有时仅涉及列x的混合查询,则有时仅列Y列,有时两列,您能够选择在x和y上建立两个单独的索引,依赖于索引组合来处理查询使用两列。您还能够在(x,y)上建立多列索引。该索引一般比涉及两列的查询的索引组合更有效,但如第11.3节所述,对于仅涉及y的查询几乎无效,所以不该该是惟一的索引。多列索引和y上的单独索引的组合能够很好地起做用。对于仅涉及x的查询,可使用多列索引,尽管它可能会较大,所以比x上的索引更慢。最后一个选择是建立全部三个索引,可是若是表的搜索比更新更频繁,而且全部三种类型的查询都是常见的,那么这多是合理的。若是其中一种查询类型比其余类型少得多,那么您可能会创建只建立最符合常见类型的两个索引。
索引也可用于强制列的值的惟一性,或多个列的组合值的惟一性。
CREATE UNIQUE INDEX name ON table (column [, ...]);
目前,只有B-tree索引能够被声明为惟一的。
当索引声明为惟一时,不容许具备相等索引值的多个表行。空值不被认为是相等的。多列惟一索引将仅拒绝全部索引列在多行中相等的状况。
当为表定义惟一的约束或主键时,PostgreSQL会自动建立惟一的索引。该索引涵盖构成主键或惟一约束的列(若是适用,则为多列索引),而且是强制约束的机制。
注意:不须要在惟一列上手动建立索引;这样作只会重复自动建立的索引。
索引列没必要仅仅是基础表的列,也能够是从表的一个或多个列计算的函数或标量表达式。此功能对于根据计算结果快速访问表是很是有用的。
例如,进行区分大小写比较的常见方法是使用lower()函数:
SELECT * FROM test1 WHERE lower(col1) = 'value';
若是在lower(col1)函数的结果中定义了一个索引,则该查询可使用索引:
CREATE INDEX test1_lower_col1_idx ON test1 (lower(col1));
若是咱们要声明这个索引UNIQUE,它将阻止建立其col1值的小写相同的行以及col1值实际上相同的行。所以,表达式上的索引能够用于强制不能被定义为简单的惟一约束的约束。
另外一个例子,若是一我的常常进行以下查询:
SELECT * FROM people WHERE (first_name || ' ' || last_name) = 'John Smith';
那么,能够建立一个这样的索引:
CREATE INDEX people_names ON people ((first_name || ' ' || last_name));
CREATE INDEX命令的语法一般须要在索引表达式周围写圆括号,如第二个示例所示。 当表达式只是一个函数调用时,能够省略括号,如第一个示例所示。
索引表达式维护相对昂贵,由于必须为插入时的每一行计算导出的表达式,而且每当更新它时。 可是,索引表达式在索引搜索期间不从新计算,由于它们已经存储在索引中。 在上述两个示例中,系统将查询视为WHERE indexedcolumn =“constant”,所以搜索速度等同于任何其余简单的索引查询。 所以,当检索速度比插入和更新速度更重要时,表达式索引是有用的。
部分索引是在表的子集上构建的索引; 该子集由条件表达式(称为部分索引的谓词)定义。 该索引仅包含知足谓词的那些表行的条目。 部分索引是一个专门的功能,但有几种状况使用部分索引是很是有用的
使用部分索引的一个主要缘由是避免索引常见值。 因为搜索公共值(一个占全部表行的百分之几)的查询将不会使用索引,因此根本没有必要在索引中保留这些行。 这减小了索引的大小,这将加快使用索引的查询。 它也将加快许多表更新操做,由于索引在全部状况下都不须要更新。 例11-1显示了这一想法的可能应用。
Example 11-1。 设置部分索引以排除常见值
假设您将Web服务器访问日志存储在数据库中。 大多数访问源自您组织的IP地址范围,但有些来自其余地方(例如拨号链接上的员工)。 若是您的IP搜索主要用于外部访问,则可能不须要对与组织的子网对应的IP范围进行索引。
假设有像这样的一张表:
CREATE TABLE access_log ( url varchar, client_ip inet, ... );
使用下面的命令来创建一个符合咱们状况的部分索引
CREATE INDEX access_log_client_ip_ix ON access_log (client_ip) WHERE NOT (client_ip > inet '192.168.100.0' AND client_ip < inet '192.168.100.255');
一个典型的使用这个索引的查询多是这样的:
SELECT *
FROM access_log
WHERE url = '/index.html' AND client_ip = inet '212.78.10.32';
下边的这个查询不能用到这个索引:
SELECT *
FROM access_log
WHERE client_ip = inet '192.168.100.23';
很明显这种部分索引要求公共值是预先肯定的,因此这种部分索引最好用于不改变的数据分布。 能够偶尔从新建立索引以调整新的数据分布,但这增长了维护工做。
部分索引的另外一个可能用途是从典型查询工做负载不感兴趣的索引中排除值; 这在例11-2中展现。 这致使与上述相同的优势,可是它防止经过该索引访问“不感兴趣”的值,即便索引扫描在这种状况下多是有利的。 显然,为这种场景设置部分索引须要大量的关注和试验。
Example 11-2。 设置部分索引以排除不感兴趣的值
若是您有一个包含已计费和未计费订单的表,其中未计费订单占总表的一小部分,而那些是最常访问的行,则能够经过仅在未计费的行上建立索引来提升性能。 建立索引的命令以下所示:
CREATE INDEX orders_unbilled_index ON orders (order_nr) WHERE billed is not true;
一个用到这个索引的可能的查询是:
SELECT * FROM orders WHERE billed is not true AND order_nr < 10000;
然而,在哪些不包含order_nr的查询上也可使用到这个索引:
SELECT * FROM orders WHERE billed is not true AND amount > 5000.00;
因为系统必须扫描整个索引,所以这不会比amount列上的局部索引更高效。 然而,若是有相对较少的未结算订单,使用这个部分索引只是找到未结算的订单多是比较好的。
注意,下边的查询用不到这一索引:
SELECT * FROM orders WHERE order_nr = 3501;
3501对应的订单多是已结算的,也多是未结算的。
示例11-2还说明了索引列和谓词中使用的列不须要匹配。 PostgreSQL支持具备任意谓词的部分索引,只要包含索引的列。可是,谓词必须匹配在索引中受益的查询中使用的条件。准确地说,只有系统能够识别查询的WHERE条件在数学上意味着索引的谓词时,才能在查询中使用部分索引。 PostgreSQL没有一个复杂的定理证实器,能够识别以不一样形式编写的数学等效表达式。 (这样一个通常的定理证实者不但难以产生,因此实际使用太慢)。系统能够识别简单的不平等含义,例如“x <1”表示“x <2”;不然谓词条件必须与查询的WHERE条件彻底匹配,不然索引将不被识别为可用。匹配发生在查询计划期间,而不是运行时。所以,参数化查询子句不适用于部分索引。例如,具备参数的准备好的查询可能指定“x <?”对于参数的全部可能值,这不会暗示“x <2”。
部分索引的第三个可能用途不须要在查询中使用索引。 这里的想法是在表的子集上建立惟一的索引,如例11-3所示。 这会在知足索引谓词的行中强制执行惟一性,而不会限制那些不符合索引谓词的行。
Example 11-3. 设置一个部分惟一索引
假设咱们有一个描述测试结果的表格。 咱们但愿确保给定的主题和目标组合只有一个“成功”条目,但可能会有任何数量的“不成功”条目。 这里有一种方法:
CREATE TABLE tests ( subject text, target text, success boolean, ... ); CREATE UNIQUE INDEX tests_success_constraint ON tests (subject, target) WHERE success;
当有不多的成功测试和许多不成功的测试时,这是一种特别有效的方法
最后,部分索引还能够用来覆盖系统的查询计划选择。另外,具备特殊分布的数据集可能致使系统在不该该使用索引时使用索引。在这种状况下,能够设置索引,使其不适用于有问题的查询。一般,PostgreSQL对索引的使用合理的选择(例如,避免他们在检索经常使用值,因此早期的例子真的节省了索引的大小,它不须要避免索引的使用),错误的计划是致使错误报告的缘由
请记住,设置一个分部索引表示您至少知道Planner所知道的状况,特别是知道索引什么时候多是有利的。造成这种知识须要经验和理解Postgresql的索引如何工做。在大多数状况下,部分索引相对于常规索引的优点是比较小的。
更多的关于部分索引的信息能够在这里找到:The case for partial indexes , Partial indexing in POSTGRES: research project, 和 Generalized Partial Indexes (cached version) .
索引定义能够为索引的每一个列指定一个运算符类。
CREATE INDEX name ON table (column opclass [sort options] [, ...]);
运算符类标识要由该列的索引使用的运算符。例如,类型为int4的B-tree索引将使用int4_ops类;此运算符类包含类型为int4的值的比较函数。实际上,列的数据类型的默认操做符类一般就足够了。使用运算符类的主要缘由是对于某些数据类型,可能有多个有意义的索引行为。例如,咱们可能但愿按绝对值或实数对复数数据类型进行排序。咱们能够经过为数据类型定义两个运算符类,而后在进行索引时选择适当的类来实现。操做员类肯定基本排序顺序(能够经过添加排序选项COLLATE,ASC / DESC和/或NULLS FIRST / NULLS LAST)进行修改。
除了默认值以外,还有一些内置的运算符类:
运算符类text_pattern_ops,varchar_pattern_ops和bpchar_pattern_ops分别支持类型为text,varchar和char的B-tree索引。与默认操做符类的区别在于,这些值是严格按字符比较而不是根据特定于区域设置的排序规则进行比较的。当数据库不使用标准“C”语言环境时,这使得这些操做符类适用于涉及模式匹配表达式(LIKE或POSIX正则表达式)的查询。举个例子,你可能像这样索引varchar列:
CREATE INDEX test_index ON test_table (col varchar_pattern_ops);
请注意,若是您但愿涉及普通<,<=,> 或 >= 比较的查询使用索引,您还应该使用默认运算符类建立索引。这样的查询不能使用xxx_pattern_ops运算符类。 (可是普通的等式比较可使用这些运算符类。)能够在不一样的运算符类的同一列上建立多个索引。若是您使用C语言环境,则不须要xxx_pattern_ops运算符类,由于具备默认运算符类的索引可用于C语言环境中的模式匹配查询。
如下查询显示全部定义的运算符类:
SELECT am.amname AS index_method, opc.opcname AS opclass_name, opf.opfname AS opfamily_name, opc.opcintype::regtype AS indexed_type, opc.opcdefault AS is_default FROM pg_am am, pg_opclass opc, pg_opfamily opf WHERE opc.opcmethod = am.oid AND opc.opcfamily = opf.oid ORDER BY index_method, opclass_name;
此查询显示全部定义的操做员族和每一个系列中包含的全部操做符:
SELECT am.amname AS index_method, opf.opfname AS opfamily_name, amop.amopopr::regoperator AS opfamily_operator FROM pg_am am, pg_opfamily opf, pg_amop amop WHERE opf.opfmethod = am.oid AND amop.amopfamily = opf.oid ORDER BY index_method, opfamily_name, opfamily_operator;
索引只能支持每一个索引列的一个排序规则。若是感兴趣的是多个排序规则,则可能须要多个索引。
考虑如下的SQL:
CREATE TABLE test1c (
id integer,
content varchar COLLATE "x"
);
CREATE INDEX test1c_content_index ON test1c (content);
索引自动使用底层列的排序规则,因此,对于如下的查询,
SELECT * FROM test1c WHERE content > constant;
可使用索引,由于默认比较将使用列的排序规则。可是,此索引没法加速涉及其余排序规则的查询。因此若是查询是如下的形式
SELECT * FROM test1c WHERE content > constant COLLATE "y";
能够建立一个支持“y”排序规则的附加索引,以下所示:
CREATE INDEX test1c_content_y_index ON test1c (content COLLATE "y");
PostgreSQL中的全部索引都是辅助索引,这意味着每一个索引都与表的主数据区域(PostgreSQL术语中称为表的堆)分开存储。这意味着在普通的索引扫描中,每行检索都须要从索引和堆中获取数据。此外,虽然与索引条件匹配的索引条目一般在索引中靠近在一块儿,可是它们引用的表行可能位于堆中的任何位置。所以,索引扫描的堆访问部分涉及到堆中的大量随机访问,这可能很慢,特别是在传统的旋转介质上。 (如第11.5节所述,位图扫描尝试经过以排序顺序执行堆访问来减轻此成本,但也仅仅作了这些)
为了解决这个性能问题,PostgreSQL支持仅索引扫描(就相似其余数据库中的覆盖索引),能够单独从索引中响应查询,而无需任何堆访问。基本思想是直接从每一个索引条目返回值,而不是查询关联的堆条目。这种方法可使用两个基本限制:
查询必须仅引用索引中存储的列。例如,给定一个也有列z的表的x和y列的索引,这些查询可使用仅索引扫描:
SELECT x, y FROM tab WHERE x = 'key';
SELECT x FROM tab WHERE x = 'key' AND y < 42;
可是另外的这些不能使用:
SELECT x, z FROM tab WHERE x = 'key';
SELECT x FROM tab WHERE x = 'key' AND z < 42;
(表达式索引和部分索引会使此规则复杂化,以下所述)
若是知足这两个基本要求,那么查询所需的全部数据值均可以从索引中得到,所以仅索引扫描在物理上是可行的。但PostgreSQL中的任何表扫描还须要额外的要求:它必须验证每一个检索到的行对查询的MVCC快照是“可见的”,如第13章所述。可见性信息不存储在索引条目中,仅存储在堆条目中;因此乍一看,彷佛每行检索都须要堆访问。若是表行最近被修改的话,的确如此。然而,对于不多变化的数据,有一个方法来解决这个问题。 PostgreSQL跟踪表中堆栈中的每一个页面,该页面中存储的全部行是否足够旧,以便对当前和将来的全部事务均可见。该信息存储在表的可见性映射中。仅索引扫描在找到候选索引条目后,检查相应堆页的可见性映射位。若是它被设置,该行是已知可见的,所以能够返回数据,而无需进一步的工做。若是未设置,则必须访问堆条目以肯定它是否可见,所以在标准索引扫描中不会得到性能优点。即便在成功的状况下,这种方法交换了对堆访问的可见性映射访问;可是因为可见性映射比它描述的堆小四个数量级,因此须要远远少于物理I / O来访问它。在大多数状况下,可见性映射仍然始终缓存在内存中。
简而言之,在给定两个基本要求的状况下,仅索引扫描是可行的,只有当表的堆页的很大一部分设置了它们的全可见映射位时,才能很是有效。可是大部分行不变的表格一般足以使这种类型的扫描在实践中很是有用。
为了有效利用仅索引扫描功能,您能够选择建立这样的索引,其中只有前导列旨在匹配WHERE子句,然后续列保存要由查询返回的“有效负载”数据。例如,若是您一般会运行这样的查询:
SELECT y FROM tab WHERE x = 'key';
加速这种查询的传统方法将是仅在x上建立索引。可是,(x,y)上的索引将提供将此查询实现为仅索引扫描的可能性。如前所述,这样的索引将更大,所以比单独的x上的索引更昂贵,所以只有当表已知大部分是静态时才是一个比较好的选择。请注意,索引在(x,y)不是(y,x)上声明很重要,由于大多数索引类型(的复合索引)(特别是B-tree)的搜索在前导搜索列不匹配的时候效率不高。
原则上,仅索引扫描能够与表达式索引一块儿使用。例如,给定f(x)上的索引,其中x是表列,应该能够执行
SELECT f(x) FROM tab WHERE f(x) < 1;
做为仅索引扫描;若是f()是一个昂贵的计算函数,这是很是有吸引力的。然而,PostgreSQL的计划者目前对这种状况作的并非很好。只有查询所需的全部列均可以从索引中得到时,才会将查询视为可执行的索引扫描。在这个例子中,除了在上下文f(x)中,不须要x,可是规划者并无注意到,而且认为只有索引扫描是不可能的。若是仅索引扫描彷佛是足够有价值的,那么能够经过将索引声明为on(f(x),x)来解决这个问题,其中第二列不指望在实践中使用,但只是在说服计划人员能够进行仅索引扫描。若是目标是避免从新计算f(x),则另外须要注意的是,规划者不必定会与索引列中不能在可索引的WHERE子句中使用f(x)。一般会在如上所示的简单查询中获取此权限,但不包括涉及链接的查询。这些缺陷可能会在之后版本的PostgreSQL中获得补救。
部分索引也与仅索引扫描有趣的交互。考虑示例11-3中所示的部分索引:
CREATE UNIQUE INDEX tests_success_constraint ON tests (subject, target) WHERE success;
原则上,咱们能够对此索引进行仅索引扫描,以知足查询:
SELECT target FROM tests WHERE subject = 'some-subject' AND success;
可是有一个问题:WHERE子句依赖了没有做为索引的结果列的success列。尽管如此,仅索引扫描是可能的,由于计划不须要在运行时从新检查WHERE子句的那部分:索引中找到的全部条目必须具备success = true,所以不须要在计划中显式检查。 PostgreSQL 9.6及更高版本将会识别这种状况,并容许生成只针对索引的扫描,但较旧的版本不会生成。
虽然PostgreSQL中的索引不须要维护或调优,但检查实际查询工做负载实际使用哪些索引仍然十分重要。使用EXPLAIN命令检查单个查询的索引使用状况。其第14.1节介绍了EXPLAIN的用法。也能够在运行的服务器中收集有关索引使用状况的统计信息,如第28.2节所述。
肯定要建立一个什么类型的索引的过程通常比较难。在上一节中,示例中已经显示了一些典型的状况。常常须要进行大量实验。本节的其他部分提供了一些技巧:
始终先运行ANALYZE。此命令收集表中值的分布统计信息。须要此信息来估计查询返回的行数,Planner须要为每一个可能的查询计划分配实际成本。在没有任何真实的统计数据的状况下,假定一些默认值,这几乎是不许确的。所以,检查应用程序的索引使用而不运行ANALYZE是不可行的。有关详细信息,请参见第24.1.3节和第24.1.6节。
使用实际数据进行实验。使用测试数据设置索引会告诉您测试数据须要哪些索引,但仅仅是对测试数据有效。
使用很是小的测试数据集尤为致命。当选择100000行中的1000个多是索引的候选者时,选择100行中的1个几乎不会是这样的,由于100行可能适合单个磁盘页面,而且没有计划能够顺序地获取1个磁盘页面。
在编写测试数据时也要当心,当应用程序还没有投入生产时,这一般是不可避免的。很是类似,彻底随机或以排序顺序插入的值将使统计数据偏离实际数据所具备的分布。
当不使用索引时,测试可能有助于强制使用它们。运行时参数能够关闭各类计划类型(参见第19.7.1节)。例如,关闭最基本的计划的顺序扫描(enable_seqscan)和嵌套循环链接(enable_nestloop)将强制系统使用不一样的计划。若是系统仍然选择顺序扫描或嵌套循环链接,那么可能还有一个更根本的缘由是为何索引没有被使用;例如,查询条件与索引不匹配。 (什么样的查询可使用上一节中介绍的类型的索引。)
若是强制索引使用确实使用索引,那么有两种可能性:系统是正确的,使用索引确实不合适,或者查询计划的成本估计并不反映现实状况。因此你应该检查使用和不使用索引进行查询时的时间消耗。 EXPLAIN ANALYZE命令在这里很是有用。
若是事实证实成本估计是错误的,那么再有两种可能性。总成本由每一个计划节点的每行成本乘以计划节点的选择性估计值计算。能够经过运行时参数来调整计划节点的成本(在第19.7.2节中描述)。选择性估算不许确是因为统计数据不足。经过调整统计信息收集参数能够改善这一点(参见ALTER TABLE)。
若是您没有成功地把成本调整的更合适,那么您可能须要明确强制使用索引使用。您还能够联系PostgreSQL开发人员来检查问题。