PostGresql索引深入分析

来源:互联网 发布:排队叫号软件 编辑:程序博客网 时间:2024/06/05 10:41

一、索引的类型:

    PostgreSQL提供了多种索引类型:B-Tree、R-Tree、Hash、GiST和GIN,由于它们使用了不同的算法,因此每种索引类型都有其适合的查询类型,缺省时,CREATE INDEX命令将创建B-Tree索引。
    
    1. B-Tree:
    CREATE TABLE test1 (
        id integer,
        content varchar
    );
    CREATE INDEX test1_id_index ON test1 (id);    
    B-Tree索引主要用于等于和范围查询,特别是当索引列包含操作符" <、<=、=、>=和>"作为查询条件时,PostgreSQL的查询规划器都会考虑使用B-Tree索引。在使用BETWEEN、IN、IS NOT NULL的查询中,PostgreSQL也可以使用B-Tree索引。IS NULL谓词不能使用B-Tree索引。

    然而对于基于模式匹配操作符的查询,如LIKE、ILIKE、~和 ~*,仅当模式存在一个常量,且该常量位于模式字符串的开头时,如col LIKE 'foo%'或col ~ '^foo',索引才会生效,否则将会执行全表扫描,如:col LIKE '%bar'。 

    

    2. R-Tree:
    R-tree 索引用 Guttman 的二次分割算法实现了标准的 R-tree,特别适合于空间数据

     CREATE INDEX name ON table USING RTREE(column);
   --下列操作符之一进行比较的时候:<<,&<,&>,>>,@,~=,&&,PostgreSQL会考虑使用 R-tree 索引

    3. Hash:
    CREATE INDEX name ON table USING hash (column);
    散列(Hash)索引只能处理简单的等于比较。当索引列使用等于操作符进行比较时,查询规划器会考虑使用散列索引。
    注意:PostgreSQL散列索引的性能不比B-Tree索引强,但是散列索引的尺寸和构造时间则更差。另外,由于散列索引操作目前没有记录WAL日志,因此一旦发生了数据库崩溃,我们将不得不用REINDEX重建散列索引。
    
    4. GiST:
    GiST 索引不是单独一种索引类型,而是一种架构,可以在这种架构上实现很多不同的索引策略。因此,可以使用 GiST 索引的特定操作符类型根据索引策略的不同而不同(操作符表)
   GiST 的意思是通用的搜索树(Generalized Search Tree)
        它是一种平衡的,树状结构的访问方法,在系统中起一个基础的模版,
        然后可以使用它实现任意索引模式。
        B+-trees,R-trees 和许多其它的索引模式都可以用 GiST 实现。 
   GiST 的一个优点是它允许一种客户化的数据类型和合适的访问方法一起开发,
        并且是由该数据类型范畴里的专家,而不是数据库专家开发

    5. GIN:
    GIN索引是反转索引,它可以处理包含多个键的值(比如数组)。与GiST类似,GIN同样支持用户定义的索引策略,从而可以使GIN索引根据不同的索引策略,而使用特定的操作符类型。作为示例,PostgreSQL的标准发布中包含了用于一维数组的GIN操作符类型,如:<@、@>、=、&&等。

二、复合索引:

    PostgreSQL中的索引可以定义在数据表的多个字段上,如:
    CREATE TABLE test2 (
        major int,
        minor int,
        name varchar
    }
    CREATE INDEX test2_mm_idx ON test2 (major, minor);

    在当前的版本中,只有B-tree、GiST和GIN支持复合索引,其中最多可以声明32个字段。
    1. B-Tree类型的复合索引:
    在B-Tree类型的复合索引中,该索引字段的任意子集均可用于查询条件,不过,只有当复合索引中的第一个索引字段(最左边)被包含其中时,才可以获得最高效率。
    
    2. GiST类型的复合索引:
    在GiST类型的复合索引中,只有当第一个索引字段被包含在查询条件中时,才能决定该查询会扫描多少索引数据,而其他索引字段上的条件只是会限制索引返回的条目。假如第一个索引字段上的大多数数据都有相同的键值,那么此时应用GiST索引就会比较低效。 

    3. GIN类型的复合索引:
    与B-Tree和GiST索引不同的是,GIN复合索引不会受到查询条件中使用了哪些索引字段子集的影响,无论是哪种组合,都会得到相同的效率。

    使用复合索引应该谨慎。在大多数情况下,单一字段上的索引就已经足够了,并且还节约时间和空间。除非表的使用模式非常固定,否则超过三个字段的索引几乎没什么用处。 

三、组合多个索引:  

    假定在一个表的列a和上定义了一个索引,对于搜索条件类似WHERE a = 5 AND b = 6的查询,只需要对这个索引扫描一次就可以得到需要的数据行。但对于WHERE a = 5 OR b = 6这样的查询,只对索引扫描一次无法得到需要的数据行。

    对于那些只扫描一次索引无法得到结果的查询,PostgresSQL可以通过扫描多个索引(包括对同一索引扫描多次)来求解这些查询。例如,对于类似WHERE x = 42 OR x = 47 OR x = 53 OR x = 99这样的查询,系统会对建在列x上的索引扫描4次,最后将每次扫描的结果合并起来。再看一个例子,假设在列xy上各创建了一个索引,对于WHERE x = 5 AND y = 6这样的查询,可以对xy上的索引各扫描一次,然后将两个结果求交集即可。

 

    可以对表中的多个列建立一个索引,也可以对每个列都建一个单独的索引。如何选择索引类型要看具体的查询类型。例如,假设数据库有一个表中有两个列,应用程序使用的查询有时候引用x,有时候引用y,有时候同时引用x和y,有下面三种建立索引的策略:

(1)在x和y上个创建一个单列索引。当处理同时引用x和y的查询时,可以同时扫描x和y上的索引,然后对两个扫描结果求交集。

(2)可以选择在x 和y上创建一个多列索引,这个索引对于同时引用x和y的查询很有用,它也可以用于只引用x的查询,但它对于只引用y的查询却没有什么用处。

(3)在y上创建一个单列索引,同时在x和y上创建一个多列索引。这种方式创建的索引比较多,要消耗较多的资源维护索引和表中数据的同步。

(4)在x和y上各创建一个单列索引,同时在x和y上创建一个多列索引。这种方式创建的索引过多,要消耗大量资源维护索引和表中数据的同步。

 

    如果大部分查询只引用x或y,可以选第一种策略。如果大部分查询同时引用x和y,可以选第二或第三种策略。如果表上的更新操作非常少,同时查询有时候引用x,有时候引用y,有时候同时引用x和y,可以选第三种或第四种策略。

    

    
四、唯一索引:

    目前,只有B-Tree索引可以被声明为唯一索引。
    CREATE UNIQUE INDEX name ON table (column [, ...]);
    如果索引声明为唯一索引,那么就不允许出现多个索引值相同的行。我们认为NULL值相互间不相等。
    
五、表达式索引:

    表达式索引主要用于在查询条件中存在基于某个字段的函数或表达式的结果与其他值进行比较的情况,如:
    SELECT * FROM test1 WHERE lower(col1) = 'value';
    此时,如果我们仅仅是在col1字段上建立索引,那么该查询在执行时一定不会使用该索引,而是直接进行全表扫描。如果该表的数据量较大,那么执行该查询也将会需要很长时间。解决该问题的办法非常简单,在test1表上建立基于col1字段的表达式索引,如:
    CREATE INDEX test1_lower_col1_idx ON test1 (lower(col1));
    如果我们把该索引声明为UNIQUE,那么它会禁止创建那种col1数值只是大小写有区别的数据行,以及col1数值完全相同的数据行。因此,在表达式上的索引可以用于强制那些无法定义为简单唯一约束的约束。现在让我们再看一个应用表达式索引的例子。
    SELECT * FROM people WHERE (first_name || ' ' || last_name) = 'John Smith';
    和上面的例子一样,尽管我们可能会为first_name和last_name分别创建独立索引,或者是基于这两个字段的复合索引,在执行该查询语句时,这些索引均不会被使用,该查询能够使用的索引只有我们下面创建的表达式索引。
    CREATE INDEX people_names ON people ((first_name || ' ' || last_name));
    CREATE INDEX命令的语法通常要求在索引表达式周围书写圆括弧,就像我们在第二个例子里显示的那样。如果表达式只是一个函数调用,那么可以省略,就像我们在第一个例子里显示的那样。
    从索引维护的角度来看,索引表达式要相对低效一些,因为在插入数据或者更新数据的时候,都必须为该行计算表达式的结果,并将该结果直接存储到索引里。然而在查询时,PostgreSQL就会把它们看做WHERE idxcol = 'constant',因此搜索的速度等效于基于简单索引的查询。通常而言,我们只是应该在检索速度比插入和更新速度更重要的场景下使用表达式索引。 
    
六、部分索引:

    部分索引(partial index)是建立在一个表的子集上的索引,而该子集是由一个条件表达式定义的(叫做部分索引的谓词)。该索引只包含表中那些满足这个谓词的行。
    由于不是在所有的情况下都需要更新索引,因此部分索引会提高数据插入和数据更新的效率。然而又因为部分索引比普通索引要小,因此可以更好的提高确实需要索引部分的查询效率。见以下三个示例:
    1. 索引字段和谓词条件字段一致
    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';

    2. 索引字段和谓词条件字段不一致:
    PostgreSQL支持带任意谓词的部分索引,唯一的约束是谓词的字段也要来自于同样的数据表。注意,如果你希望你的查询语句能够用到部分索引,那么就要求该查询语句的条件部分必须和部分索引的谓词完全匹配。 准确说,只有在PostgreSQL能够识别出该查询的WHERE条件在数学上涵盖了该索引的谓词时,这个部分索引才能被用于该查询。
    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;
    那么对于如下查询呢?
    SELECT * FROM orders WHERE billed is not true AND amount > 5000.00;
    这个查询将不像上面那个查询这么高效,毕竟查询的条件语句中没有用到索引字段,然而查询条件"billed is not true"却和部分索引的谓词完全匹配,因此PostgreSQL将扫描整个索引。这样只有在索引数据相对较少的情况下,该查询才能更有效一些。
    下面的查询将不会用到部分索引。
    SELECT * FROM orders WHERE order_nr = 3501;
    
    3. 数据表子集的唯一性约束:
    CREATE TABLE tests (
        subject text,
        target text,
        success boolean,
        ...
    );
    CREATE UNIQUE INDEX tests_success_constraint ON tests(subject, target) WHERE success;

    该部分索引将只会对success字段值为true的数据进行唯一性约束。在实际的应用中,如果成功的数据较少,而不成功的数据较多时,该实现方法将会非常高效。
     
七、检查索引的使用:

    见以下四条建议:
    1. 总是先运行ANALYZE
    该命令将会收集表中数值分布状况的统计。在估算一个查询返回的行数时需要这个信息,而规划器则需要这个行数以便给每个可能的查询规划赋予真实的开销值。如果缺乏任何真实的统计信息,那么就会使用一些缺省数值,这样肯定是不准确的。因此,如果还没有运行ANALYZE就检查一个索引的使用状况,那将会是一次失败的检查。 
    2. 使用真实的数据做实验。
    用测试数据填充数据表,那么该表的索引将只会基于测试数据来评估该如何使用索引,而不是对所有的数据都如此使用。比如从100000行中选1000行,规划器可能会考虑使用索引,那么如果从100行中选1行就很难说也会使用索引了。因为100行的数据很可能是存储在一个磁盘页面中,然而没有任何查询规划能比通过顺序访问一个磁盘页面更加高效了。与此同时,在模拟测试数据时也要注意,如果这些数据是非常相似的数据、完全随机的数据,或按照排序顺序插入的数据,都会令统计信息偏离实际数据应该具有的特征。    
    3. 如果索引没有得到使用,那么在测试中强制它的使用也许会有些价值。有一些运行时参数可以关闭各种各样的查询规划。
    4. 强制使用索引用法将会导致两种可能:一是系统选择是正确的,使用索引实际上并不合适,二是查询计划的开销计算并不能反映现实情况。这样你就应该对使用和不使用索引的查询进行计时,这个时候EXPLAIN ANALYZE命令就很有用了。