PostgreSQL 无缝自增ID的实现 - by advisory lock

PostgreSQL 无缝自增ID的实现 - by advisory lock

作者

digoal

日期

2016-10-20

标签

PostgreSQL , advisory lock , 无缝自增ID


背景

一般来说,数据库都会有序列的功能,例如PostgreSQL就支持序列。

序列是指一直增长的值,但是它有一个不好的地方,就是用掉后就不会再有了,因此对于使用者来说,可能会拿到空洞的值。

例如

postgres=# create table seq_test(id serial, info text);
CREATE TABLE

postgres=# \d+ seq_test
                                             Table "public.seq_test"
 Column |  Type   |                       Modifiers                       | Storage  | Stats target | Description
--------+---------+-------------------------------------------------------+----------+--------------+-------------
 id     | integer | not null default nextval('seq_test_id_seq'::regclass) | plain    |              |
 info   | text    |                                                       | extended |              |

序列的值只要被获取后,就消耗掉了,一直往前。

所以如果中间出现过回滚,就会出现空洞。

postgres=# insert into seq_test (info) values ('test');
INSERT 0 1
postgres=# begin;
BEGIN
postgres=# insert into seq_test (info) values ('test');
INSERT 0 1
postgres=# rollback;
ROLLBACK
postgres=# insert into seq_test (info) values ('test');
INSERT 0 1
postgres=# select * from seq_test;
 id | info
----+------
  1 | test
  3 | test
(2 rows)

那么有没有一种方法可以得到完全无缝的自增序列值呢?

本文将给大家提供一种方法。

advisory lock

PostgreSQL提供了一个很棒的特性,叫做advisory lock,使用这个锁,可以提供并发的生成能力。

这个锁分为会话锁和事务锁,详见

https://www.postgresql.org/docs/9.6/static/functions-admin.html#FUNCTIONS-ADVISORY-LOCKS

Name Return Type Description
pg_advisory_lock(key bigint) void Obtain exclusive session level advisory lock
pg_advisory_lock(key1 int, key2 int) void Obtain exclusive session level advisory lock
pg_advisory_lock_shared(key bigint) void Obtain shared session level advisory lock
pg_advisory_lock_shared(key1 int, key2 int) void Obtain shared session level advisory lock
pg_advisory_unlock(key bigint) boolean Release an exclusive session level advisory lock
pg_advisory_unlock(key1 int, key2 int) boolean Release an exclusive session level advisory lock
pg_advisory_unlock_all() void Release all session level advisory locks held by the current session
pg_advisory_unlock_shared(key bigint) boolean Release a shared session level advisory lock
pg_advisory_unlock_shared(key1 int, key2 int) boolean Release a shared session level advisory lock
pg_advisory_xact_lock(key bigint) void Obtain exclusive transaction level advisory lock
pg_advisory_xact_lock(key1 int, key2 int) void Obtain exclusive transaction level advisory lock
pg_advisory_xact_lock_shared(key bigint) void Obtain shared transaction level advisory lock
pg_advisory_xact_lock_shared(key1 int, key2 int) void Obtain shared transaction level advisory lock
pg_try_advisory_lock(key bigint) boolean Obtain exclusive session level advisory lock if available
pg_try_advisory_lock(key1 int, key2 int) boolean Obtain exclusive session level advisory lock if available
pg_try_advisory_lock_shared(key bigint) boolean Obtain shared session level advisory lock if available
pg_try_advisory_lock_shared(key1 int, key2 int) boolean Obtain shared session level advisory lock if available
pg_try_advisory_xact_lock(key bigint) boolean Obtain exclusive transaction level advisory lock if available
pg_try_advisory_xact_lock(key1 int, key2 int) boolean Obtain exclusive transaction level advisory lock if available
pg_try_advisory_xact_lock_shared(key bigint) boolean Obtain shared transaction level advisory lock if available
pg_try_advisory_xact_lock_shared(key1 int, key2 int) boolean Obtain shared transaction level advisory lock if available

并行无缝自增序列的实现

将逻辑放到函数中,如下,需要保证ID字段的唯一,以及它的顺序绝对保证,插入数据后,返回ID值给客户端。

postgres=# create table uniq_test(id int primary key, info text);
CREATE TABLE

create or replace function f_uniq(i_info text) returns int as $$
declare
  newid int;
  i int := 0;
  res int;
begin
  loop
    if i>0 then
      perform pg_sleep(0.2*random());
    else
      i := i+1;
    end if;

    -- 获取已有的最大ID+1 (即将插入的ID)
    select max(id)+1 into newid from uniq_test;
    if newid is not null then
      -- 获取AD LOCK
      if pg_try_advisory_xact_lock(newid) then
        -- 插入
    insert into uniq_test (id,info) values (newid,i_info);
        -- 返回此次获取到的UID
    return newid;
      else
    -- 没有获取到AD LOCK则继续循环
    continue;
      end if;
    else
      -- 表示这是第一条记录,获取AD=1 的LOCK
      if pg_try_advisory_xact_lock(1) then
    insert into uniq_test (id, info) values (1, i_info);
        return 1;
      else
    continue;
      end if;
    end if;
  end loop;

  -- 如果因为瞬态导致PK冲突了,继续调用
  exception when others then
    select f_uniq(i_info) into res;
    return res;
end;
$$ language plpgsql strict;

并行压测

$ vi test.sql
select f_uniq('test');

$ pgbench -M prepared -n -r -P 1 -f ./test.sql -c 164 -j 164 -T 10
progress: 1.0 s, 9526.0 tps, lat 13.759 ms stddev 69.983
progress: 2.0 s, 12305.9 tps, lat 13.554 ms stddev 67.042
progress: 3.0 s, 12378.7 tps, lat 13.206 ms stddev 65.303
progress: 4.0 s, 12277.0 tps, lat 12.969 ms stddev 68.373
progress: 5.0 s, 12332.3 tps, lat 13.535 ms stddev 71.023
progress: 6.0 s, 11852.9 tps, lat 13.715 ms stddev 70.337
progress: 7.0 s, 12168.1 tps, lat 13.582 ms stddev 71.053
progress: 8.0 s, 12174.8 tps, lat 13.390 ms stddev 69.684
progress: 9.0 s, 12145.0 tps, lat 13.393 ms stddev 70.059
progress: 10.0 s, 12240.7 tps, lat 13.642 ms stddev 68.665
transaction type: ./test.sql
scaling factor: 1
query mode: prepared
number of clients: 164
number of threads: 164
duration: 10 s
number of transactions actually processed: 119565
latency average = 13.791 ms
latency stddev = 70.055 ms
tps = 11729.522019 (including connections establishing)
tps = 11737.830312 (excluding connections establishing)
script statistics:
 - statement latencies in milliseconds:
        13.791  select f_uniq('test');

性能还不错。

验证

postgres=# select count(*),max(id) from uniq_test ;
 count  |  max
--------+--------
 119565 | 119565
(1 row)

Count

时间: 2024-08-31 10:25:45

PostgreSQL 无缝自增ID的实现 - by advisory lock的相关文章

MySQL分表自增ID问题的解决方法_Mysql

当我们对MySQL进行分表操作后,将不能依赖MySQL的自动增量来产生唯一ID了,因为数据已经分散到多个表中.  应尽量避免使用自增IP来做为主键,为数据库分表操作带来极大的不便.  在postgreSQL.oracle.db2数据库中有一个特殊的特性---sequence. 任何时候数据库可以根据当前表中的记录数大小和步长来获取到该表下一条记录数.然而,MySQL是没有这种序列对象的.  可以通过下面的方法来实现sequence特性产生唯一ID:  1. 通过MySQL表生成ID 对于插入也就

两种mysql对自增id重新从1排序的方法

  本文介绍了两种mysql对自增id重新从1排序的方法,简少了对于某个项目初始化数据的工作量,感兴趣的朋友可以参考下 最近老是要为现在这个项目初始化数据,搞的很头疼,而且数据库的Id自增越来越大,要让自增重新从1开始:那么就用下面的方法吧: 方法一:如果曾经的数据都不需要的话,可以直接清空所有数据,并将自增字段恢复从1开始计数 truncate table 表名 方法二:dbcc checkident ('table_name', reseed, new_reseed_value) 当前值设置

mysql事务特性实现并发安全的自增ID示例

 项目中经常会用到自增id,比如uid,下面为大家介绍下利用mysql事务特性实现并发安全的自增ID,感兴趣的朋友可以参考下 项目中经常会用到自增id,比如uid,最简单的方法就是用直接用数据库提供的AUTO_INCREMENT,但是如果用户量非常大,几千万,几亿然后需要分表存储的时候呢,这种方案就搞不定了,所以最好有一个全局的自增ID的生成器,不管是否分表,都能从生成器中获取到全局自增的ID.    实现方法应该有很多,不过所有的方案都需要解决一个问题,就是保证在高并发的情景下,数据获取依然正

php实现Mongodb自定义方式生成自增ID的方法

 这篇文章主要介绍了php实现Mongodb自定义方式生成自增ID的方法,实例分析了Mongodb自增字段的实现技巧与对应php操作方法,需要的朋友可以参考下     本文实例讲述了php实现Mongodb自定义方式生成自增ID的方法.分享给大家供大家参考.具体分析如下: 代码如下: //首先创建一个自动增长id集合 ids >db.ids.save({name:"user", id:0}); //可以查看一下是否成功 > db.ids.find(); { "_i

【MySQL】如何获取自增id

 去"O"的业务支撑的时候,遇到一个开发询问如何获取到MySQL 的自增id,这里做一个总结,我们可以通过四种方式来获取MySQL 的自增id. 一 last_insert_id()官方介绍如下:"The ID that was generated is maintained in the server on a per-connection basis. This means that the value returned by the function to a give

Twitter的分布式自增ID算法Snowflake

Twitter早期使用MySQL存储数据,随着用户的增长,单一MySQL实例无法支持海量数据,Twitter开始把存储系统从MySQL迁移到Cassandra,但是Cassandra没有内置的顺序ID生成机制,因此Twitter开发了一套分布式系统全局唯一ID生成服务:Snowflake. 对于Twitter而言,必须满足每秒上万条消息的请求,并且每条消息能够分配一个全局唯一的ID,因此,ID生成服务要求必须满足高性能(>10K ids/s).低延迟(<2ms).高可用的特性,同时生成的ID还

MySQL源码学习:innodb_autoinc_lock_mode 下自增id不连续的原因

一.问题复现 文件/tmp/data.sql中两列,每列一个数字1; 输入 CREATE TABLE `t` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `k` int(10) unsigned NOT NULL DEFAULT '0', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; load data infile '/tmp/data.s

spring mvc-Springmvc框架下的,关于数据库自增ID获取的问题

问题描述 Springmvc框架下的,关于数据库自增ID获取的问题 向数据库中插入新的数据.并且ID是设置为自增的,我该如何做到插入的同时还能够获取到这个数据的id呢 解决方案 http://blog.csdn.net/yaerfeng/article/details/7231093 解决方案二: 有一个selectkey的属性,加上就好了 解决方案三: 首先,先在数据库中设置其ID为自增 然后,往数据库中插入数据 再然后,根据所插入的一些信息查找出刚刚插入数据的ID 解决方案四: spring

详解Twitter开源分布式自增ID算法snowflake,附演算验证过程

1.snowflake简介 互联网快速发展的今天,分布式应用系统已经见怪不怪,在分布式系统中,我们需要各种各样的ID,既然是ID那么必然是要保证全局唯一,除此之外,不同当业务还需要不同的特性,比如像并发巨大的业务要求ID生成效率高,吞吐大:比如某些银行类业务,需要按每日日期制定交易流水号:又比如我们希望用户的ID是随机的,无序的,纯数字的,且位数长度是小于10位的.等等,不同的业务场景需要的ID特性各不一样,于是,衍生了各种ID生成器,但大多数利用数据库控制ID的生成,性能受数据库并发能力限制,