表空间
表空间的含义是为不同的数据库、表或索引指定的不同的存储目录。而我们知道,在Linux上,不同的目录可以挂载不同的存储介质,比如/data目录挂载的是机械硬盘,而/datafile目录挂载的是固态硬盘,那么这两个目录里的数据读取速度自然就有区别。可以通过这种方法将频繁读取的表放在性能较好的硬盘上,可以显著提高数据库的性能。
表空间的定义
创建表空间的语法格式是:
CREATE TABLESPACE tablespace_name [ OWNER user_name ] LOCATION 'directory'
示例如下:
在Linux环境下,如果要指定某个目录为PostgreSQL里的某张表或者数据库的表空间,那么数据库的启动用户必须对这个目录有读写权限。而一般PostgreSQL的启动用户是postgres,即需要将这个目录配置为postgres用户具有可读写权限。
先创建目录不设置权限,然后尝试创建表空间,命令如下:
[root@cephadmin ~]# mkdir /datafile2
[root@cephadmin ~]# su - postgres
Last login: Fri Feb 2 16:17:23 CST 2018 on pts/1
-bash-4.2$ psql
Password:
psql (9.6.6)
Type "help" for help.
postgres=# create tablespace tp_triggerdb location '/datafile2';
ERROR: could not set permissions on directory "/datafile2": Operation not permitted
从上面的示例中可以看到,没有给目录设置正确的权限的时候,会提示操作不被允许。我们修改目录权限后,再尝试创建表空间。
postgres=# \q
-bash-4.2$ exit
logout
[root@cephadmin ~]# chown postgres:postgres /datafile2
[root@cephadmin ~]# su - postgres
Last login: Fri Feb 2 16:23:36 CST 2018 on pts/1
psq-bash-4.2$ psql
Password:
psql (9.6.6)
Type "help" for help.
postgres=# create tablespace tp_triggerdb location '/datafile2';
CREATE TABLESPACE
这次可以看到,创建表空间成功。创建成功以后,我们来看下/datafile2目录,如下所示:
[root@cephadmin ~]# ls /datafile2/
PG_9.6_201608131
[root@cephadmin ~]# ls /datafile2/PG_9.6_201608131/
可以看到在这个目录下生成了一个新的子目录,名称是以PG开头,中间是PostgreSQL版本号,最后是今天的日期。而这个子目录下没有任何文件,说明这个目录就是表空间目录。
数据库设置表空间、修改表空间
我们在刚才的表空间tp_triggerdb上创建一个数据库,命令如下:
postgres=# create database db01 tablespace tp_triggerdb;
CREATE DATABASE
可以看到创建成功。此时我们再看刚才的目录,如下所示:
[root@cephadmin ~]# ls /datafile2/PG_9.6_201608131/
18275
[root@cephadmin ~]# ls /datafile2/PG_9.6_201608131/18275/
112 13177 2602 2615_fsm 2668 2757 3256 3599
113 13177_fsm 2602_fsm 2615_vm 2669 2830 3256_vm 3600
1247 13177_vm 2602_vm 2616 2670 2830_vm 3257 3600_fsm
1247_fsm 13179 2603 2616_fsm 2673 2831 3258 3600_vm
1247_vm 13181 2603_fsm 2616_vm 2674 2832 3394 3601
1249 13182 2603_vm 2617 2675 2832_vm 3394_fsm 3601_fsm
......
可以看到,确实已经写入了很多数据,说明数据确实被写入了这个表空间里。
我们再创建一个数据库,使用默认设置,然后再修改这个数据库的表空间为上面创建的tp_triggerdb,命令如下:
postgres=# create database db02;
CREATE DATABASE
postgres=# alter database db02 set tablespace tp_triggerdb ;
ALTER DATABASE
然后我们再看对应的表空间目录/datafile2,结果如下:
ls /datafile2/PG_9.6_201608131/
18275 18276
这就说明,在表空间目录底下,也是以目录形式保存数据库中的数据。
表设置表空间、修改表空间
在PostgreSQL 9.5及9.6中,当表和数据库的表空间一致时,使用\d table_name
命令查看表时,不会显示出表的表空间信息,只有当表的表空间信息和数据库的不一致时,才会显示出来。
如下面代码所示,我们刚创建的测试数据库db01对应的表空间是tp_triggerdb,然后我们创建一张新表tb01,我们查看一下它的表空间信息:
postgres=# create table tb01(id int, name text);
CREATE TABLE
postgres=# \d tb01;
Table "test.tb01"
Column | Type | Modifiers
--------+---------+-----------
id | integer |
name | text |
没有看到任何表空间信息,说明和数据库的表空间一致,我们将其表空间修改为pg_default,即PostgreSQL默认的表空间,然后再看。
修改表空间的命令格式是:
alter table table_name set tablespace tp_name
db01=# alter table public.tb01 set tablespace pg_default;
ALTER TABLE
db01=# \d public.tb01
Table "public.tb01"
Column | Type | Modifiers
--------+---------+-----------
id | integer |
name | text |
Tablespace: "pg_default"
可以看到最后显示了修改后的表空间是pg_default,足以说明上面的结论。
我们再看,如果修改数据库的表空间,那么已有的表和新建的表,对应的表空间会是什么?
先新建一个目录/datafile3,然后以这个目录创建一个新的表空间tp_triggerdb3,命令如下:
# 创建目录
[root@master ~]# mkdir /datafile3
[root@master ~]# chown postgres:postgres /datafile3
# 修改数据库的表空间
postgres=# alter database db01 set tablespace tp_triggerdb3;
ALTER DATABASE
# 切换到数据库db01,查看表tb01
postgres=# \c db01
You are now connected to database "db01" as user "postgres".
db01=# \d public.tb01
Table "public.tb01"
Column | Type | Modifiers
--------+---------+-----------
id | integer |
name | text |
Tablespace: "pg_default"
从上面可以看到,tb01的表空间不变,还是pg_default。我们再创建一张新表tb02,查看tb02的表空间。
db01=# create table public.tb02 (id int, info text);
CREATE TABLE
db01=# \d public.tb02
Table "public.tb02"
Column | Type | Modifiers
--------+---------+-----------
id | integer |
info | text |
从上面可以看到,没有显示出表空间信息,说明新表使用的是和数据库相同的表空间tp_triggerdb3,此外,在修改数据库表空间的时候,我们还遇到下面这样一个错误:
db01=# alter database db01 set tablespace tp_triggerdb3;
ERROR: cannot change the tablespace of the currently open database
提示我们,不能修改当前打开的数据库,说明修改数据库的表空间时,数据库上不能有用户连接,否则就报错。
如果想在创建表的时候,可以直接设置表的表空间,命令格式如下:
create table table_name (field1 type1, field2 type2 ...) tablespace tp_name;
我们创建一个新表tb03看看:
db01=# create table public.tb03 (id int, email text) tablespace tp_triggerdb;
CREATE TABLE
db01=# \d public.tb03;
Table "public.tb03"
Column | Type | Modifiers
--------+---------+-----------
id | integer |
email | text |
Tablespace: "tp_triggerdb"
这次我们故意将表tb03的表空间设置为tp_triggerdb,和数据库的不同,于是就能显示出tb03的表空间信息,从输出的结果来看,确实是这样。
最后还要注意的一点就是,在修改表的表空间时,会锁表,锁表的时间根据表的大小和转移表空间的速度决定的,因此进行表空间修改时一定要对可能的影响进行评估。
索引设置表空间、修改表空间
我们知道,索引的用途之一就是用来加速数据的查询,但是索引本身也是存储在表空间,占用文件存储的。因此,对索引设置合适的表空间,也可以加快索引的操作。为索引指定表空间的命令格式如下:
CREATE index idx_name on table_name(field1...) tablespace tp_name
示例如下:
db02=# create index idx_t01_id on test01(id) tablespace tp_triggerdb;
CREATE INDEX
db02=# \d test01;
Table "public.test01"
Column | Type | Modifiers
--------+---------+-----------
id | integer |
name | text |
Indexes:
"idx_t01_id" btree (id), tablespace "tp_triggerdb"
从上面示例中可以看到,当索引的表空间和表的表空间不一致时,会显示出索引的表空间信息。下面我们将索引的表空间修改为tp_triggerdb3,和表的一致,再看。
db02=# alter index idx_t01_id set tablespace tp_triggerdb3
db02-# ;
ALTER INDEX
db02=# \d test01;
Table "public.test01"
Column | Type | Modifiers
--------+---------+-----------
id | integer |
name | text |
Indexes:
"idx_t01_id" btree (id)
可以看到,此时不显示索引的表空间信息。说明索引的表空间属性和表的一致。
唯一索引的表空间设置
命令格式是:
ALTER table table_name ADD CONSTRAINT unique_tb_name unique(field1) USING INDEX TABLESPACE tp_name;
示例如下,将test01表的id字段设置唯一性约束:
ALTER TABLE test01 ADD CONSTRAINT unique_tb01_id unique(id) USING INDEX TABLESPACE tp_triggerdb3;
db02=# ALTER TABLE test01 ADD CONSTRAINT unique_tb01_id unique(id) USING INDEX TABLESPACE tp_triggerdb3;
ALTER TABLE
# 表的信息如下,可以看到新增的唯一性约束,因为索引表空间和表的表空间一致。
# 所以不会显示表空间信息
db02=# \d test01;
Table "public.test01"
Column | Type | Modifiers
--------+---------+-----------
id | integer |
name | text |
Indexes:
"unique_tb01_id" UNIQUE CONSTRAINT, btree (id)
"idx_t01_id" btree (id)
主键索引的表空间设置
示例如下,为test01表的id字段添加主键索引,和添加唯一性约束命令类似:
ALTER TABLE test01 ADD CONSTRAINT pk_tb01_id primary key(id) USING INDEX TABLESPACE tp_triggerdb;
# 这次设置索引的表空间是tp_triggerdb,和表的表空间不同,再看显示的结果
db02=# ALTER TABLE test01 ADD CONSTRAINT pk_tb01_id primary key(id) USING INDEX TABLESPACE tp_triggerdb;
ALTER TABLE
db02=# \d test01;
Table "public.test01"
Column | Type | Modifiers
--------+---------+-----------
id | integer | not null
name | text |
Indexes:
"pk_tb01_id" PRIMARY KEY, btree (id), tablespace "tp_triggerdb"
"unique_tb01_id" UNIQUE CONSTRAINT, btree (id)
"idx_t01_id" btree (id)
从上面可以看到pk_tb01_id这个主键索引的信息,同时还看到显示出了表空间的信息。
索引
索引的作用是,将设置了索引的字段对应的值按照一定的规则进行排序,然后对这个字段的数据查找时,通过索引,可以加快查找的顺序。就像我们平时看书时根据书前面的目录可以快速找到对应的章节内容。但是索引需要占用额外的硬盘空间,在表非常大的时候,索引占用的空间也会比较大。同时,对有索引的字段更新数据时,如果没有特别设置,也会同时更新索引,会导致耗费较长的时间。
索引的分类
PostgreSQL支持的索引有以下几类:
- B-tree:最常用的索引,适用于等值和范围查询。支持的操作符有<,<=,=,=>,>,BETWEEN,IN,IS NULL,IS NOT NULL。还有正则匹配有时候也是使用B-tree索引。
- Hash:只能处理最简单的等值查询,在使用=号操作符的时候,数据库查询计划器可能会考虑使用Hash索引。
- GiST:GiST索引不是一累简单的索引,而是一种多种不同索引策略组合的框架。GiST索引可以使用的操作符依赖于索引策略(操作符类)。标准的PostgreSQL发行版包含支持二位地图数据类型的操作符类。支持的操作符包括: <<,&<,&>,>>,<<|,>>|,&<|,|&>,|>>,@>,<@,~=,&&,另外支持的很多操作符在contrib插件里,需要自己安装。GiST索引还支持邻近搜索。例如查询一个点一定范围内的另外的点。
- SP-GiST:和GiST类型,提高支持多种搜索的框架,SP-GiST允许实现各种不同的非平衡的基于磁盘的数据结构,例如四叉树、k-d tree和radix trees。支持的二位点操作符包括:<<,>>,~=,<@,<,>。
- GIN:翻转索引,适用于有多个值的数据。例如属组。一个反转索引为每一个单独的值都有一个索引条目,并且能高效处理针对某些特定值是否存在的测试查询。想GiST和SP-GiST,GIN支持多种用户自定义的索引策略,并且在索引上使用特定的操作符依赖于不同的索引策略。例如标准版本的PostgreSQL里针对一维数组的GIN操作符类,支持的索引查询操作符有:<@,@>,=,&&.
- BRIN:保存的是存储在连续物理块范围表里的数据值的摘要。支持多种自定义索引策略,在索引上使用特定的操作符依赖于不同的索引策略。对于具有线性排序顺序的数据类型,索引数据对应于每个块范围的列中值的最小值和最大值。 索引查询支持使用下面这些运算符:<,<=,=,>=,>。
每种索引都使用不同的算法来适用于不同类型的数据。默认情况下,create index使用的是B-tree索引。
创建索引
创建索引的命令格式是:
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 ]
看起来非常复杂,那我们先解释一下这条命令里的一些参数的作用:
- UNIQUE,创建唯一性索引,即被创建索引的字段里面的值都是唯一的,一般用于主键或者有约束性要求的字段
- CONCURRENTLY,配置这个参数时,数据库表可以进行插入、更新和删除操作。而默认情况下,建立索引时会锁表,只允许读取表内的值。这个选项使用的时候有一些限制。
- IF NOT EXISTS name,配置这个参数后,如果存在同名的索引时不要报错。只会给出一个提示,需要给出所以名称name。
- USING method,method即索引的类型,例如btree、hash,gist,spgist,gin和brin。默认是btree
- column_name,指的是要建索引的字段。
- expression,基于表一个或多个字段的表达式,写的时候需要用括号括起来。如果表达式是函数调用时,括号也可以省略。
- collation,用于索引的排序规则名称,一般都使用默认值。
- opclass, 操作符类的名称,指的是为索引添加的操作符类支持,一般使用默认值。
- ASC/DESC,指定索引的排序顺序,ASC是默认顺序,表示升序排列,DESC表示降序排列
- NULLS FIRST/LAST,FIRST指定null值排序在非null值前面当前面使用DESC的时候,这个是默认配置。LAST指定null值排在非null值后面,当前面使用ASC的时候是默认配置。
- storage_parameter,表示特定索引方法参数,一般不使用。
- tablespace_name,表空间,上面表空间里讲过。
- predicate 部分索引的约束表达式。
当然了,上面这么多参数,实际应用中全部用的机会很少。很多参数都是使用默认设置。我们了解这么多参数,只是知道它可以这么用,后面需要的时候直接查笔记就好了。我们常用的几种格式如下:
CREATE INDEX idx_name ON table_name(field1);
CREATE INDEX idx_name ON table_name(field1, field2....);
CREATE INDEX idx_name ON table_name(field1) with (storage_parameter=value);
CREATE UNIQUE INDEX u_idx_name ON table_name(filed3);
下面我们就可以根据这个命令来尝试着为表创建索引。
db02=# \d info
Table "public.info"
Column | Type | Modifiers
--------+---------+-----------
id | integer |
name | text |
phone | text |
db02=# create index idx_info_id on info(id);
CREATE INDEX
db02=# \d info
Table "public.info"
Column | Type | Modifiers
--------+---------+-----------
id | integer |
name | text |
phone | text |
Indexes:
"idx_info_id" btree (id)
上面就是一个简单的示例,给info表的id字段创建一个普通的B-Tree索引。下面我们再来看一个创建双字段索引的示例,比如给name和phone字段创建一个组合索引:
db02=# create index idx_info_name_phone on info(name,phone);
CREATE INDEX
db02=# \d info
Table "public.info"
Column | Type | Modifiers
--------+---------+-----------
id | integer |
name | text |
phone | text |
Indexes:
"idx_info_id" btree (id)
"idx_info_name_phone" btree (name, phone)
和创建单个字段的索引一样,都非常简单。最后看一下创建唯一性约束索引,这种类型的索引稍有特殊,我们一起来看看:
# 先看下表info2的内容如下,可以看到里面有两个相同的id=2,那么我们来尝试创建唯一性约束索引
db02=# select * from info2;
id | name | phone
----+------+---------
1 | Alis | 8788234
2 | Bob | 8788347
2 | Tim | 8788485
4 | Tom | 8767234
(4 rows)
db02=# create unique index idx_uq_info2_id on info2(id);
ERROR: could not create unique index "idx_uq_info2_id"
DETAIL: Key (id)=(2) is duplicated.
从上面可以看到,会直接报错。因此在创建唯一性索引的时候要保证你的表中对应的字段里面没有重复的值。我们删除对应的字段,然后再尝试创建,会发现这个时候就可以了,如下所示:
db02=# delete from info2 where name='Bob';
DELETE 1
db02=# select * from info2;
id | name | phone
----+------+---------
1 | Alis | 8788234
2 | Tim | 8788485
4 | Tom | 8767234
(3 rows)
db02=# create unique index idx_uq_info2_id on info2(id);
CREATE INDEX
db02=# \d info2;
Table "public.info2"
Column | Type | Modifiers
--------+---------+-----------
id | integer |
name | text |
phone | text |
Indexes:
"idx_uq_info2_id" UNIQUE, btree (id)
修改索引
索引创建完毕后,我们来继续看怎么修改索引,毕竟索引是要随着数据库的使用而进行调整的。修改索引的命令基本格式是:
ALTER INDEX idx_name OWNER TO db_user/db_role;
ALTER INDEX idx_name RENAME TO idx_name2;
ALTER INDEX idx_name SET (idx_property = value);
ALTER INDEX idx_name SET tablespace = value;
ALTER INDEX idx_name RESET (idx_property = value2);
第一条命令用来修改索引的所有者或角色;
第二条命令用来修改索引的名称;
第三条命令用来设置索引的某个属性值;
第四条命令用来设置索引的表空间;
第五条命令用来重新设置索引的某个属性值;
其中的属性值这里不过多介绍,后面用到的时候再讲。我们先看其他的三个命令示例:
首先看第一个例子,修改索引的所有者,默认操作用户是postgres
db02=# \d test01;
Table "public.test01"
Column | Type | Modifiers
--------+---------+-----------
id | integer | not null
name | text |
Indexes:
"pk_tb01_id" PRIMARY KEY, btree (id), tablespace "tp_triggerdb"
"unique_tb01_id" UNIQUE CONSTRAINT, btree (id)
"idx_t01_id" btree (id)
db02=# alter index unique_tb01_id owner to osdba;
WARNING: cannot change owner of index "unique_tb01_id"
HINT: Change the ownership of the index's table, instead.
ALTER INDEX
从上面可以看到,它会提示你修改了索引,但是会警告你,不能修改索引的所有者,因为这会导致索引和表的所有者不一致,进而导致其他权限问题。同时会提示你修改索引所在的表的权限来代替,保证表和索引的权限一致。
再看修改索引的名称,还是修改上面这个索引,代码如下:
db02=# alter index unique_tb01_id rename to idx_uq_tb01_id;
ALTER INDEX
db02=# \di
List of relations
Schema | Name | Type | Owner | Table
--------+-----------------+-------+----------+--------
public | idx_t01_id | index | postgres | test01
public | idx_uq_info2_id | index | postgres | info2
public | idx_uq_tb01_id | index | postgres | test01
public | pk_tb01_id | index | postgres | test01
(4 rows)
可以看到,这个命令也非常简单。修改索引表空间的内在上面表空间里也讲过,因此这里就不多说了。
删除索引
最后来看一下删除索引,上面看到索引是分为两种,一种是普通的索引,另外一种是唯一性索引。其删除命令格式都是:
drop index idx_name
示例代码如下:
db02=# drop index idx_info_id ;
DROP INDEX
db02=# \d info
Table "public.info"
Column | Type | Modifiers
--------+---------+-----------
id | integer |
name | text |
phone | text |
#先给id字段创建唯一索引
db02=# create unique index idx_uq_id on info(id);
CREATE INDEX
db02=# \d info
Table "public.info"
Column | Type | Modifiers
--------+---------+-----------
id | integer |
name | text |
phone | text |
Indexes:
"idx_uq_id" UNIQUE, btree (id)
#删除唯一性索引
db02=# drop index idx_uq_id ;
DROP INDEX
删除成功后就会返回一个DROP INDEX的提示。删除索引的内容就上面这些。
唯一索引和唯一约束索引的区别
在PostgreSQL中,存在唯一索引,还有一个唯一性约束索引,目前还没看出它们两者在使用时的区别,但是在创建和删除的时候稍有区别,首先看创建唯一索引:
db02=# create unique index idx_uq_id on info(id);
CREATE INDEX
db02=# \d info
Table "public.info"
Column | Type | Modifiers
--------+---------+-----------
id | integer |
name | text |
phone | text |
Indexes:
"idx_uq_id" UNIQUE, btree (id)
再看创建唯一性约束索引:
db02=# alter table info add constraint unique_info_id unique(id) USING INDEX tablespace pg_default;
ALTER TABLE
db02=# \d info;
Table "public.info"
Column | Type | Modifiers
--------+---------+-----------
id | integer |
name | text |
phone | text |
Indexes:
"idx_uq_id" UNIQUE, btree (id)
"unique_info_id" UNIQUE CONSTRAINT, btree (id), tablespace "pg_default"
可以看到创建以后,他俩显示出来的信息也稍有区别,同时创建的时候,唯一性约束索引必须带上表空间的信息。
然后再看删除,首先是删除唯一索引:
db02=# drop index idx_uq_id ;
DROP INDEX
可以看到,很简单就删除了。然后再看唯一性约束索引,如下所示:
db02=# drop index unique_info_id ;
ERROR: cannot drop index unique_info_id because constraint unique_info_id on table info requires it
HINT: You can drop constraint unique_info_id on table info instead.
可以看到直接使用删除索引的命令是不能删除的,会提示你这个表依赖于这个唯一性约束索引。同时提示你,可以用删除约束的方式来删除这个索引,我们来尝试一下:
db02=# alter table info drop constraint unique_info_id;
ALTER TABLE
db02=# \d info
Table "public.info"
Column | Type | Modifiers
--------+---------+-----------
id | integer |
name | text |
phone | text |
可以看到,使用删除约束的方式能够直接删除。从这一点上来看,它俩的侧重点,可能是前者更偏向于索引,而后者更偏向于约束。个人的观点,如果大家觉得写错了,可以在下面留言一起探讨。
到这里,表空间和索引的内容就讲解完毕了,下一章我们一起来看权限相关的内容。