我有一个表,其中包含ID列表以及其他各种列,例如IDName。
表的主键是ID本身,但它不是auto_increment。所以,我希望能够生成/计算下一个主键,但是有一个转折:
主键应采用特定格式,即8位数ID由三部分组成:
<the level><a code><a sequence #>
,例如<2><777><0123> = 27770123
因此,当我为表创建新ID时,我想要特定级别和代码的下一个序列号。例如。按照上面的例子,我可能想知道第2级的下一个序列号,代码为777,结果应该是ID 27770124(0124是序列中的下一个)。
非常感谢任何帮助。
答案 0 :(得分:6)
这看起来像gapless sequence problem的变体;也见过here。
无缝序列具有严重的性能和并发性问题。
非常认真地考虑一次发生多个插入时会发生什么。您必须准备好在LOCK TABLE myTable IN EXCLUSIVE MODE
之前重试失败的插入内容或INSERT
,这样一次只能有一个INSERT
可以在飞行中。
在这种情况下我要做的是:
CREATE TABLE sequence_numbers(
level integer,
code integer,
next_value integer DEFAULT 0 NOT NULL,
PRIMARY KEY (level,code),
CONSTRAINT level_must_be_one_digit CHECK (level BETWEEN 0 AND 9),
CONSTRAINT code_must_be_three_digits CHECK (code BETWEEN 0 AND 999),
CONSTRAINT value_must_be_four_digits CHECK (next_value BETWEEN 0 AND 9999)
);
INSERT INTO sequence_numbers(level,code) VALUES (2,777);
CREATE OR REPLACE FUNCTION get_next_seqno(level integer, code integer)
RETURNS integer LANGUAGE 'SQL' AS $$
UPDATE sequence_numbers
SET next_value = next_value + 1
WHERE level = $1 AND code = $2
RETURNING (to_char(level,'FM9')||to_char(code,'FM000')||to_char(next_value,'FM0000'))::integer;
$$;
然后获取ID:
INSERT INTO myTable (sequence_number, blah)
VALUES (get_next_seqno(2,777), blah);
这种方法意味着一次只有一个事务可以插入任何给定(级别,模式)对的行,但我认为它是无竞争的。
如果两个并发事务尝试以不同的顺序插入行,则仍然存在两个并发事务可能死锁的问题。对此没有简单的解决方法;您必须订购插件,以便始终在高位之前插入低级别和模式,为每个事务执行一次插入,或者使用死锁并重试。就个人而言,我会做后者。
问题示例,有两个psql会话。设置是:
CREATE TABLE myTable(seq_no integer primary key);
INSERT INTO sequence_numbers VALUES (1,666)
然后在两个会话中:
SESSION 1 SESSION 2
BEGIN;
BEGIN;
INSERT INTO myTable(seq_no)
VALUES(get_next_seqno(2,777));
INSERT INTO myTable(seq_no)
VALUES(get_next_seqno(1,666));
INSERT INTO myTable(seq_no)
VALUES(get_next_seqno(2,777));
INSERT INTO myTable(seq_no)
VALUES(get_next_seqno(1,666));
您会注意到会话2中的第二个插入将挂起而不返回,因为它正在等待会话1持有的锁定。当会话1继续尝试在第二个插入中获取会话2持有的锁定时,它也会挂起。没有进展,所以在一两秒后PostgreSQL将检测到死锁并中止其中一个事务,允许另一个事务继续进行:
ERROR: deadlock detected
DETAIL: Process 16723 waits for ShareLock on transaction 40450; blocked by process 18632.
Process 18632 waits for ShareLock on transaction 40449; blocked by process 16723.
HINT: See server log for query details.
CONTEXT: SQL function "get_next_seqno" statement 1
您的代码必须准备好处理此问题并重试整个事务,否则它必须使用单插入事务或谨慎排序来避免死锁。
顺便说一句,如果你想要在第一次使用时创建sequence_numbers
表中尚不存在的(级别,代码)组合,那么这是非常复杂的,因为它是{{3的变体}}。我个人修改get_next_seqno
看起来像这样:
CREATE OR REPLACE FUNCTION get_next_seqno(level integer, code integer)
RETURNS integer LANGUAGE 'SQL' AS $$
-- add a (level,code) pair if it isn't present.
-- Racey, can fail, so you have to be prepared to retry
INSERT INTO sequence_numbers (level,code)
SELECT $1, $2
WHERE NOT EXISTS (SELECT 1 FROM sequence_numbers WHERE level = $1 AND code = $2);
UPDATE sequence_numbers
SET next_value = next_value + 1
WHERE level = $1 AND code = $2
RETURNING (to_char(level,'FM9')||to_char(code,'FM000')||to_char(next_value,'FM0000'))::integer;
$$;
此代码可能会失败,因此您必须始终准备重试事务。正如depesz的文章所解释的那样,更可靠的方法是可能的,但通常不值得。如上所述,如果两个事务同时尝试添加相同的新(级别,代码)对,则一个将失败:
ERROR: duplicate key value violates unique constraint "sequence_numbers_pkey"
DETAIL: Key (level, code)=(0, 555) already exists.
CONTEXT: SQL function "get_next_seqno" statement 1
答案 1 :(得分:0)
好的,到目前为止我已经提出了,我认为这符合我的要求(除非有任何关于更优化方法的建议吗?)
SELECT ID
FROM myTable
WHERE ID > 27770000
AND ID < 27780000
ORDER BY ID DESC
LIMIT 1
答案 2 :(得分:0)
除非您的应用需求非常高,否则碰撞次数会非常少。因此,如果出现关键错误,我只会重试。
select coalesce(max(id), 27770000) + 1
from myTable
where id / 10000 = 2777
如果级别/代码尚不存在,则会出现合并。