postgresql Postgres中的事务可以处理并发数据吗?

qybjjes1  于 2023-06-22  发布在  PostgreSQL
关注(0)|答案(1)|浏览(117)

我是PostgreSQL的新手。我创建了一个函数来将数据插入2个表并更新1个表。
1.获取最新发票号并更新表res_invoice_number。2.插入res_purchase表。3.插入表res_purchase_detail。4.我在Supabase函数中使用了这个函数。5.我调用这个函数的同时在抖发票号好.
我想知道的是,如果客户端同时请求这个函数,它得到的发票号是一样的吗?
谢谢

create or replace function public.rpc_purchase_create(json text)
    returns int
    language 'plpgsql'
    security definer
as $body$
declare json_main jsonb;
declare json_detail jsonb;
declare invoice_number integer;
declare main_id text;
declare client_code text;
declare invoice text;
begin
  --assign json
  json_main := json::jsonb;
  json_detail := (json_main -> 'list_purchase_detail_model') :: jsonb;
  --assign sale id
  main_id := (json_main ->> 'id') :: text;
  client_code := (json_main ->> 'client_id') :: text;
  invoice := (json_main ->> 'invoice') :: text;
  invoice := split_part(invoice,'/',1);
  --increase invoice number
  with x as (update res_invoice_number set number = number + 1 where code = 'PO' and client_id=client_code returning number) select x.number into invoice_number from x;
  --insert into main
  insert into res_purchase (id,client_id,supplier_id,datetime,due_date,invoice,converted,reference,note,list_picture,sub_total,discount_percent,discount_amount,
                            discount_total,total,vat,vat_total,grand_total,amount_paid,amount_left,active,create_by,create_at,modify_by,modify_at) 
  values (
    main_id,
    client_code,
    json_main->>'supplier_id',
    (json_main->>'datetime')::bigint,
    (json_main->>'due_date')::bigint,
    concat(invoice,'/' ,trim(to_char(invoice_number,'000000'))), 
    '',
    json_main->>'reference',
    json_main->>'note',
    (json_main->>'list_picture')::jsonb,
    (json_main->>'sub_total')::float4,
    (json_main->>'discount_percent')::int2,
    (json_main->>'discount_amount')::float4,
    (json_main->>'discount_total')::float4,
    (json_main->>'total')::float4,
    (json_main->>'vat')::float4,
    (json_main->>'vat_total')::float4,
    (json_main->>'grand_total')::float4,
    (json_main->>'amount_paid')::float4,
    (json_main->>'amount_left')::float4,
    (json_main->>'active')::bool,
    json_main->>'create_by',
    (json_main->>'create_at')::bigint,
    null,null
  );
  --insert into detail
   insert into res_purchase_detail (
        purchase_id,
        product_id,
        product_name,
        description,
        qty,
        cost,
        discount,
        amount
    ) 
    select main_id,
           dt.data ->> 'product_id',
           dt.data ->> 'product_name',
           dt.data ->> 'description',
           (dt.data ->> 'qty')::float4,
           (dt.data ->> 'cost')::float4,
           (dt.data ->> 'discount')::int4,
           (dt.data ->> 'amount')::float4
    from jsonb_array_elements(json_detail) as dt(data);
  return invoice_number;
end;  

$body$;
rqqzpn5f

rqqzpn5f1#

由于函数在单个UPDATE中递增number,而不是使用单独的SELECT来获取当前值,后跟UPDATE,因此无论事务隔离级别如何,并发请求都不会具有相同的发票号。当以PostgreSQL的默认READ COMMITTED隔离模式运行时,每个请求将具有不同的发票编号。当以更严格的REPEATABLE READSERIALIZABLE隔离模式运行时,并发请求要么成功,要么发票号不同,要么引发异常;例如:

ERROR:  could not serialize access due to concurrent update

在post函数中,设置发票号的语句

with x as (update res_invoice_number set number = number + 1 where code = 'PO' and client_id=client_code returning number) select x.number into invoice_number from x;

可以简化为

update res_invoice_number set number = number + 1 where code = 'PO' and client_id=client_code returning number into invoice_number;

运行以下命令以建立演示环境:

CREATE TABLE demo_table (
  id integer PRIMARY KEY,
  number integer
);

INSERT INTO demo_table (id, number)
VALUES (1, 0);

CREATE OR REPLACE FUNCTION concurrency_demo_good(id demo_table.id%TYPE, sleep_seconds double precision DEFAULT 0)
  RETURNS demo_table.number%TYPE LANGUAGE PLPGSQL AS
$func$
  DECLARE
    invoice_number demo_table.number%TYPE;
  BEGIN
    UPDATE demo_table t
      SET number = t.number + 1
      WHERE t.id = concurrency_demo_good.id
    RETURNING t.number
    INTO invoice_number;

    PERFORM pg_sleep(sleep_seconds);
    RETURN invoice_number;
  END
$func$;

CREATE OR REPLACE FUNCTION concurrency_demo_bad(id demo_table.id%TYPE, sleep_seconds double precision DEFAULT 0)
  RETURNS demo_table.number%TYPE LANGUAGE PLPGSQL AS
$func$
  <<local>>
  DECLARE
    invoice_number demo_table.number%TYPE;
  BEGIN
    SELECT t.number + 1
      INTO invoice_number
      FROM demo_table t
      WHERE t.id = concurrency_demo_bad.id;

    UPDATE demo_table t
      SET number = local.invoice_number
      WHERE t.id = concurrency_demo_bad.id;

    PERFORM pg_sleep(sleep_seconds);
    RETURN invoice_number;
  END
$func$;

请注意,这些函数定义通过使用%TYPE而不是直接声明类型,将代码与列类型更改隔离开来。限定符用于保护函数不受表列和变量之间名称冲突的影响。
函数concurrency_demo_good使用单个UPDATE,因此递增number是一个原子操作。在concurrency_demo_bad中,invoice_number的新值在UPDATE之外建立。
开两个会。在两个会话中执行以下操作,以确保两者都具有READ COMMITTED隔离级别:

SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL READ COMMITTED;

接下来,在第一个会话运行中

SELECT concurrency_demo_good(1, 10);

在运行该查询时,在第二个会话中运行以下命令:

SELECT concurrency_demo_good(1);

第二个查询要在第一个查询之后才能完成。每个查询的返回值都不同。
在第一个会话中重复相同的查询,但这次在第一个会话的查询完成之前在第二个会话中运行以下操作:

SELECT concurrency_demo_bad(1);

同样,第二个查询要在第一个查询之后才能完成。这一次两个查询将返回相同的值。
对其他隔离级别(REPEATABLE READSERIALIZABLE)重复这些步骤会导致第一个会话返回发票编号,第二个会话报告异常。

相关问题