PostgreSQL
 sql >> Cơ Sở Dữ Liệu >  >> RDS >> PostgreSQL

Tính toán Khóa chính tiếp theo - có định dạng cụ thể

Điều này trông giống như một biến thể của bài toán chuỗi không có khoảng trống; cũng thấy ở đây.

Các chuỗi không có khoảng trống có vấn đề nghiêm trọng về hiệu suất và đồng thời.

Hãy suy nghĩ thật kỹ về điều gì sẽ xảy ra khi nhiều lần chèn diễn ra cùng một lúc. Bạn phải chuẩn bị để thử lại các lần chèn không thành công hoặc LOCK TABLE myTable IN EXCLUSIVE MODE trước INSERT vì vậy chỉ có một INSERT có thể bay cùng một lúc.

Sử dụng bảng trình tự có khóa hàng

Điều tôi phải làm trong tình huống này là:

CREATE TABLE sequence_numbers(
    level integer,
    code integer,
    next_value integer DEFAULT 0 NOT NULL,
    PRIMARY KEY (level,code),
    CONSTRAINT level_must_be_one_digit CHECK (level BETWEEN 0 AND 9),
    CONSTRAINT code_must_be_three_digits CHECK (code BETWEEN 0 AND 999),
    CONSTRAINT value_must_be_four_digits CHECK (next_value BETWEEN 0 AND 9999)
);

INSERT INTO sequence_numbers(level,code) VALUES (2,777);

CREATE OR REPLACE FUNCTION get_next_seqno(level integer, code integer)
RETURNS integer LANGUAGE 'SQL' AS $$
    UPDATE sequence_numbers 
    SET next_value = next_value + 1
    WHERE level = $1 AND code = $2
    RETURNING (to_char(level,'FM9')||to_char(code,'FM000')||to_char(next_value,'FM0000'))::integer;
$$;

sau đó để nhận ID:

INSERT INTO myTable (sequence_number, blah)
VALUES (get_next_seqno(2,777), blah);

Cách tiếp cận này có nghĩa là chỉ có một giao dịch có thể chèn một hàng với bất kỳ cặp (cấp độ, chế độ) nhất định nào tại một thời điểm, nhưng tôi nghĩ rằng nó không có cuộc đua.

Cẩn thận với những bế tắc

Vẫn còn một vấn đề trong đó hai giao dịch đồng thời có thể bị bế tắc nếu chúng cố gắng chèn các hàng theo một thứ tự khác nhau. Không có cách sửa chữa dễ dàng cho điều này; bạn phải sắp xếp thứ tự các lần chèn của mình để bạn luôn chèn mức và chế độ thấp trước mức cao, thực hiện một lần chèn cho mỗi giao dịch hoặc sống với bế tắc và thử lại. Cá nhân tôi sẽ làm điều sau.

Ví dụ về vấn đề, với hai phiên psql. Thiết lập là:

CREATE TABLE myTable(seq_no integer primary key);
INSERT INTO sequence_numbers VALUES (1,666)

sau đó trong hai phiên:

SESSION 1                       SESSION 2

BEGIN;
                                BEGIN;

INSERT INTO myTable(seq_no)
VALUES(get_next_seqno(2,777));
                                INSERT INTO myTable(seq_no)
                                VALUES(get_next_seqno(1,666));

                                INSERT INTO myTable(seq_no)
                                VALUES(get_next_seqno(2,777));

INSERT INTO myTable(seq_no)
VALUES(get_next_seqno(1,666));

Bạn sẽ nhận thấy rằng phần chèn thứ hai trong phiên 2 sẽ bị treo mà không quay trở lại, bởi vì nó đang chờ một khóa được giữ bởi phiên 1. Khi phiên 1 tiếp tục cố gắng lấy khóa được giữ bởi phiên 2 trong phần chèn thứ hai của nó, nó cũng sẽ hang. Không thể thực hiện tiến trình nào, vì vậy sau một hoặc hai giây PostgreSQL sẽ phát hiện ra bế tắc và hủy bỏ một trong các giao dịch, cho phép giao dịch kia tiếp tục:

ERROR:  deadlock detected
DETAIL:  Process 16723 waits for ShareLock on transaction 40450; blocked by process 18632.
Process 18632 waits for ShareLock on transaction 40449; blocked by process 16723.
HINT:  See server log for query details.
CONTEXT:  SQL function "get_next_seqno" statement 1

Mã của bạn phải được chuẩn bị để xử lý điều này và thử lại toàn bộ giao dịch hoặc nó phải tránh bế tắc bằng cách sử dụng các giao dịch chèn một lần hoặc đặt hàng cẩn thận.

Tự động tạo các cặp (cấp, mã) không tồn tại

BTW, nếu bạn muốn các kết hợp (cấp, mã) chưa tồn tại trong sequence_numbers bảng sẽ được tạo trong lần sử dụng đầu tiên, điều đó phức tạp một cách đáng ngạc nhiên vì nó là một biến thể của vấn đề nâng cấp. Cá nhân tôi muốn sửa đổi get_next_seqno trông như thế này:

CREATE OR REPLACE FUNCTION get_next_seqno(level integer, code integer)
RETURNS integer LANGUAGE 'SQL' AS $$

    -- add a (level,code) pair if it isn't present.
    -- Racey, can fail, so you have to be prepared to retry
    INSERT INTO sequence_numbers (level,code)
    SELECT $1, $2
    WHERE NOT EXISTS (SELECT 1 FROM sequence_numbers WHERE level = $1 AND code = $2);

    UPDATE sequence_numbers 
    SET next_value = next_value + 1
    WHERE level = $1 AND code = $2
    RETURNING (to_char(level,'FM9')||to_char(code,'FM000')||to_char(next_value,'FM0000'))::integer;

$$;

Mã này có thể bị lỗi, vì vậy bạn luôn phải chuẩn bị để thử lại các giao dịch. Như bài báo của depesz giải thích, có thể có nhiều cách tiếp cận mạnh mẽ hơn nhưng thường không đáng. Như đã viết ở trên, nếu hai giao dịch đồng thời cố gắng thêm cùng một cặp (cấp, mã) mới, một giao dịch sẽ không thành công với:

ERROR:  duplicate key value violates unique constraint "sequence_numbers_pkey"
DETAIL:  Key (level, code)=(0, 555) already exists.
CONTEXT:  SQL function "get_next_seqno" statement 1


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Cách pg_typeof () hoạt động trong PostgreSQL

  2. Cắt bỏ tất cả các bảng trong cơ sở dữ liệu Postgres

  3. Chuyển đổi từ không đồng bộ sang sao chép đồng bộ trong PostgreSQL

  4. Sự khác biệt giữa các dấu thời gian có / không có múi giờ trong PostgreSQL

  5. SQL, Postgres OIDs, Chúng là gì và tại sao chúng hữu ích?