How to check a sequence efficiently for used and unused values in PostgreSQL

前端 未结 4 1756
暖寄归人
暖寄归人 2020-12-21 11:36

In PostgreSQL (9.3) I have a table defined as:

CREATE TABLE charts
( recid serial NOT NULL,
  groupid text NOT NULL,
  chart_number integer NOT NULL,
  \"tim         


        
4条回答
  •  长情又很酷
    2020-12-21 12:23

    Consider not doing it. Read these related answers first:

    • Gap-less sequence where multiple transactions with multiple tables are involved
    • Compacting a sequence in PostgreSQL

    If you still insist on filling in gaps, here is a rather efficient solution:

    1. To avoid searching large parts of the table for the next missing chart_number, create a helper table with all current gaps once:

    CREATE TABLE chart_gap AS
    SELECT chart_number
    FROM   generate_series(1, (SELECT max(chart_number) - 1  -- max is no gap
                               FROM charts)) chart_number
    LEFT   JOIN charts c USING (chart_number)
    WHERE  c.chart_number IS NULL;
    

    2. Set charts_chartnumber_seq to the current maximum and convert chart_number to an actual serial column:

    SELECT setval('charts_chartnumber_seq', max(chart_number)) FROM charts;
    
    ALTER TABLE charts
       ALTER COLUMN chart_number SET NOT NULL
     , ALTER COLUMN chart_number SET DEFAULT nextval('charts_chartnumber_seq');
    
    ALTER SEQUENCE charts_chartnumber_seq OWNED BY charts.chart_number; 
    

    Details:

    • How to reset postgres' primary key sequence when it falls out of sync?
    • Safely and cleanly rename tables that use serial primary key columns in Postgres?

    3. While chart_gap is not empty fetch the next chart_number from there. To resolve possible race conditions with concurrent transactions, without making transactions wait, use advisory locks:

    WITH sel AS (
       SELECT chart_number, ...  -- other input values
       FROM   chart_gap
       WHERE  pg_try_advisory_xact_lock(chart_number)
       LIMIT  1
       FOR    UPDATE
       )
    , ins AS (
       INSERT INTO charts (chart_number, ...) -- other target columns
       TABLE sel 
       RETURNING chart_number
       )
    DELETE FROM chart_gap c
    USING  ins i
    WHERE  i.chart_number = c.chart_number;
    

    Alternatively, Postgres 9.5 or later has the handy FOR UPDATE SKIP LOCKED to make this simpler and faster:

    ...
       SELECT chart_number, ...  -- other input values
       FROM   chart_gap
       LIMIT  1
       FOR    UPDATE SKIP LOCKED
    ...
    

    Detailed explanation:

    • Postgres UPDATE ... LIMIT 1

    Check the result. Once all rows are filled in, this returns 0 rows affected. (you could check in plpgsql with IF NOT FOUND THEN ...). Then switch to a simple INSERT:

       INSERT INTO charts (...)  -- don't list chart_number
       VALUES (...);  --  don't provide chart_number
    

提交回复
热议问题