Warning: file_get_contents(/data/phpspider/zhask/data//catemap/1/php/298.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Php 同时从PostgreSQL获取唯一的序列号_Php_Sql_Database_Postgresql_Concurrent Programming - Fatal编程技术网

Php 同时从PostgreSQL获取唯一的序列号

Php 同时从PostgreSQL获取唯一的序列号,php,sql,database,postgresql,concurrent-programming,Php,Sql,Database,Postgresql,Concurrent Programming,我们正在设计一个订单管理系统,订单id用Postgresql设计成bigint,位置结构实现如下: 以2015072201000010001为例,前八位为日期,此处为20150722,后七位为地区代码,此处为0100001,后四位为上述地区和日期下的序列号 因此,每次创建新订单时,php逻辑应用层都会使用以下类似sql的语句查询PostgreSQL: select id from orders where id between 2015072201000010000 and 2015072201

我们正在设计一个订单管理系统,订单id用Postgresql设计成bigint,位置结构实现如下:

以2015072201000010001为例,前八位为日期,此处为20150722,后七位为地区代码,此处为0100001,后四位为上述地区和日期下的序列号

因此,每次创建新订单时,php逻辑应用层都会使用以下类似sql的语句查询PostgreSQL:

select id from orders where id between 2015072201000010000 and 2015072201000019999 order by id desc limit 1 offset 0
然后增加新订单的id,然后将订单插入PostgreSQL数据库

如果一次只有一个订单生成过程,这是可以的。但是,由于PostgreSQL的数据库读/写锁定机制,在数百个并发订单生成请求的情况下,订单ID发生冲突的可能性非常大

假设有两个订单请求A和B。A尝试从数据库中读取最新的订单id,然后B也读取最新的订单id,然后A写入数据库,最后B写入数据库将失败,因为订单id主键发生冲突


关于如何使此订单生成操作同时可行,您有什么想法吗?

尝试使用UUIDv1类型,它是时间戳和MAC地址的组合。如果插入顺序对您很重要,您可以在服务器端自动生成它。否则,可以在插入之前从任何客户端生成ID,您可能需要同步它们的时钟。请注意,使用UUIDv1 is,您可以公开生成UUID的主机的MAC地址。在这种情况下,您可能想要欺骗MAC地址

对于你的情况,你可以做如下的事情

CREATE TABLE orders (
    id uuid PRIMARY KEY DEFAULT uuid_generate_v1(),
    created_at timestamp NOT NULL DEFAULT now(),
    region_code text NOT NULL REFERENCES...
    ...
);
create sequence seq_0100001;

阅读更多信息,请尝试使用UUIDv1类型,它是时间戳和MAC地址的组合。如果插入顺序对您很重要,您可以在服务器端自动生成它。否则,可以在插入之前从任何客户端生成ID,您可能需要同步它们的时钟。请注意,使用UUIDv1 is,您可以公开生成UUID的主机的MAC地址。在这种情况下,您可能想要欺骗MAC地址

对于你的情况,你可以做如下的事情

CREATE TABLE orders (
    id uuid PRIMARY KEY DEFAULT uuid_generate_v1(),
    created_at timestamp NOT NULL DEFAULT now(),
    region_code text NOT NULL REFERENCES...
    ...
);
create sequence seq_0100001;

阅读更多信息,请访问避免在Postgres中锁定ID的通常方法是通过序列

可以对每个区域使用Postgresql序列。差不多

CREATE TABLE orders (
    id uuid PRIMARY KEY DEFAULT uuid_generate_v1(),
    created_at timestamp NOT NULL DEFAULT now(),
    region_code text NOT NULL REFERENCES...
    ...
);
create sequence seq_0100001;
然后,您可以使用以下方法从中获取数字:

select nextval('seq_'||regioncode) % 10000 as order_seq
这意味着订单号不会每天重置为0001,但订单号的范围为0000->9999。它会卷起来的

因此,您最终可能会:

2015072201000010001 -> 2015072201000017500 
2015072301000017501 -> 2015072301000019983
2015072401000019984 -> 2015072401000010293

或者,您可以只为每一天/地区组合生成一个序列,但您需要在第二天开始时删除前几天的序列。

避免在Postgres中锁定ID的常用方法是通过序列

可以对每个区域使用Postgresql序列。差不多

CREATE TABLE orders (
    id uuid PRIMARY KEY DEFAULT uuid_generate_v1(),
    created_at timestamp NOT NULL DEFAULT now(),
    region_code text NOT NULL REFERENCES...
    ...
);
create sequence seq_0100001;
然后,您可以使用以下方法从中获取数字:

select nextval('seq_'||regioncode) % 10000 as order_seq
这意味着订单号不会每天重置为0001,但订单号的范围为0000->9999。它会卷起来的

因此,您最终可能会:

2015072201000010001 -> 2015072201000017500 
2015072301000017501 -> 2015072301000019983
2015072401000019984 -> 2015072401000010293

或者,您可以只为每一天/区域组合生成一个序列,但您需要在第二天开始时删除前几天的序列。

在许多并发操作的情况下,您唯一的选择是使用序列。在这个场景中,您需要为每个日期和区域创建一个序列。这听起来需要做很多工作,但大部分都可以自动化

创建序列 您可以根据日期和区域命名序列。所以你可以这样做:

CREATE SEQUENCE seq_201507220100001;
您应该为日期和区域的每个组合创建一个序列。在函数中执行此操作以避免重复。每天运行此功能一次。你可以提前完成这项工作,或者——甚至更好——在每天的计划工作中完成这项工作,以创建明天的序列。假设您不需要将订单日期追溯到前几天,您可以在同一函数中删除昨天的序列

CREATE FUNCTION make_and_drop_sequences() RETURNS void AS $$
DECLARE
  region    text;
  tomorrow  text;
  yesterday text;
BEGIN
  tomorrow  := to_char((CURRENT_DATE + 1)::date, 'YYYYMMDD');
  yesterday := to_char((CURRENT_DATE - 1)::date, 'YYYYMMDD');
  FOREACH region IN 
    SELECT DISTINCT region FROM table_with_regions
  LOOP
    EXECUTE format('CREATE SEQUENCE %I', 'seq_' || tomorrow || region);
    EXECUTE format('DROP SEQUENCE %I', 'seq_' || yesterday|| region);
  END LOOP;
  RETURN;
END;
$$ LANGUAGE plpgsql;
使用序列 在PHP代码中,您显然知道需要输入新订单id的日期和区域。创建另一个函数,根据日期和区域从正确的序列生成新值:

CREATE FUNCTION new_date_region_id (region text) RETURN bigint AS $$
DECLARE
  dt_reg  text;
  new_id  bigint;
BEGIN
  dt_reg := tochar(CURRENT_DATE, 'YYYYMMDD') || region;
  SELECT dt_reg::bigint * 10000 + nextval(quote_literal(dt_reg)) INTO new_id;
  RETURN new_id;
END;
$$ LANGUAGE plpgsql STRICT;
然后在PHP中调用:

SELECT new_date_region_id('0100001');

它将为今天指定的区域提供下一个可用的id。

在许多并发操作的情况下,您唯一的选择是使用序列。在这个场景中,您需要为每个日期和区域创建一个序列。这听起来需要做很多工作,但大部分都可以自动化

创建序列 您可以根据日期和区域命名序列。 所以你可以这样做:

CREATE SEQUENCE seq_201507220100001;
您应该为日期和区域的每个组合创建一个序列。在函数中执行此操作以避免重复。每天运行此功能一次。你可以提前完成这项工作,或者——甚至更好——在每天的计划工作中完成这项工作,以创建明天的序列。假设您不需要将订单日期追溯到前几天,您可以在同一函数中删除昨天的序列

CREATE FUNCTION make_and_drop_sequences() RETURNS void AS $$
DECLARE
  region    text;
  tomorrow  text;
  yesterday text;
BEGIN
  tomorrow  := to_char((CURRENT_DATE + 1)::date, 'YYYYMMDD');
  yesterday := to_char((CURRENT_DATE - 1)::date, 'YYYYMMDD');
  FOREACH region IN 
    SELECT DISTINCT region FROM table_with_regions
  LOOP
    EXECUTE format('CREATE SEQUENCE %I', 'seq_' || tomorrow || region);
    EXECUTE format('DROP SEQUENCE %I', 'seq_' || yesterday|| region);
  END LOOP;
  RETURN;
END;
$$ LANGUAGE plpgsql;
使用序列 在PHP代码中,您显然知道需要输入新订单id的日期和区域。创建另一个函数,根据日期和区域从正确的序列生成新值:

CREATE FUNCTION new_date_region_id (region text) RETURN bigint AS $$
DECLARE
  dt_reg  text;
  new_id  bigint;
BEGIN
  dt_reg := tochar(CURRENT_DATE, 'YYYYMMDD') || region;
  SELECT dt_reg::bigint * 10000 + nextval(quote_literal(dt_reg)) INTO new_id;
  RETURN new_id;
END;
$$ LANGUAGE plpgsql STRICT;
然后在PHP中调用:

SELECT new_date_region_id('0100001');

这将为今天的指定区域提供下一个可用id。

这将在20150724上产生时间顺序错误的订单id,如果OP对此有任何顾虑的话。这似乎是一个简单而轻量级的解决方案。这将在20150724上产生时间顺序错误的订单id,这似乎是一个简单而轻量级的解决方案。请原谅我对PostgreSQL的无知,但事务处理可以吗?我的意思是,由于事务是原子的,我们可以将读写合并为一个事务。是的,这是一个选项,但您仍然需要每天的序列,区域组合。否则,您将必须查找每个订单的最高分配id,如您在问题中所示,这在计算上非常昂贵,并且会阻止等待新订单id的所有其他事务。序列是原子的,因此不需要锁定其他事务。另请参阅更新的检索id的函数:值总是针对今天,所以不需要指定日期。请原谅我在PostgreSQL上的无知,但事务处理可以吗?我的意思是,由于事务是原子的,我们可以将读写合并为一个事务。是的,这是一个选项,但您仍然需要每天的序列,区域组合。否则,您将必须查找每个订单的最高分配id,如您在问题中所示,这在计算上非常昂贵,并且会阻止等待新订单id的所有其他事务。序列是原子的,因此不需要锁定其他事务。另请参阅更新的检索id的函数:值始终用于今天,因此无需指定日期。此id设计考虑了日期和区域,其优点是只需一个筛选条件即可轻松有效地对记录进行排序和筛选。因此,保留原始设计对我们来说是件好事。这种id设计考虑了日期和区域,只需一个过滤条件,即可轻松有效地对记录进行排序和过滤。因此,保留原始设计对我们来说是有益的。