Postgresql8.1
的索引
索引是提高数据库性能的常用方法。索引可以大大加速对特定字段机器组合的检索或查找速度,但与此同时又会增加数据库的负荷,所以如何有效的利用索引成为了一名
DBA
必须掌握的技术。
介绍
对于下面这样一个表:
Create table test1(
Id integer,
Content varchar
)
在该表上将大量采用如下查询:
Select content from test1 where id=constant;
(
constant
表示一个常熟)
通常数据库不得不一行一行地扫描整个
test1
表来寻找所有匹配的数据。如果,最终得到的数据只占
test1
中很小的一部分,那么使用如上的方法显然很低效。如果我们在
id
列上维护一个索引用于定位匹配的行,那么数据库将只需通过检索搜查树上的若干层就可以得到所需的结果。
下面的命令将在
id
上建立索引:
Create index test1_id_index on test1(id);
当然,我们可以自由地给索引取一个名字,只要之后可以方便地想起来就行。
删除索引地命令:
Drop index test1_id_index;
很大程度上,一个有效的索引是建立在事前分析的基础上的,一旦建立了一个索引,就不需要再有太多地干预了,不过也可以定期使用
analyze
命令来更新统计数据,使数据库查询规划器能做出有效的判断。
除了需要避免不必要的索引对系统带来过多的负荷,索引无论使对查找、删除还是更新,都将带来一定程度上性能的提升。
索引的类型
关键字:操作符集
一定类型的索引只能在相关操作符集中触发
Postgresql8.1
提供
b-tree
,
r-tree
,
hash
,
gist
这几种索引类型。每一种索引,因为所使用的算法不同,所以适用的场合也有所不同。缺省的情况下,系统将使用
b-tree
索引,它也可以适用于大多数的情况。
b-tree
可以处理按照某种顺序排列的数据的等值或范围查询,当索引所在列使用一下几种比较符号时,查询规划器将考虑使用索引。
>
,
<
,
>=
,
<=
,
=
构造等效于这些符号的组合也将会启动索引,如
between
,
in
等。需要注意的使
is null
不等于
=
,所以不会启动索引。当匹配模式是一个常量并且常量位于开头处时,如:
col like ‘foo%’
、
col ~ ‘^foo’
,也将启动索引。
r-tree
适用与处理二维空间的数据时,要创建一个
r-tree
索引可以使用如下命令:
create indes INDEX_NAME on TABLE_NAME using rtree (COLUMN_NAME);
当一个索引了的列涉及到使用下列操作符之一进行比较的时候,
PostgreSQL
的查询规划器都会考虑使用
R-tree
索引∶
<<
,
&<
,
&>
,
>>
,
<<|
,
&<|
,
|>&
,
|>>
,
~
,
@
,
~=
,
&&
散列(
hash
)索引只能处理简单的等值查询。就目前而言,由于
hash
索引的性能不比
b-tree
的好,再加上它的尺寸较大、制作时间较长,并且不支持
wal
日志,所以不建议使用。
Gist
不是一种单独的索引类型,而是一种架构,可以在这种架构上实现不同的索引策略。根据策略的不同,可以使用使用
gist
索引的操作符也不尽相同。需要注意的是,
gist
上有实现
r-tree
的一组操作符,而且支持
wal
日志,因此比
r-tree
更加适用。
多字段日志
关键字:前导字段
索引的前导字段对查询性能起着至关重要的作用
一个索引可以定义在表中的多个字段上。比如,你有如下的表:
Create table test2(
Major int,
Minor int,
Name varchar
);
并且经常使用如下的查询语句:
Select * from test2 where major=X and minor=Y;
那么在
major
和
mino
上定义一个索引会比较合适,
Create index multi_index on test2(major,minor);
目前只有
b-tree
和
gist
支持多字段索引,缺省只有
32
个字段。可以在编译
postgresql
之前,通过修改
pg_config_manual.h
中的相关参数来改变。
只要在查询条件中包含了多字段索引的子集或者全部,那么就有可能触发索引,但是只有在查询条件中使用索引的最前端字段(前导字段)时,索引才最为有效。准确的规则是在索引的前导字段中使用等值约束,再在第二个字段上加上一个非等值查询,这样将限制需要扫描索引的范围。在这两个字段之后的条件将在索引中查询,这样就减少了对表的操作,但这不会减少需要扫描的索引。
比如:有一个建立在
(a,b,c)
上的索引,有一个查询的条件:
a=5 and b>44 and c<=77
。索引将从符合
a=5 and b=44
条件的第一个条目开始扫描,直到所有符合
a=5
条件的条目。此时,将跳过对
c>=77
的条目的扫描,但之后还是会扫描的。
这个索引也可以用在没有对
a
进行约束的查询上,但这势必会大大降低性能,因为这将扫描整个索引。
只有当前导字段出现在查询条件中时,才会启用
gist
索引。附加的条件将限制返回的索引条目,但前导字段还是起了决定性的作用,它决定需要扫描字段的数量。如果前导索引相应的字段中有较多的相同值,那么即使在附加索引字段上添加查询条件,查询的整体性能依然将不太理想。
必须很谨慎地使用多字段索引。在大多数情况下,单字段索引已足够了,而且还节省了生成时间和数据库空间。除非对某个表的查许非常固定,一般不要使用多余
3
个字段的索引。
多个索引的组合
关键字:
bitmap bitmap
保存按物理位置排序的表中行的入口
总结上面两节,单个索引只有适用于以下这种情况:
查询语句中使用了与索引相关的操作符,并且包含索引对应字段的子集字段的交集。举例说就是,如(
a
,
b
)的索引可以在如查询条件
where a=50 and b=70
的情况下使用,但如果查询条件时
where a=50 or b=70
就无法使用该索引了。
从
8.1
版本开始,
postgresql
就增加了组合多索引的功能来解决单个索引无法处理的情况。如
where x=44 or x=88 or x=43 or x=12
这样的查询,可以分解为
4
个单独的查询,
postgresql
将
4
个单独查询的结果用
or
运算组合在一起得到最终的结果。再来一个例子是,
where x=45 and y=46
,如果再
x
和
y
上各有一个索引,那么
postgresql
将分别在这两个索引上扫描,然后将得到的进行与运算,得到最终的结果。
为了合并多个索引,
postgresql
扫描需要的索引,将符合索引条件的表中行标识在内存中的
bitmap
中,
bitmap
进行与、或运算得到最终的结果。
Bitmap
中记载了按照磁盘顺序排序的表中行的物理位置(查询的结果将对行进行标识),这就意味着任何使用合并的查询将丢失在索引上的排序。如果有
order by
这样的排序语句,那么排序的行为将在查询的最后进行,这就降低了性能。所以,有时候规划器会忽略多索引的存在,而只使用单索引。
在大多数最简单的应用中,可能会有多种索引的组合可供使用,数据库的开发员必须作出合理的选择以优化数据库的性能。有时候,使用多字段索引会很有效,但有时候建立单字段索引再将它们组合起来会更灵活。例如在一表上有对
x
、以、
x
和
y
的三种查询。可以分别在
x
、
y
上建立单独的索引,利用索引组合可以实现对三种查询的优化。也可以使用一个(
x
,
y
)的多字段索引,单它对
y
的查询无能为力。综合两种方法,可以建立一个(
x
,
y
)多字段索引,再加上一个建立在
y
字段上的索引,唯一的缺点是在
x
上的查询性能将弱于建立在
x
上的单独索引。最后一种选择是,建立三种查询的各个索引,只是这会降低更新数据库的性能,因此只能用在查询远多于更新,且三种查询都很普遍的情况下。如果其中有一个字段的索查询量远大于其它的字段及其组合,那么建立单独的两个索引会比较合适。
唯一索引
关键字:实现唯一性的细节
索引可以强制其所对应字段或字段集合的唯一性。
Create unique index NAME on TABLE (COLUMN…)
;
目前,只有
b-tree
支持唯一索引。一旦索引被声明为唯一,那么所对应的字段(集合)就不允许存在重复的值。
Postgresql
在用户为一字段声明唯一或主约束时,自动生成一个唯一索引。
事实上,应当只将唯一索引当作一种实现唯一性的细节,一般情况下使用如下:
Alter table …add constraint…
来实现唯一性约束,而不直接创建唯一索引。
建立在表达式上的索引
关键字:计算值
索引中存储计算值,与单索引的查询速度相同
索引不仅可以建立在对印表的某些字段上,还可以建立在以某些字段的值为参数的函数和表达式上。这个特性对于优化以计算值为查询条件的搜索很有效。
例如,查询一个与大小写无关的比较
:
Select * from test1 wherelower(clo1)=’value’;
相应的,可以建立如下的索引:
Create index test_lower_col1_idx on test1(lower(col1));
如果我们将这个索引声明为唯一索引,那么就可以实现与大小写无关的唯一性约束了。
另一个例子是,如果经常使用如下的查询:
Select * from people where ( first_name||’’||last_name)=’jone smith’;
可以建立如下的索引:
Create index full_name_idx on people((first_name || ‘’ || lastname));
在
create index
语法中,除了单个函数外,对应的字段表达式都要有圆括号包起来。
维护表达式索引的代价相对较大,因为索引中存储的是真正的计算值而不是在查询中计算,虽然这大大提升了相关的查询速度(与单字段索引的速度相同),但每次执行更新或者插入都需要计算出相应的值。所以,这种索引只用在追求查询速度,而对插入和更新不太计较的情况下。
部分索引
关键字:索引条件
查询只有在匹配索引条件时才可能被触发
部分索引是建立在表的子集上的索引,该子集有索引中的条件表达式决定。只有符合表达式条件的表的行才会被建立索引。部分索引是一种特殊的特征,但在一些特定的场合中会很有用。
建立部分索引的主要理由是避免对普便的数据建立索引。因为一个重复的普遍数据可能在表中占据较可观的比例,在其上建立索引不会起到优化性能的结果,事实上规划器甚至不会启动索引。建立部分索引,不但减小了索引的大小,从而提高了查询的速度,而且加速了更新的速度。
假设,在数据库中存储了网络服务器的登陆记录。大多数的记录显示来自于内网的访问。如果我们需要检索的仅仅是来自外网的记录,那么就不必在整张表上建立索引,只需建立部分索引。
Create table access_log(
url varchar,
client_ip inet,
…
);
可建立如下的部分索引:
Create index access_log_client_ip_idx 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.htm’ and client_ip = ‘212.78.10.32’::inet;
一个不能使用该索引的例子:
Select * from access_log where client_ip = inet ‘192.168.100.23’;
注意到像这样的部分索引需要可预计的普遍数值。如果数值的分布因为应用程序的性质而是固定的,或者是不随时间变化的静态值,那么建立这样的部分索引并不困难。但如果普遍数据来自于不固定的大量一致性数据(拥有相同的索引对应字段的值)装载,那么就需要管理员不断地修改索引。
另一个部分索引地应用是剔除某些典型查询不感兴趣的数据。在能收到于以上情况相同的效果的同时,它还避免进入不干兴趣的数据,即使索引对访问这些不感兴趣的数据有好处。建立这种索引必须很小心,应该事先做大量的试验。
例子:
如果有这么一个表,包含了已付款和未付款的订单,未付款的订单只占总数据很小的一部分,但却被很频繁地访问,为优化性能,可以在该表上建立为付款订单的部分索引。索引如下:
Create index orders_unbilled_index on orders(order_nr)
Where billed is not true;
一个可能启动该索引的查询是:
Select * from orders where billed is not true and order<10000;
然而,该索引还可以被不涉及到
order_nr
的查询触发:
Select * from orders where billed is not true and amount>500.00;
该查询显然不会比直接建立在
amount
上的索引高效,因为这相当于在检索一个表,但如果
unbilled
的数据量较少的话,它还是一个不错的选择。
这样的查询将不会使用以上的索引:
Select * from orders where order_nr=3501;
这个例子说明了索引字段不必于条件中涉及的字段相同。
Postgresql
支持任意条件字段的部分索引,只要这些字段出现在表中。要记住的一点是,只有在索引中的条件符合查询条件时,部分索引才会触发。更准确地说,只有在查询条件中,以数学上地意义包含了部分索引的条件,该索引才会起效。至今,
postgresql
还没有一套复杂的理论来保证系统可以识别形式不同、但却有相同意义的数学表达式。(这样功能的实现过于复杂,而且在真正应用中会很慢。)系统可以自动识别简单的不等式包含,比如:“
x<1
”包含在“
x<2
”中,除此之外必须提供精确匹配索引条件的查询条件。
第三种情况不需要部分索引被查询使用,主要用于在字段的部分数据上建立唯一约束。
Create table tests(
Subject text,
target text,
success Boolean,
…..
)
Create unique index tests_success_constraint on tests (subject, target) where success;
最后,部分索引可以重载系统的查询规划选择。这发生在因为数据分布较为特殊,而使系统在不该使用索引的时候使用了索引。这时可以建立部分索引使得它在此类查询中不可用。
需要注意的是,建立部分索引意味着索引建立者必须知道查询,特别是要知道索引在什么情况下是有用的。要完全掌握需要丰富的经验,并且要理解
postgresql
索引的工作原理。大多数情况下,部分索引不比一个普通的索引更显优势。