1. 安装

1.1. docker

docker-compose.yml
version: '3'
services:
    postgresql:
        image: postgres:alpine
        container_name: postgresql
        volumes:
            - ~/volumes/postgresql/:/var/lib/postgresql/data/
        environment:
            - POSTGRES_USER=jy
            - POSTGRES_PASSWORD=123456
            - TZ=Asia/Shanghai
        network_mode: host

1.2. 安装包安装

groupadd postgres
useradd postgres -g postgres
passwd postgres
mkdir -p /database/pg/pg_root
chown -R postgres:postgres /database/pg

sudo apt install postgresql-all
initdb -D /database/pg/pg_root -E UTF8 --locale=C -U postgres -W

2. 基本命令

# 创建db
createdb -U <username> <dbName>
# 删除b
dropdb -U <username> <dbName>
# 重建索引
reindexdb -d <dbName>
# 对数据库物理文件垃圾回收
vacuumdb <dbName>
# 清除数据库中未引用的大对象
vacuumlo <dbName>

pgdump
pgrestore
pgbench
# 登录数据库
psql [-h HOST] [-p PORT] DB [USERNAME]
# 获取pg配置
pg_config
# 获取数据库状态
pg_ctl -D <data-dir> status
# 启动数据库
postgres -D <data-dir> &
# 关闭数据库
pg_ctl -D <data-dir> -m [fast|smart|immediate]  -t <timeout> stop

3. 配置

3.1. pg_hba.conf

TYPE  DATABASE  USER  ADDRESS  METHOD
  • TYPE: 允许连接的方式

    • local: Unix domain socket

    • host: TCP/IP, localhost default

    • hostssl: OpenSSL restrict

    • hostnossl: SSL not permitted

  • METHOD: 认证方法

    • trust

    • password: 明文密码

    • md5

    • reject: 拒绝访问

    • scram-sha-256

3.2. postgresql.conf

postgresql 启动时postgresql.auto.conf会覆盖postgresql.conf内容
更改配置生效: pg_ctl -D <data-dir> reload

4. psql命令

  • psql -c "SQL" [-d DB_NAME] [-U USERNAME] [-W PASSWORD] [-f SQL_FILE]

  • \db: 查看表空间

  • \l: 查看数据库

  • \d <DB_NAME>: 查看表定义

  • \dt+ <DB_NAME>: 查看表空间大小

  • \di+ <IDX_NAME>: 查看索引空间大小

  • \x: 切换查询显示模式

  • COPY <DB> FROM|TO "FILE_PATH" : (大表)导入导出数据(必须有superuser权限)

  • \copy <DB> FROM|TO "FILE_PATH" : (小表)导入导出数据

  • \set VAR_NAME VALUE: 设置变量, :VAR_NAME 使用

  • \timing: 开启sql计时

5. 数据类型

5.1. 数字类型

  • int2 int4 int8

  • decimal/numeric[(precision,scale)]

  • real 6位十进制精度浮点数

  • double precision 15位十进制精度浮点数

  • smallserial/serial/bigserial 2/4/8字节自增序列

5.2. 字符串类型

  • varchar/character varying 变长

  • character/char 定长

  • text 变长, 长度小于1GB

5.3. 日期类型

  • timestamp[without time zone] 不带时区的时间戳

  • timestamp[with time zone] / timestamptz 带时区的时间戳

  • date 日期

  • time[with[out] time zone] 一天的时间

  • interval 时间间隔

5.4. range类型

  • int4range

  • int8range

  • numrange

  • tsrange

  • tstzrange

  • daterange

5.5. 其他

  • boolean

  • cidr/inet/macaddr/macaddr8

  • 数组

  • json/jsonb

json和jsonb的区别
  • json以文本格式存储, jsonb以二进制存储.

  • json输入和输出的键顺序保持一致, jsonb不保证.

  • json会保留输入中的空格, jsonb不会.

  • jsonb会删除重复的键, 只保留最后一个.

6. 函数

-- 计算字符串中的字符数
select char_length('abcd'); -- 4
-- 计算字符串占用的字节数
select octet_length('abcd'); -- 4
-- 获取字符在字符串中的位置, 位置从1开始
select position('bc' in 'abcd'); -- 2
-- 提取字符串中的子串
select substring('abcd' from 2 for 3); -- bcd
-- 分割字符串
select split_part('abc,def,ghi', ',', 2); -- def

-- 时间字段提取
select extract(year from '2019-07-15:12:34:56'::timestamp); -- 2019

-- 数组两种形式
select array[1,2,3];
select '{1,2,3}';
-- 获取数组指定下标元素, 位置从1开始
select arr[1] from (select array[1,2,3] arr) a; -- 1
-- 数组追加元素
select array_append(array[1,2,3],4); -- {1,2,3,4}
select array[1,2,3]||4;
select array[1,2,3] || array[1,2,3]; -- {1,2,3,1,2,3}
-- 数组删除元素
select array_remove(array[1,2,2,2,3],2); -- {1,3}
-- 判断数组是否相等
select array[1,2,3] = array [1,2,2,2,3]; -- false
-- 判断数组是否不相等
select array[1,2,3] <> array [1,2,2,2,3]; -- true
-- 比较数组
select array[1,2,3] <= array[2,1]; -- true
select array[1,2,3] >= array[2,1]; -- false
-- 判断数组包含关系
select array[1,2,3] @> array[1]; -- true
select array[1,2,3] <@ array[1,2,3,4,5]; -- true
-- 判断数组是否有公共元素
select array[1,2,3] && array[22]; -- false
-- 获取数组维度
select array_dims(array[[4],[3],[2],[1]]); -- [1:4] [1:1]
-- 获取数组指定维度的长度
select array_length(array[1,2,3,4],1); -- 3
select array_length(array[[4],[3],[2],[1]],2); -- 1
-- 获取数组某一个元素第一次出现的位置, 位置从1开始
select array_position(array[1,2,3],3); -- 3
-- 替换数组指定元素
select array_replace(array[1,2,3],2,11); -- {1,11,3}
-- 数组转为字符串
select array_to_string(array[1,2,3,null],',','99'); -- 1,2,3

-- 范围
select int4range(1,10,'[]'); -- [1,11)
select daterange('2019-06-01','2019-07-02'); -- [2019-06-01,2019-07-02)
-- 获取范围下界
select lower(int4range(1,10));
-- 获取范围上界
select upper(int4range(1,10));
-- 判断范围是否为空
select isempty(int4range(1,10));

-- json表示
select '{"a":1}'::json;
-- json字段值获取
select j -> 'a' from ( select '{"a":1}'::json j) sub; -- 1
-- 提取json中的键值对
select * from json_each('{"a":1,"b":2}'::json); -- a 1 b 2
select * from json_each_text('{"a":"aaa","b":2}'::json); -- a 1 b 2
-- 删除jsonb中的key
select '{"a":1,"b":2}'::jsonb - 'a'; -- {"b":2}
-- 判断key是否为顶层key
select '{"a":1,"b":2, "c":{"d":4}}'::jsonb ? 'd'; -- false
-- 获取json所有key
select json_object_keys('{"a":1,"b":2}'); -- a b
-- 删除json指定key
select '{"a":1,"b":2, "c":{"d":4}}'::jsonb - 'a'; -- {"b": 2, "c": {"d": 4}}
-- 删除json嵌套key
select '{"a":1,"b":2, "c":{"d":4}}'::jsonb #- '{c,d}'::text[];

7. 高级特性

  • with从句

  • 批量插入: insert into select from / insert into values (),() / COPY

  • upsert: insert into …​ on conflict do {NOTHING | update set <colName> = EXCLUDED.colName}

  • insert/update/delete .. returning *

  • select from <table> TABLESAMPLE {SYSTEM | BERNOULLI}

  • string_agg() / array_agg()

  • 窗口函数

    • row_number() : select row_number() OVER partition by <colName>,eg: 1,2,3, 1

    • rank() : 分组重复则序号相同, 但下一个分组内不同行的序号保持增长,eg: 1,1,3

    • dense_rank() : 分组重复则序号相同, 下一个分组内不同行的序号继续增长,eg: 1,1,2

    • lag(field,offset,defaultValue): 获取行偏移offset那行某个字段的数据(offset为正向上偏移,为负则相反)

    • first_value(field): 取分组第一行数据

    • last_value(field): 取分组最后一行数据

    • nth_value(field,line): 取分组指定行数据

    • 别名: select …​[rank() over NAME] from <table> WINDOW <NAME> AS ()

8. Monitor

8.1. pg_stat_activity

查看实时连接.

Table 1. pg_stat_activity schema
列名 描述

datid

数据库id

datname

数据库名称

pid

服务于这个连接的进程id

usesysid

连接的用户id

usename

连接的用户名

application_name

应用名称

client_addr

客户端ip

client_hostname

客户端主机名

client_port

客户端端口号

backend_start

连接何时被启动

xact_start

事务何时被启动

query_start

连接查询起始时间

state_change

state列修改时间

wait_event_type

wait_event

state

连接状态: * active: 后台进程正在执行该SQL. * idle: 后台进程处于空闲状态, 等待后续客户端发出命令. * idle in transaction: 后台进程正在事务中. * idle in transaction(aborted): 事务中的部分SQL异常. * fastpath function call: 正在执行fast-path函数.

query

当前或上一次的sql语句

backend_xid

backend_xmin

backend_type

-- 查看活动会话
select pid, client_addr, query_start, state, query, wait_event, wait_event_type
from pg_stat_activity
where datid is not null
  and pid <> pg_backend_pid()
order by query_start desc;

-- 查看数据库连接数
select datname, client_addr, count(*)
from pg_stat_activity
where pid <> pg_backend_pid()
group by datname, client_addr
order by 1, 2, 3 desc;

-- 查看会话状态统计
select datname,
       count(*)                                                AS open,
       count(*) filter ( where state = 'active' )              AS active,
       count(*) filter ( where state = 'idle' )                AS idle,
       count(*) filter ( where state = 'idle in transaction' ) AS idle_in_tx
from pg_stat_activity
where backend_type = 'client backend'
group by rollup (1);

-- 查看当前事务
select pid, xact_start, now() - xact_start AS duration
from pg_stat_activity
where state like '%transaction%'
order by 3 desc;

8.2. pg_stat_database

查看数据库统计信息.

Table 2. pg_stat_database schema
列名 描述

datid

数据库id

datname

数据库名

numbackends

当前打开的数据库连接数

xact_commit

倾向于事务提交

xact_rollback

倾向于事务回滚

blks_read

缓冲未命中数

blks_hit

缓冲命中数

tup_returned

返回行数

tup_fetched

查询行数

tup_inserted

插入行数

tup_updated

更新行数

tup_deleted

删除行数

conflicts

temp_files

磁盘临时文件数

temp_bytes

磁盘临时文件大小

deadlocks

死锁次数

blk_read_time

IO读操作花费时间

blk_write_time

IO写操作花费时间

blk_read_time/blk_write_time默认为空, 需要打开 track_io_time 参数, 可以使用 pg_test_timing 命令测试计时性能.

8.3. pg_stat_user_tables

显示各个表的活动.

Table 3. pg_stat_user_tables schema
列名 描述

relid

表id

schemaname

schema名

relname

表名

seq_scan

顺序扫描次数

seq_tup_read

顺序扫描时元组读取个数

idx_scan

索引使用次数

idx_tup_fetch

索引元组返回个数

检测哪些表可能需要索引
select schemaname, relname, seq_scan, seq_tup_read, seq_tup_read / seq_scan as avg, idx_scan
from pg_stat_user_tables
where seq_scan > 0
order by seq_tup_read desc
limit 25;

8.4. pg_stat_statements

查看耗時最多的sql
select round((100 * total_time / sum(total_time) over ())::numeric, 2) AS percent,
       round(total_time::numeric, 2)                                   AS total,
       calls,
       round(mean_time::numeric, 2)                                    AS mean,
       query
from pg_stat_statements
order by total_time desc
limit 10;

9. 体系结构

9.1. 逻辑结构

创建一个Database时会为这个Database创建一个名为public的默认schema.
相同数据库不同schema可以拥有相同名称的table/index/view/sequence/function等

9.2. 物理结构

9.2.1. OID

OID,对象标识符,无符号4字节整数.所有的数据库对象由各自的OID管理
  • 数据库对象OID保存在pg_database系统表里.

  • 表/索引/序列等对象OID保存在pg_class系统表里.

9.2.2. 表空间

初始化数据库目录时会自动创建两个表空间: pg_global和pg_default
  • pg_global保存在global目录中, 用来保存系统表

  • pg_default保存在base目录中, 默认数据库表空间

    每个数据库的oid都是base目录下的子目录, 表文件在所属数据库目录下以表OID命名.
    杜宇超过1GB大小的表文件则会自动切分为多个文件存储,以OID.<seq> 命名

9.3. 进程结构

  • postmaster

  • postgres

  • syslogger

  • checkpointer

  • bgwriter

  • walwriter

9.4. 内存结构

  • 本地内存

    • work_mem: ORDER BY/DISTINCT会用到

    • maintenance_work_mem: VACUUM/REINDEX/CREATE INDEX会用到

    • temp_buffers: 临时表操作会用到

  • 共享内存

    • shared buffer pool: 将表/索引文件载入内存

    • WAL buffer: WAL文件持久化缓冲区

    • CommitLog buffer: commit log中保存事务的状态,保存在缓冲区

10. 事务

create table tbl_mvcc
(
    id   bigserial primary key,
    ival integer
);
insert into tbl_mvcc(ival) values (1);

10.1. 脏读

一个事务看到了另外一个事务未提交的数据. (PostgreSQL下不可复现)

Table 4. Dirty Read
console1 console2
-- MySQL
set session transaction isolation level read uncommitted;
start transaction;
select * from tbl_mvcc where id = 1; -- 1
start transaction;
update tbl_mvcc set ival = 10 where id = 1;
select * from tbl_mvcc where id = 1; -- 10

10.2. 不可重复读

一个事务查询结果与第一次的结果不同.(受到其他已提交事务 UPDATE 的影响)

Table 5. Non-repeatable Read
console1 console2
begin transaction isolation level read committed;
select * from tbl_mvcc where id = 1; -- 1
begin;
update tbl_mvcc set ival = 10 where id = 1;
end;
select * from tbl_mvcc where id = 1; -- 10

10.3. 幻读

一个事务两次查询的结果集数量不一致.(受到其他已提交事务 INSERT/DELETE 的影响)

Table 6. Phantom Read
console1 console2
begin transaction isolation level read committed;
select * from tbl_mvcc where id between 1 and 10;
begin;
delete from tbl_mvcc where id > 5 ;
end;
-- 与之前结果相比少了一些数据
select * from tbl_mvcc where id between 1 and 10;
Table 7. Serialization Anomaly
console1 console2
begin transaction isolation level repeatable read;
select ival from tbl_mvcc where id =1;
update tbl_mvcc set ival = 10 where id = 1;
update tbl_mvcc set ival = 100 where id = 1;
-- [40001] ERROR: could not serialize access due to concurrent update
Table 8. 事务隔离级别
隔离级别 脏读 不可重复读 幻读

Read Uncommitted

Read Committed (PostgreSQL默认)

Repeatable Read (MySQL默认)

Serializable

10.4. 查看全局事务默认隔离级别

select name,setting from pg_settings where name='default_transaction_isolation';

10.5. 修改全局事务默认隔离级别

  • 修改postgresql.conf的default_transaction_isolation参数

  • ALTER SYSTEM SET default_transaction_isolation TO 'REPEATABLE READ';

10.6. 查看当前会话事务默认隔离级别

  • SHOW transaction_isolation;

  • select current_setting('transaction_isolation');

10.7. 设置当前会话事务默认隔离级别

  • set session characteristics as transaction isolation level REPEATABLE READ

  • START|BEGIN TRANSACTION ISOLATION LEVEL READ UNCOMMITTED …​ END

11. 分区

将一个表根据不同的规则分成多个块的行为, 称为分区, 每一个分区称为分区表.

  • 应用了分区规则的列会自动添加not null的约束.

  • 如果插入的值根据规则找不到匹配的分区, 则会报错.

  • PostgreSQL 10之后才内置分区功能, 支持Range和List分区, 11之后支持Hash分区.

11.1. 优势

  • 每个分区表的索引相对于单表的索引大小会减小, 查询和更新的性能会提高

  • 删除特定范围的数据可以通过直接删除某个分区表实现

只有当表本身大小超过了物理内存的大小, 分区后才会受益.

11.2. 分区方式

  • Range分区

    根据某一列值的范围插入相应的分区表, 比如根据日期范围分区, 仅支持单个列.
  • List分区

    根据每个分区表的某一列值的集合分区. 支持多列/多表达式
  • Hash分区

    根据某一列值的hash值分区

11.3. SQL

-- 创建主表
CREATE TABLE [ IF NOT EXISTS ] parent_table ( [
  { column_name data_type [ COLLATE collation ] [ column_constraint [ ... ] ]
 ] ) PARTITION BY { RANGE | LIST | HASH } ( { column_name | ( expression ) }
-- 创建range型分区表
CREATE TABLE partition_table_name PARTITION OF parent_table FOR VALUES FROM (start) TO (end);
-- 创建list型分区表
CREATE TABLE partition_table_name PARTITION OF parent_table FOR VALUES IN (val1, val2) ;
-- 创建hash型分区表
CREATE TABLE partition_table_name PARTITION OF parent_table FOR VALUES WITH (MODULUS 4, REMAINDER 3);

-- 删除分区关系
ALTER TABLE parent_table ATTACH PARTITION partition_table_name
update语句违反了当前分区键的约束会报错

12. 清除WAL日志

pg_archivecleanup -d <archive_location> <oldest_kept_walfile>

如: pg_archivecleanup -d /var/lib/postgresql/data/pg_wal 000000010000000000000036 会将 000000010000000000000001~000000010000000000000035所有文件删除

pg_archivecleanup -d . `ls -r | head -2 | tail -1`

13. 查询优化

13.1. 逻辑优化

根据关系代数等价式优化查询

  • 尽量将选择操作下推到叶子节点来做

  • 尽量在叶子节点上使用投影缩小中间结果

13.2. 物理优化

通过代价估算的方式挑选代价比较低的物理路径

14. pgbench

pgbench 命令是PostgreSQL自带的性能测试工具, 用于批量生产测试数据以及SQL性能测试. 默认测试数据集由4张表组成, 可以通过 -s 参数设置数据量倍数, 默认数据量如下

表名 数据量

pgbench_branches

1

pgbench_tellers

10

pgbench_accounts

100000

pgbench_history

0

14.1. 初始化数据集

pgbench -i -I dtgvpf -q -s 1000 benchtest
参数说明:
  • -i : 表明要进行数据初始化.

  • -I : 设置初始化步骤:

    • d: 删除已经存在的测试表.

    • t: 创建测试表.

    • g: 生成测试数据.

    • v: 执行 VACUUM .

    • p: 创建主键.

    • f: 创建外键.

  • -q : 每5秒数据输出一次进度, 否则每生成10万条数据输出一次.

  • -s : 设置测试数据量倍数, 如100则表示 pgbench_accounts 里会生成1000万条数据.

  • benchtest : 用于测试的数据库名, 示例中库名为 benchtest.

14.2. SQL性能测试

pgbench -b tpcb-like -c 100 -C -j 100 -M simple -v -P 5 -r -T 30 benchtest
参数说明:
  • -b: 运行内置脚本, 如果为自定义脚本, 替换为 -f xxx.sql 参数.

    • tpcb-like: 执行一个事务块, 包括3条update, 1条select和1条insert语句.

    • simple-update: 只执行3条update语句.

    • select-only: 只执行1条select语句.

  • -c : 测试时建立的连接数量, 默认为1.

  • -C : 标识每次执行测试SQL时重新建立连接.

  • -j : pgbench线程数量.

  • -M : SQL模式: simple/extended/prepared.

  • -v : 标识测试前vacuum pgbench_tellerspgbench_branches 表, 并truncate pgbench_history 表.

  • -P : 设置每隔几秒输出一次测试进度.

  • -r : 标识输出SQL平均执行耗时.

  • -T : 设置测试执行时间.

  • -R : 设置测试每秒发送事务数.

  • benchtest : 用于测试的数据库名, 示例中库名为 benchtest.

15. 参考配置

# 固定参数
listen_addresses = '*'
superuser_reserved_connections = 10
unix_socket_directories = '., /tmp'
unix_socket_permissions = 0700
tcp_keepalives_idle = 60
tcp_keepalives_interval = 10
tcp_keepalives_count = 10
huge_pages = try
dynamic_shared_memory_type = posix
vacuum_cost_delay = 0
vacuum_cost_limit = 10000
bgwriter_delay = 10ms
bgwriter_lru_maxpages = 1000
bgwriter_lru_multiplier = 10.0
bgwriter_flush_after = 512kB
effective_io_concurrency = 0
max_worker_processes = 256
parallel_leader_participation = on
old_snapshot_threshold = 6h
wal_level = replica
synchronous_commit = off
full_page_writes = on
wal_compression = on
wal_buffers = 16MB
wal_writer_delay = 10ms
wal_writer_flush_after = 1MB
checkpoint_timeout = 30min
checkpoint_completion_target = 0.2
checkpoint_flush_after = 256kB
archive_mode = on
archive_command = '/bin/date'
max_wal_senders = 64
max_replication_slots = 64
hot_standby = on
max_standby_archive_delay = 120s
max_standby_streaming_delay = 120s
wal_receiver_status_interval = 1s
hot_standby_feedback = off
max_logical_replication_workers = 64
enable_partitionwise_join = on
enable_partitionwise_aggregate = on
random_page_cost = 1.1
log_destination = 'csvlog'
logging_collector = on
log_directory = 'log'
log_filename = 'postgresql-%a.log'
log_truncate_on_rotation = on
log_rotation_age = 1d
log_rotation_size = 1GB
log_min_duration_statement = 5s
log_checkpoints = on
log_connections = off
log_disconnections = off
log_error_verbosity = verbose
log_lock_waits = on
log_statement = 'ddl'
log_temp_files = 256MB
track_io_timing = on
track_functions = pl
autovacuum = on
log_autovacuum_min_duration = 0
autovacuum_vacuum_scale_factor = 0.02
autovacuum_analyze_scale_factor = 0.01
autovacuum_freeze_max_age = 1200000000
autovacuum_multixact_freeze_max_age = 1250000000
autovacuum_vacuum_cost_delay = 0ms
idle_in_transaction_session_timeout = '6h'
vacuum_freeze_table_age = 200000000
vacuum_multixact_freeze_table_age = 200000000
default_text_search_config = 'pg_catalog.english'
shared_preload_libraries = 'pg_stat_statements'
deadlock_timeout = 1s
log_timezone = 'PRC'
datestyle = 'iso, mdy'
timezone = 'PRC'
lc_messages = 'C'
lc_monetary = 'C'
lc_numeric = 'C'
lc_time = 'C'

# 动态参数(以16C64G为例)
max_connections = 3200                 # 物理内存(GB)*1000*(1/4)/5
shared_buffers = 16GB                  # IF use hugepage: 主机内存*(1/4)   ELSE: min(32GB, 主机内存*(1/4))
max_prepared_transactions = 3200       # max_prepared_transactions=max_connections
work_mem = 16MB                        # max(min(物理内存/4096, 64MB), 4MB)
maintenance_work_mem = 1GB             # min( 8G, (主机内存*1/8)/max_parallel_maintenance_workers )
autovacuum_work_mem = 1GB              # min( 8G, (主机内存*1/8)/autovacuum_max_workers )
max_parallel_maintenance_workers = 8   # min( max(2, CPU核数/2) , 16 )
max_parallel_workers_per_gather = 12   # min( max(2, CPU核数-4) , 24 )
max_parallel_workers = 12              # max(2, CPU核数-4)
max_wal_size = 32GB                    # shared_buffers*2
min_wal_size = 8GB                     # shared_buffers/2
max_sync_workers_per_subscription = 12 # min ( 32 , max(2, CPU核数-4) )
effective_cache_size = 32GB            # 主机内存/2
autovacuum_max_workers = 8             # max(min( 8 , CPU核数/2 ) , 5)