Appearance
PostgreSQL 序列操作函数
概述
序列(Sequence)是 PostgreSQL 中的一种特殊数据库对象,用于生成唯一的数字序列。序列对象通常用于为表的主键字段生成唯一标识符,是实现自增长字段的标准方式。PostgreSQL 提供了一组专门的函数来操作序列对象,确保在多用户并发环境下安全地获取唯一的序列值。
INFO
序列的特点
- 序列是使用
CREATE SEQUENCE
创建的特殊单行表 - 提供多用户安全的方法获取连续序列值
- 支持原子操作,确保并发安全
- 序列值不会因事务回滚而回收
核心序列函数
1. nextval() - 获取下一个序列值
nextval(regclass)
函数将序列推进到下一个值并返回该值,这是一个原子操作。
语法
sql
nextval(sequence_name) → bigint
业务场景示例:用户注册系统
问题陈述:在用户注册系统中,需要为每个新注册的用户分配唯一的用户 ID。
解决方案:
sql
-- 创建用户ID序列
CREATE SEQUENCE user_id_seq
START WITH 1000
INCREMENT BY 1
CACHE 50;
-- 创建用户表
CREATE TABLE users (
user_id BIGINT DEFAULT nextval('user_id_seq') PRIMARY KEY,
username VARCHAR(50) NOT NULL,
email VARCHAR(100) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
sql
-- 方式1:依赖默认值自动生成ID
INSERT INTO users (username, email)
VALUES ('alice', '[email protected]');
-- 方式2:手动调用nextval
INSERT INTO users (user_id, username, email)
VALUES (nextval('user_id_seq'), 'bob', '[email protected]');
-- 批量插入
INSERT INTO users (username, email) VALUES
('charlie', '[email protected]'),
('diana', '[email protected]'),
('edward', '[email protected]');
输入和输出:
sql
-- 查看插入的结果
SELECT user_id, username, email FROM users ORDER BY user_id;
user_id | username | |
---|---|---|
1000 | alice | [email protected] |
1001 | bob | [email protected] |
1002 | charlie | [email protected] |
1003 | diana | [email protected] |
1004 | edward | [email protected] |
分析过程:
- 序列从 1000 开始,每次递增 1
DEFAULT nextval('user_id_seq')
确保每次插入时自动获取下一个序列值- 即使多个用户同时注册,每个用户都会获得唯一的 ID
CACHE 50
提高性能,预先缓存 50 个序列值
TIP
权限要求使用 nextval()
函数需要对序列拥有 USAGE
或 UPDATE
权限。
2. setval() - 设置序列当前值
setval()
函数用于设置序列的当前值,支持两种和三种参数形式。
语法
sql
setval(sequence_name, value) → bigint
setval(sequence_name, value, is_called) → bigint
业务场景示例:数据迁移和序列重置
问题陈述:从旧系统迁移数据到新系统时,需要保持用户 ID 的连续性,并正确设置序列的起始位置。
解决方案:
sql
-- 假设从旧系统导入了最大ID为5000的用户数据
-- 需要将序列设置为从5001开始
-- 方式1:双参数形式(下一个nextval返回5001)
SELECT setval('user_id_seq', 5000);
-- 验证:下一个nextval将返回5001
SELECT nextval('user_id_seq'); -- 返回: 5001
-- 方式2:三参数形式,is_called=true(与双参数相同)
SELECT setval('user_id_seq', 5000, true);
SELECT nextval('user_id_seq'); -- 返回: 5001
sql
-- 场景:需要让下一个nextval返回指定值(比如6000)
SELECT setval('user_id_seq', 6000, false);
-- 验证:下一个nextval将返回6000(而不是6001)
SELECT nextval('user_id_seq'); -- 返回: 6000
SELECT nextval('user_id_seq'); -- 返回: 6001
完整示例:订单编号管理系统
sql
-- 创建订单序列,从特定编号开始
CREATE SEQUENCE order_number_seq
START WITH 202401001
INCREMENT BY 1;
-- 创建订单表
CREATE TABLE orders (
order_id BIGINT DEFAULT nextval('order_number_seq') PRIMARY KEY,
customer_name VARCHAR(100),
order_date DATE DEFAULT CURRENT_DATE,
total_amount DECIMAL(10,2)
);
-- 场景1:正常使用
INSERT INTO orders (customer_name, total_amount)
VALUES ('张三', 199.99);
-- 场景2:需要跳过某些编号(比如预留给特殊订单)
SELECT setval('order_number_seq', 202401010); -- 跳到202401010
INSERT INTO orders (customer_name, total_amount)
VALUES ('李四', 299.99); -- 将获得订单号202401011
-- 场景3:需要从特定编号重新开始
SELECT setval('order_number_seq', 202402001, false); -- 下一个将是202402001
INSERT INTO orders (customer_name, total_amount)
VALUES ('王五', 399.99); -- 将获得订单号202402001
分析过程:
setval(seq, value)
等同于setval(seq, value, true)
is_called=true
表示指定的值已经被"使用",下次 nextval 会返回 value+1is_called=false
表示指定的值尚未被使用,下次 nextval 会返回 value
3. currval() - 获取当前会话序列值
currval()
函数返回当前会话中最近通过 nextval()
获取的序列值。
语法
sql
currval(sequence_name) → bigint
业务场景示例:主从表数据插入
问题陈述:在电商系统中,创建订单时需要同时插入订单主表和订单明细表,订单明细需要引用刚创建的订单 ID。
解决方案:
sql
-- 创建订单表和订单明细表
CREATE TABLE orders (
order_id BIGINT DEFAULT nextval('order_id_seq') PRIMARY KEY,
customer_id INTEGER,
order_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
total_amount DECIMAL(10,2)
);
CREATE TABLE order_items (
item_id BIGINT DEFAULT nextval('order_item_id_seq') PRIMARY KEY,
order_id BIGINT REFERENCES orders(order_id),
product_id INTEGER,
quantity INTEGER,
unit_price DECIMAL(8,2)
);
-- 创建序列
CREATE SEQUENCE order_id_seq START WITH 1;
CREATE SEQUENCE order_item_id_seq START WITH 1;
完整事务示例:
sql
BEGIN;
-- 插入订单主记录
INSERT INTO orders (customer_id, total_amount)
VALUES (1001, 599.98);
-- 获取刚插入的订单ID
-- currval获取当前会话中最近的nextval值
INSERT INTO order_items (order_id, product_id, quantity, unit_price) VALUES
(currval('order_id_seq'), 101, 2, 199.99),
(currval('order_id_seq'), 102, 1, 199.99);
COMMIT;
输入和输出:
sql
-- 查看插入结果
SELECT o.order_id, o.customer_id, o.total_amount,
oi.item_id, oi.product_id, oi.quantity, oi.unit_price
FROM orders o
JOIN order_items oi ON o.order_id = oi.order_id
ORDER BY o.order_id, oi.item_id;
order_id | customer_id | total_amount | item_id | product_id | quantity | unit_price |
---|---|---|---|---|---|---|
1 | 1001 | 599.98 | 1 | 101 | 2 | 199.99 |
1 | 1001 | 599.98 | 2 | 102 | 1 | 199.99 |
分析过程:
currval()
只能在当前会话中调用过nextval()
之后使用- 它返回会话本地值,不受其他会话影响
- 适用于需要获取刚生成的序列值的场景
WARNING
注意事项如果在当前会话中从未对指定序列调用过 nextval()
,调用 currval()
会报错。
4. lastval() - 获取最后使用的序列值
lastval()
函数返回当前会话中最近由任意序列的 nextval()
返回的值。
语法
sql
lastval() → bigint
业务场景示例:通用日志记录系统
问题陈述:在一个应用系统中,有多个模块都需要记录操作日志,需要获取最近生成的任何序列值用于关联。
解决方案:
sql
-- 创建多个业务表和对应序列
CREATE SEQUENCE user_seq START WITH 1;
CREATE SEQUENCE product_seq START WITH 1;
CREATE SEQUENCE order_seq START WITH 1;
CREATE TABLE users (
id BIGINT DEFAULT nextval('user_seq') PRIMARY KEY,
username VARCHAR(50)
);
CREATE TABLE products (
id BIGINT DEFAULT nextval('product_seq') PRIMARY KEY,
product_name VARCHAR(100)
);
CREATE TABLE operation_log (
log_id BIGINT DEFAULT nextval('log_seq') PRIMARY KEY,
operation_type VARCHAR(20),
reference_id BIGINT, -- 引用最近操作的记录ID
operation_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
description TEXT
);
CREATE SEQUENCE log_seq START WITH 1;
使用示例:
sql
-- 创建用户
INSERT INTO users (username) VALUES ('新用户');
-- 记录操作日志,使用lastval获取刚创建的用户ID
INSERT INTO operation_log (operation_type, reference_id, description)
VALUES ('CREATE_USER', lastval(), '创建新用户账户');
sql
-- 创建产品
INSERT INTO products (product_name) VALUES ('新产品');
-- 记录操作日志
INSERT INTO operation_log (operation_type, reference_id, description)
VALUES ('CREATE_PRODUCT', lastval(), '添加新产品到目录');
sql
-- 创建通用的操作记录函数
CREATE OR REPLACE FUNCTION log_operation(
op_type VARCHAR(20),
description TEXT
) RETURNS VOID AS $$
BEGIN
INSERT INTO operation_log (operation_type, reference_id, description)
VALUES (op_type, lastval(), description);
END;
$$ LANGUAGE plpgsql;
-- 使用示例
INSERT INTO users (username) VALUES ('测试用户');
SELECT log_operation('CREATE_USER', '通过函数记录用户创建');
输入和输出:
sql
-- 查看操作日志
SELECT log_id, operation_type, reference_id, description, operation_time
FROM operation_log
ORDER BY log_id;
log_id | operation_type | reference_id | description | operation_time |
---|---|---|---|---|
1 | CREATE_USER | 1 | 创建新用户账户 | 2024-01-15 10:30:00 |
2 | CREATE_PRODUCT | 1 | 添加新产品到目录 | 2024-01-15 10:31:00 |
3 | CREATE_USER | 2 | 通过函数记录用户创建 | 2024-01-15 10:32:00 |
分析过程:
lastval()
不需要指定序列名称,自动引用最近使用的序列- 适用于需要获取"最后操作"相关 ID 的通用场景
- 简化了跨表关联操作的代码编写
DANGER
重要提醒如果当前会话中从未调用过任何序列的 nextval()
,调用 lastval()
会报错。
序列函数权限要求
函数 | 所需权限 |
---|---|
nextval() | USAGE 或 UPDATE |
setval() | UPDATE |
currval() | USAGE 或 SELECT |
lastval() | 对最后使用的序列需要 USAGE 或 SELECT |
序列行为特性和注意事项
并发安全性
事务回滚特性
WARNING
重要特性序列值不会因事务回滚而回收,这可能导致序列中出现间隙。
演示示例:
sql
-- 查看当前序列值
SELECT currval('user_id_seq'); -- 假设当前是 1005
BEGIN;
-- 获取新的序列值
SELECT nextval('user_id_seq'); -- 返回 1006
-- 插入操作(故意失败)
INSERT INTO users (user_id, username, email)
VALUES (currval('user_id_seq'), NULL, '[email protected]'); -- 违反NOT NULL约束
ROLLBACK;
-- 事务回滚后,序列值1006不会被回收
SELECT nextval('user_id_seq'); -- 返回 1007,而不是 1006
系统崩溃恢复
INFO
持久性说明
- 如果数据库在提交包含序列操作的事务前崩溃,序列状态可能不确定
- 对于数据库内使用是安全的,因为未提交事务的其他影响也不会可见
- 如果需要在数据库外使用序列值,确保在使用前事务已提交
最佳实践
1. 序列命名规范
sql
-- 推荐的命名模式
CREATE SEQUENCE table_name_column_name_seq;
-- 示例
CREATE SEQUENCE users_user_id_seq;
CREATE SEQUENCE orders_order_id_seq;
CREATE SEQUENCE products_product_id_seq;
2. 性能优化配置
sql
-- 为高并发场景配置序列
CREATE SEQUENCE high_traffic_seq
START WITH 1
INCREMENT BY 1
CACHE 100 -- 增加缓存大小
NO CYCLE; -- 明确指定不循环
3. 序列监控和维护
sql
-- 查看序列当前状态
SELECT
schemaname,
sequencename,
last_value,
start_value,
increment_by,
max_value,
min_value,
cache_value,
is_cycled,
is_called
FROM pg_sequences
WHERE sequencename LIKE '%user%';
-- 检查序列使用情况
SELECT
n.nspname AS schema_name,
c.relname AS sequence_name,
format_type(s.seqtypid, NULL) AS data_type,
s.seqstart AS start_value,
s.seqincrement AS increment_by,
s.seqmax AS max_value,
s.seqmin AS min_value,
s.seqcache AS cache_value,
s.seqcycle AS is_cycled
FROM pg_sequence s
JOIN pg_class c ON c.oid = s.seqrelid
JOIN pg_namespace n ON n.oid = c.relnamespace
ORDER BY schema_name, sequence_name;
4. 错误处理模式
sql
-- 安全的currval使用模式
CREATE OR REPLACE FUNCTION safe_currval(seq_name TEXT)
RETURNS BIGINT AS $$
DECLARE
result BIGINT;
BEGIN
SELECT currval(seq_name) INTO result;
RETURN result;
EXCEPTION
WHEN OBJECT_NOT_IN_PREREQUISITE_STATE THEN
-- 如果currval失败,使用nextval
RETURN nextval(seq_name);
END;
$$ LANGUAGE plpgsql;
总结
PostgreSQL 序列操作函数提供了强大而灵活的机制来生成唯一标识符:
- nextval() - 核心函数,获取下一个序列值,保证并发安全
- currval() - 获取当前会话的序列值,用于关联操作
- setval() - 设置序列位置,用于数据迁移和序列重置
- lastval() - 获取最近使用的序列值,简化通用操作
正确理解和使用这些函数,能够确保数据的一致性和唯一性,同时在高并发环境下保持良好的性能表现。
TIP
关键要点
- 序列操作是原子的,保证并发安全
- 序列值不会因事务回滚而回收
- 合理设置权限和缓存参数
- 注意会话作用域的限制
- 做好错误处理和监控