postgresql 建立索引

postgresql 建立索引,第1张

一、索引的类型:

PostgreSQL提供了多种索引类型:B-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 NULL和IS NOT NULL的查询中,PostgreSQL也可以使用B-Tree索引。然而对于基于模式匹配 *** 作符的查询,如LIKE、ILIKE、~和 ~*,仅当模式存在一个常量,且该常量位于模式字符串的开头时,如col LIKE 'foo%'或col ~ '^foo',索引才会生效,否则将会执行全表扫描,如:col LIKE '%bar'。

2. Hash:

CREATE INDEX name ON table USING hash (column)

散列(Hash)索引只能处理简单的等于比较。当索引列使用等于 *** 作符进行比较时,查询规划器会考虑使用散列索引。

这里需要额外说明的是,PostgreSQL散列索引的性能不比B-Tree索引强,但是散列索引的尺寸和构造时间则更差。另外,由于散列索引 *** 作目前没有记录WAL日志,因此一旦发生了数据库崩溃,我们将不得不用REINDEX重建散列索引。

3. GiST:

GiST索引不是一种单独的索引类型,而是一种架构,可以在该架构上实现很多不同的索引策略。从而可以使GiST索引根据不同的索引策略,而使用特定的 *** 作符类型。

4. 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)

1. B-Tree类型的复合索引:

在B-Tree类型的复合索引中,该索引字段的任意子集均可用于查询条件,不过,只有当复合索引中的第一个索引字段(最左边)被包含其中时,才可以获得最高效率。

2. GiST类型的复合索引:

在GiST类型的复合索引中,只有当第一个索引字段被包含在查询条件中时,才能决定该查询会扫描多少索引数据,而其他索引字段上的条件只是会限制索引返回的条目。假如第一个索引字段上的大多数数据都有相同的键值,那么此时应用GiST索引就会比较低效。

3. GIN类型的复合索引:

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

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

三、组合多个索引:

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 *** 作并生成最终的结果行。

为了组合多个索引,系统扫描每个需要的索引,然后在内存里组织一个BITMAP,它将给出索引扫描出的数据在数据表中的物理位置。然后,再根据查询的需要,把这些位图进行AND或者OR的 *** 作并得出最终的BITMAP。最后,检索数据表并返回数据行。表的数据行是按照物理顺序进行访问的,因为这是位图的布局,这就意味着任何原来的索引的排序都将消失。如果查询中有ORDER BY子句,那么还将会有一个额外的排序步骤。因为这个原因,以及每个额外的索引扫描都会增加额外的时间,这样规划器有时候就会选择使用简单的索引扫描,即使有多个索引可用也会如此。

四、唯一索引:

CREATE UNIQUE INDEX name ON table (column [, ...])

五、表达式索引:

表达式索引主要用于在查询条件中存在基于某个字段的函数或表达式的结果与其他值进行比较的情况,如:

SELECT * FROM test1 WHERE lower(col1) = 'value'

此时,如果我们仅仅是在col1字段上建立索引,那么该查询在执行时一定不会使用该索引,而是直接进行全表扫描。如果该表的数据量较大,那么执行该查询也将会需要很长时间。解决该问题的办法非常简单,在test1表上建立基于col1字段的表达式索引,如:

CREATE INDEX test1_lower_col1_idx ON test1 (lower(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命令就很有用了。

1. 检查约束

检查约束是最常见的约束类型。它允许你声明在某个字段里的数值必须使一个布尔表达式为真。比如,要强制一个正数的产品价格,你可以用:

CREATE TABLE products (product_no integer,name text,price numeric CHECK (price >0) )

如你所见,约束定义在数据类型之后,就好像缺省值定义一样。缺省值和约束可以按任意顺序排列。一个检查约束由一个关键字 CHECK 后面跟一个放在圆括弧里的表达式组成。检查约束表达式应该包含受约束的字段,否则这个约束就没什么意义了。

你还可以给这个约束取一个独立的名字。这样就可以令错误信息更清晰,并且在你需要修改它的时候引用这个名字。语法是:

CREATE TABLE products (product_no integer,name text,price numeric CONSTRAINT positive_price CHECK (price >0) )

因此,要声明一个命名约束,使用关键字 CONSTRAINT 后面跟一个标识符(作为名字),然后再跟约束定义。如果你不用这个方法声明约束,那么系统会自动为你选择一个名字。

一个检查约束也可以引用多个字段。假设你存储一个正常价格和一个折扣价,并且你想保证折扣价比正常价低。

CREATE TABLE products (product_no integer,name text,price numeric CHECK (price >0),discounted_price numeric CHECK (discounted_price >0),CHECK (price >discounted_price) )

头两个约束看上去很面熟。第三个使用了一个新的语法。它没有附着在某个字段上,而是在逗号分隔的字段列表中以一个独立行的形式出现。字段定义和约束定义可以按照任意顺序列出。

我们称头两个约束是"字段约束",而第三个约束是"表约束"(和字段定义分开写)。字段约束也可以写成表约束,而反过来很可能不行,因为系统假设字段约束只引用它所从属的字段。PostgreSQL 并不强制这条规则,但是如果你希望自己的表定义可以和其它数据库系统兼容,那么你最好还是遵循这条规则。上面的例子也可以这么写:

CREATE TABLE products (product_no integer,name text,price numeric,CHECK (price >0),discounted_price numeric,CHECK (discounted_price >0),CHECK (price >discounted_price) ) 或者是

CREATE TABLE products (product_no integer,name text,price numeric CHECK (price >0),discounted_price numeric,CHECK (discounted_price >0 AND price >discounted_price) )

这只是风格的不同。

和字段约束一样,我们也可以给表约束赋予名称,方法也相同:

CREATE TABLE products (product_no integer,name text,price numeric,CHECK (price >0),discounted_price numeric,CHECK (discounted_price >0),CONSTRAINT valid_discount CHECK (price >discounted_price) )

我们还要注意的是,当约束表达式计算结果为 NULL 的时候,检查约束会被认为是满足条件的。因为大多数表达式在含有 NULL *** 作数的时候结果都是 NULL ,所以这些约束不能阻止字段值为 NULL 。要确保一个字段值不为 NULL ,可以使用下一节介绍的非空约束。

2. 非空约束

非空约束只是简单地声明一个字段必须不能是 NULL 。下面是一个例子:

CREATE TABLE products (product_no integer NOT NULL,name text NOT NULL,price numeric )

一个非空约束总是写成一个字段约束。非空约束在功能上等效于创建一个检查约束 CHECK (column_name IS NOT NULL) ,但在 PostgreSQL 里,创建一个明确的非空约束效率更高。缺点是你不能给它一个明确的名字。

当然,一个字段可以有多个约束。只要一个接着一个写就可以了:

CREATE TABLE products (product_no integer NOT NULL,name text NOT NULL,price numeric NOT NULL CHECK (price >0) )

它们的顺序无所谓。顺序并不影响约束检查的顺序。

NOT NULL 约束有个相反的约束:NULL 约束。它并不意味着该字段必须是空,因为这样的字段也没用。它只是定义了该字段可以为空的这个缺省行为。在 SQL 标准里没有定义 NULL 约束,因此不应该在可移植的应用中使用它。在 PostgreSQL 里面增加这个约束只是为了和其它数据库系统兼容。不过,有些用户喜欢它,因为这个约束可以让他们很容易在脚本文件里切换约束。比如,你可以从下面这样开始

CREATE TABLE products (product_no integer NULL,name text NULL,price numeric NULL )

然后在需要的时候插入 NOT 关键字。

【提示】在大多数数据库设计里,主要的字段都应该标记为非空。

3. 唯一约束

唯一约束保证在一个字段或者一组字段里的数据与表中其它行的数据相比是唯一的。它的语法是:

CREATE TABLE products (product_no integer UNIQUE,name text,price numeric )

上面是写成字段约束,下面这个则写成表约束:

CREATE TABLE products (product_no integer,name text,price numeric,UNIQUE (product_no) )

如果一个唯一约束引用一组字段,那么这些字段用逗号分隔列出:

CREATE TABLE example (a integer,b integer,c integer,UNIQUE (a, c) )

这样就声明了特定字段值的组合在整个表范围内是唯一的。但是这些字段中的某个单独值可以不必是(并且通常也确实不是)唯一的。

你也可以给唯一约束赋予一个自己定义的名字,方法与前面相同:

CREATE TABLE products (product_no integer CONSTRAINT must_be_different UNIQUE,name text,price numeric )

通常,如果包含在唯一约束中的那几个字段在表中有多个相同的行,就违反了唯一约束。但是在这种比较中,NULL 被认为是不相等的。这就意味着,在多字段唯一约束的情况下,如果在至少一个字段上出现 NULL ,那么我们还是可以存储同样的这种数据行。这种行为遵循 SQL 标准,但是我们听说其它 SQL 数据库可能不遵循这个标准。因此如果你要开发可移植的程序,那么最好仔细些。

4. 主键

从技术上讲,主键约束只是唯一约束和非空约束的组合。所以,下面两个表定义是等价的:

CREATE TABLE products (product_no integer UNIQUE NOT NULL,name text,price numeric )CREATE TABLE products (product_no integer PRIMARY KEY,name text,price numeric )

主键也可以约束多于一个字段;其语法类似于唯一约束:

CREATE TABLE example (a integer,b integer,c integer,PRIMARY KEY (a, c) )

主键表示一个或多个字段的组合可以用于唯一标识表中的数据行。这是定义一个主键的直接结果。请注意:一个唯一约束实际上并不能提供一个唯一标识,因为它不排除 NULL 。这个功能对文档目的和客户应用都很有用。比如,一个可以修改行数值的 GUI 应用可能需要知道一个表的主键才能唯一地标识每一行。

一个表最多可以有一个主键(但是它可以有多个唯一和非空约束)。关系型数据库理论告诉我们,每个表都必须有一个主键。PostgreSQL 并不强制这个规则,但我们最好还是遵循它。

5. 外键

外键约束声明一个字段(或者一组字段)的数值必须匹配另外一个表中出现的数值。我们把这个行为称为两个相关表之间的参照完整性。

假设你有个产品表,我们可能使用了好几次:

CREATE TABLE products (product_no integer PRIMARY KEY,name text,price numeric )

假设你有一个存储这些产品的订单的表。我们想保证订单表只包含实际存在的产品。因此我们在订单表中定义一个外键约束引用产品表:

CREATE TABLE orders (order_id integer PRIMARY KEY,product_no integer REFERENCES products (product_no),quantity integer )

现在,我们不能创建任何其 product_no 没有在产品表中出现的订单。

在这种情况下我们把订单表叫做引用表,而产品表叫做被引用表。同样,也有引用字段和被引用字段。

你也可以把上面的命令简写成

CREATE TABLE orders (order_id integer PRIMARY KEY,product_no integer REFERENCES products,quantity integer )

因为如果缺少字段列表的话,就会引用被引用表的主键。

一个外键也可以约束和引用一组字段。同样,也需要写成表约束的形式。下面是一个捏造出来的语法例子:

CREATE TABLE t1 ( a integer PRIMARY KEY, b integer, c integer, FOREIGN KEY (b, c) REFERENCES other_table (c1, c2) )

当然,被约束的字段数目和类型需要和被引用字段数目和类型一致。

和平常一样,你也可以给外键约束赋予自定义的名字。

一个表可以包含多于一个外键约束。这个特性用于实现表之间的多对多关系。比如你有关于产品和订单的表,但现在你想允许一个订单可以包含多种产品(上面那个结构是不允许这么做的),你可以使用这样的结构:

CREATE TABLE products (product_no integer PRIMARY KEY,name text,price numeric ) CREATE TABLE orders (order_id integer PRIMARY KEY,shipping_address text,... ) CREATE TABLE order_items (product_no integer REFERENCES products,order_id integer REFERENCES orders,quantity integer,PRIMARY KEY (product_no, order_id) )

注意最后的表的主键和外键是重叠的。

我们知道外键不允许创建和任何产品都无关的订单。但是如果一个订单创建之后其引用的产品被删除了怎么办?SQL 也允许你处理这个问题。简单说,我们有几种选择:

不允许删除一个被引用的产品

同时也删除订单

其它的?

为了说明这个问题,我们对上面的多对多关系制定下面的策略:如果有人想删除一种仍然被某个订单引用的产品(通过 order_items),那么就不允许这么做。如果有人删除了一个订单,那么订单项也被删除。

CREATE TABLE products (product_no integer PRIMARY KEY,name text,price numeric ) CREATE TABLE orders (order_id integer PRIMARY KEY,shipping_address text,... ) CREATE TABLE order_items (product_no integer REFERENCES products ON DELETE RESTRICT,order_id integer REFERENCES orders ON DELETE CASCADE,quantity integer,PRIMARY KEY (product_no, order_id) )

限制和级联删除是两种最常见的选项。RESTRICT 禁止删除被引用的行。NO ACTION 的意思是如果在检查约束的时候还存在任何引用行,则抛出错误;如果你不声明任何东西,那么它就是缺省的行为。这两个选择的实际区别是:NO ACTION 允许约束检查推迟到事务的晚些时候,而 RESTRICT 不行。CASCADE 声明在删除一个被引用的行的时候,所有引用它的行也会被自动删除掉。在外键字段上的动作还有两个选项:SET NULL 和 SET DEFAULT ,它们导致在被引用行删除的时候,将引用它们的字段分别设置为 NULL 和缺省值。请注意这些选项并不能让你逃脱被观察和约束的境地。比如,如果一个动作声明 SET DEFAULT ,但是缺省值并不能满足外键,那么该动作就会失败。

与 ON DELETE 类似的还有 ON UPDATE 选项,它是在被引用字段修改(更新)的时候调用的,可用的动作是一样的。

有关更新和删除数据的更多信息可以在章6里找到。

最后,我们应该说明的是,一个外键必须要么引用一个主键,要么引用一个唯一约束。如果外键引用了一个唯一约束,那么在如何匹配 NULL 这个问题上还有一些其它的可能性。这些东西都在 CREATE TABLE 中解释。


欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/bake/11941590.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-05-19
下一篇 2023-05-19

发表评论

登录后才能评论

评论列表(0条)

保存