数据库层面解决高并发时获取最大主键重复问题

一、问题

在高并发下,因没有加锁处理,没有将获取最大主键值+1,然后将新数据插入到数据库这一流程加锁。导致两个用户的数据获取相同主键,并插入到数据库。此时其中一条数据将发生主键重复异常。

二、解决方案

当然,我们可以通过加锁将这两步合并起来处理。
但本文将探讨另一种可能性。
有一种方法可以使从数据库获取的主键永不重复
答案呼之欲出:使用数据库序列!

三、数据库序列(SEQUENCE)

什么是序列

序列是一种特殊的单行表,顾名思义,他只有一行,用来存储和维护一个序列值。
序列是基于bigint算法的,因此范围是不能超过一个八字节 整数的范围(-9223372036854775808 到 9223372036854775807)。
序列名称必须与同一模式中任何其他序列、表、索引、 视图或者外部表的名称不同。
在创建一个序列后,我们可以使用序列操作函数来获取一个最新的序列值,且获取后这个值会被增加一点。

例:
CREATE SEQUENCE user_id START 1;
这表示创建了一个名叫user_id的序列,获取这个序列值时,将从1开始。
SELECT nextval(user_id);
这表示获取user_id的序列值,第一次返回1,同时会将当前的user_id增加一个指定的数量,默认增加1点。
且这个序列是非事务的。 也就是说,执行该操作将无法回滚。user_id的序列值在使用后将永久增加1点。

显然,使用该方法获取主键时,无论多高的并发量,都可以完全避免主键重复。而且这无需修改你当前的代码,没有增加任何一行代码。就能解决主键重复的问题。
不过,这只适用于单一数据库场景,分库场景下,由于多数据库,该序列只在一个库中唯一。
后续会给出多数据库下,保证序列值唯一的解决方案。

1. 序列的创建

1)创建序列

CREATE SEQUENCE sequence_name
使用CREATE SEQUENCE来创建序列。
例:CREATE SEQUENCE user_id

2)设置初始值

CREATE SEQUENCE sequence_name START number
使用START bumber来设置一个初始值。
例:CREATE SEQUENCE user_id START 101

3)设置增量

CREATE SEQUENCE sequence_name START number INCREMENT BY increment
使用INCREMENT BY increment来设置一个增量,如果这个值为负,则序列值会减少。
例:CREATE SEQUENCE user_id START 101 INCREMENT BY 2

更多用法:PostgreSQL 使用手册

2.操作序列

1)nextval(regclass)函数

使用nextval函数可以返回下一个最新的序列值,并使序列递增。
例:SELECT nextval(user_id)

2)currval(regclass)函数

使用currval函数可以返回当前会话中,最近一次用nextval获取的指定序列的值。
例:SELECT currval(user_id)

3)lastval()函数

使用lastval函数可以返回当前会话中,最近一次用nextval获取的任何序列的值
例:SELECT lastval()

4)setval(regclass,bigint)函数

使用setval函数可以设置序列的当前值。
setval函数也是非事务的,无法回滚。
该函数需要序列上的UPDATE权限。
例:setval(user_id, 100)

5)setval(regclass,bigint,boolean)函数

使用setval函数可以设置序列的当前值,用第三个参数来表示该值是否被返回过,如果为true,下次使用nextval将递增后返回新值,如果为false,下次使用nextval时,将返回该值。
例:setval(user_id, 100,true)

3.更新序列

ALTER SEQUENCE sequence_name
使用ALTER SEQUENCE来更新一个序列。

1)更新序列名

ALTER SEQUENCE name RENAME TO new_name
例:ALTER SEQUENCE user_id RENAME TO new_user_id
将序列名从user_id变成new_user_id

2)更新默认值

ALTER SEQUENCE name START number
例:ALTER SEQUENCE user_id START 101
重新使序列user_id从101开始获取新值。
更多用法:PostgreSQL 使用手册

4.删除序列

DROP SEQUENCE sequence_name
使用DROP SEQUENCE来删除一个序列。

四、数据库分库下序列不唯一的解决方案

由于序列值只能在单一数据库中保证唯一不重复,但多数据库下无法保证重复,因此正常情况下只能使用在单一数据库中。
而多数据情况下,我们可以设置增量来保证序列唯一
比如将增量设置为2,第一个数据库从1开始,第二个数据库从2开始。
这样第一个数据库获取的序列值始终为单数(1,3,5,7,9)。
第二个数据库序列值始终为双数(2,4,6,8,10)。
如此可解决多数据库序列不唯一的问题。

对你有帮助吗?点个赞吧~

猜你喜欢

转载自blog.csdn.net/qq_42068856/article/details/124516408