PostgreSQL 中 sequence 的用法
PostgreSQL 中 sequence 的用法
介绍
sequence 是 pg 自带的高效的自增id工具(也叫序列)。sequence 使用了轻量级锁的方式来做到高效自增id的,所以会比 UPDATE
行锁快。sequence 的返回数据类型默认是64位的整数,pg 10 可以自定 smallint, integer 或者是 bigint。
用法
使用 sequence
# 创建
CREATE SEQUENCE temp_seq;
# 获取下一个sequence
select nextval('temp_seq'); # 1
# 设置 sequence 的开始值
select setval('temp_seq', 42);
# 获取下一个sequence
select nextval('temp_seq'); # 43
# 创建一个自动循环的 sequence
CREATE SEQUENCE temp_seq2 MINVALUE 18 MAXVALUE 20 CYCLE;
select nextval('temp_seq2'); # 18
select nextval('temp_seq2'); # 19
select nextval('temp_seq2'); # 20
select nextval('temp_seq2'); # 18
# 创建表的时候把类型设置为 serial 会自动创建一个 sequence
CREATE TABLE temp_seq_table (id serial);
postgres=# \d temp_seq_table
Table "public.temp_seq_table"
Column | Type | Modifiers
--------+---------+-------------------------------------------------------------
id | integer | not null default nextval('temp_seq_table_id_seq'::regclass)
# 自动创建了 sequence temp_seq_table_id_seq
CACHE 参数
sequence 是可以保证自增数据不重复的,也就是说每次自增后都会持久化保存,那么为了继续提高性能,可以加上 CACHE 参数(默认为1),每个进程(连接)可以缓存一个子序列在当前进程内存里面,当子序列用完了才会去原序列取新的子序列。
这个用个例子简单说一下,创建 sequence temp_seq3 时用了 CACHE 10,A session 可以获取到的值是 1...10,B session 可以获取到的值是 11...20,那么获取顺序可能是 (A, A, B, A),返回值是 (1, 2, 11, 3),这个不是严格自增的序列,但可以保证回次返回都是唯一的,用了 CYCLE 参数的除外。
根据文档和源码( link 第80行),缓存在内存里面的最大值(cached)是每个进程都不一样的,所以如果要求严格自增的服务不能用 CACHE。
CREATE SEQUENCE temp_seq3 CACHE 10;
select nextval('temp_seq3'); # 1
select nextval('temp_seq3'); # 2
# \q 退出 psql 重新链接
select nextval('temp_seq3'); # 11
pgbench 压测
这里写三种用 pg 做自增id的方式
- 直接用
UPDATE SET RETURNING
- 用默认值的 SEQUENCE
- 用 CACHE=10 的SEQUENCE
测试命令,在 MacOS 上用 docker 开一个 pg 在里面运行一下 pgbench 脚本,其中 sql.sql 的内容替换成对应的压测脚本。
pgbench -c 10 -T 10 -f sql.sql -U postgres postgres
# 准备数据库表
CREATE TABLE temp_incr_table (id integer primary key, val integer);
INSERT INTO temp_incr_table VALUES(1, 1);
# 上面例子创建的 temp_seq 和 temp_seq3 都会用于压测
UPDATE;
BEGIN;
UPDATE temp_incr_table SET val = val + 1 WHERE id = 1 RETURNING val;
END;
TPS: 986
CACHE 默认为1 的sequence
BEGIN;
SELECT nextval('temp_seq');
END;
TPS: 7332
CACHE 默认为10 的sequence
BEGIN;
SELECT nextval('temp_seq3');
END;
TPS: 7451
压测总结
- sequence 比 update 快 8 倍左右,在并发量大的时候更明显,而且从 pg 运行原理来说也是可以证明的,mvvc 里面的 update 都会写一份新的数据,有额外的写和额外的vacuum工作,而且行锁还需要把锁的上下文记录到stat表。
- CACHE 优化不明显,理论上是可以减少更新 buffer 的次数,后续可以在生产环境的机器上验证一下。
ref
看看微信的分布式id生成器,原理都差不多,"实际应用中每次提升的步长为10000" = "CACHE 10000"。link
以上优化已经是用在友好速搭电商系统的订单号和优惠券号生成服务中,为商家提供更快更可靠的服务。
友好速搭是国内领先的电商服务和云技术提供方。依托技术、营销、企业服务三大业务,为高端零售品牌打造个性化交易系统,并快速链接行业资源与精准流量,使品牌价值最大化。官网链接