PostgreSQL 的 SERIAL 与 MySQL 的 AUTO_INCREMENT?
PostgreSQL's SERIAL vs MySQL's AUTO_INCREMENT?
我有 MySQL 的这个片段:
CREATE TABLE seq_test (
id INT PRIMARY KEY AUTO_INCREMENT,
name TEXT
);
INSERT INTO seq_test(id, name) VALUES (1, 'one');
INSERT INTO seq_test(name) VALUES ('two');
当我尝试在 PostgreSQL 中编写此代码时:
CREATE TABLE seq_test (
id SERIAL PRIMARY KEY,
name TEXT
);
INSERT INTO seq_test(id, name) VALUES (1, 'one');
INSERT INTO seq_test(name) VALUES ('two');
我收到以下错误:
[23505] ERROR: duplicate key value violates unique constraint "seq_test_pkey"
Detail: Key (id)=(1) already exists.
这是因为在 PostgreSQL 中,插入一个不会增加下一个插入的 id。如何创建我的 table 以使其符合 MySQL 行为?
这当然是一个人为的例子,但我正在将一个大型代码库从 MySQL 移植到 PostgreSQL 并且部分代码(我无法控制)使用两种样式(即有和没有ids),它们在 MySQL 中有效,但在 PostgreSQL 中无效。
一个丑陋的 hack 是总是做 SELECT setval('my_table_id_seq', (SELECT count(*) FROM my_table), TRUE)
...
对此没有 unhacky 解决方案:您要么坚持使用 SERIAL
功能,要么自己处理。
但是你引起了我的兴趣,我想出了这个(我希望能少一点骇人听闻的)解决方案:创建一个触发器来完成所有肮脏的工作。
触发函数
(已添加通知以便我们了解发生了什么):
CREATE OR REPLACE FUNCTION update_seq_val_seq_test()
RETURNS TRIGGER AS $$
BEGIN
RAISE NOTICE 'id is %', NEW.id;
IF NEW.id > currval('seq_test_id_seq' :: REGCLASS)
THEN
RAISE NOTICE 'curval is %', currval('seq_test_id_seq' :: REGCLASS);
PERFORM setval('seq_test_id_seq' :: REGCLASS, (NEW.id) :: BIGINT);
RAISE NOTICE 'new curval is %', currval('seq_test_id_seq' :: REGCLASS); END IF;
RETURN NULL;
END;
$$
LANGUAGE 'plpgsql' COST 1;
设置触发器
CREATE TRIGGER seq_test_update_serial
AFTER INSERT ON seq_test
FOR EACH ROW EXECUTE PROCEDURE update_seq_val_seq_test();
扣动扳机
Fast'n'dirty 测试
tests2=# insert into seq_test (name) values ('first');
NOTICE: id is 30
INSERT 0 1
tests2=# select * from seq_test;
id | name
----+-------
30 | first
(1 row)
tests2=# select currval('seq_test_id_seq'::regclass);
currval
---------
30
(1 row)
tests2=# insert into seq_test (id, name) values (31, 'thirty one');
NOTICE: id is 31
NOTICE: curval is 30
NOTICE: new curval is 31
INSERT 0 1
tests2=# select currval('seq_test_id_seq'::regclass);
currval
---------
31
(1 row)
tests2=# select * from seq_test;
id | name
----+------------
30 | first
31 | thirty one
(2 rows)
tests2=# insert into seq_test (name) values ('thirty dunno what');
NOTICE: id is 32
INSERT 0 1
tests2=# insert into seq_test (id, name) values (21, 'back to the future');
NOTICE: id is 21
INSERT 0 1
tests2=# select currval('seq_test_id_seq'::regclass);
currval
---------
32
(1 row)
tests2=# select * from seq_test;
id | name
----+--------------------
30 | first
31 | thirty one
32 | thirty dunno what
21 | back to the future
(4 rows)
tests2=# insert into seq_test (name) values ('thirty dunno what++');
NOTICE: id is 33
INSERT 0 1
tests2=# select * from seq_test;
id | name
----+---------------------
30 | first
31 | thirty one
32 | thirty dunno what
21 | back to the future
33 | thirty dunno what++
(5 rows)
所以,现在 Postgres 更像您希望的那样处理这种情况,但是有很多事情需要您检查:它如何处理批量插入、回滚、这个触发器如何影响性能,以及更多有趣的事情给你。
我有 MySQL 的这个片段:
CREATE TABLE seq_test (
id INT PRIMARY KEY AUTO_INCREMENT,
name TEXT
);
INSERT INTO seq_test(id, name) VALUES (1, 'one');
INSERT INTO seq_test(name) VALUES ('two');
当我尝试在 PostgreSQL 中编写此代码时:
CREATE TABLE seq_test (
id SERIAL PRIMARY KEY,
name TEXT
);
INSERT INTO seq_test(id, name) VALUES (1, 'one');
INSERT INTO seq_test(name) VALUES ('two');
我收到以下错误:
[23505] ERROR: duplicate key value violates unique constraint "seq_test_pkey"
Detail: Key (id)=(1) already exists.
这是因为在 PostgreSQL 中,插入一个不会增加下一个插入的 id。如何创建我的 table 以使其符合 MySQL 行为?
这当然是一个人为的例子,但我正在将一个大型代码库从 MySQL 移植到 PostgreSQL 并且部分代码(我无法控制)使用两种样式(即有和没有ids),它们在 MySQL 中有效,但在 PostgreSQL 中无效。
一个丑陋的 hack 是总是做 SELECT setval('my_table_id_seq', (SELECT count(*) FROM my_table), TRUE)
...
对此没有 unhacky 解决方案:您要么坚持使用 SERIAL
功能,要么自己处理。
但是你引起了我的兴趣,我想出了这个(我希望能少一点骇人听闻的)解决方案:创建一个触发器来完成所有肮脏的工作。
触发函数
(已添加通知以便我们了解发生了什么):
CREATE OR REPLACE FUNCTION update_seq_val_seq_test()
RETURNS TRIGGER AS $$
BEGIN
RAISE NOTICE 'id is %', NEW.id;
IF NEW.id > currval('seq_test_id_seq' :: REGCLASS)
THEN
RAISE NOTICE 'curval is %', currval('seq_test_id_seq' :: REGCLASS);
PERFORM setval('seq_test_id_seq' :: REGCLASS, (NEW.id) :: BIGINT);
RAISE NOTICE 'new curval is %', currval('seq_test_id_seq' :: REGCLASS); END IF;
RETURN NULL;
END;
$$
LANGUAGE 'plpgsql' COST 1;
设置触发器
CREATE TRIGGER seq_test_update_serial
AFTER INSERT ON seq_test
FOR EACH ROW EXECUTE PROCEDURE update_seq_val_seq_test();
扣动扳机
Fast'n'dirty 测试
tests2=# insert into seq_test (name) values ('first');
NOTICE: id is 30
INSERT 0 1
tests2=# select * from seq_test;
id | name
----+-------
30 | first
(1 row)
tests2=# select currval('seq_test_id_seq'::regclass);
currval
---------
30
(1 row)
tests2=# insert into seq_test (id, name) values (31, 'thirty one');
NOTICE: id is 31
NOTICE: curval is 30
NOTICE: new curval is 31
INSERT 0 1
tests2=# select currval('seq_test_id_seq'::regclass);
currval
---------
31
(1 row)
tests2=# select * from seq_test;
id | name
----+------------
30 | first
31 | thirty one
(2 rows)
tests2=# insert into seq_test (name) values ('thirty dunno what');
NOTICE: id is 32
INSERT 0 1
tests2=# insert into seq_test (id, name) values (21, 'back to the future');
NOTICE: id is 21
INSERT 0 1
tests2=# select currval('seq_test_id_seq'::regclass);
currval
---------
32
(1 row)
tests2=# select * from seq_test;
id | name
----+--------------------
30 | first
31 | thirty one
32 | thirty dunno what
21 | back to the future
(4 rows)
tests2=# insert into seq_test (name) values ('thirty dunno what++');
NOTICE: id is 33
INSERT 0 1
tests2=# select * from seq_test;
id | name
----+---------------------
30 | first
31 | thirty one
32 | thirty dunno what
21 | back to the future
33 | thirty dunno what++
(5 rows)
所以,现在 Postgres 更像您希望的那样处理这种情况,但是有很多事情需要您检查:它如何处理批量插入、回滚、这个触发器如何影响性能,以及更多有趣的事情给你。