How can I use a PostgreSQL triggers to store changes (SQL statements and row changes)

后端 未结 5 1062
轻奢々
轻奢々 2020-12-07 16:05

Using PostgreSQL triggers, is it possible to record the changes that have happened to a table due to INSERT or UPDATE SQL statements and log them to a file for later executi

相关标签:
5条回答
  • 2020-12-07 16:32

    example of an audit trigger from https://www.postgresql.org/docs/current/static/plpgsql-trigger.html

    CREATE TABLE emp (
        empname           text NOT NULL,
        salary            integer
    );
    
    CREATE TABLE emp_audit(
        operation         char(1)   NOT NULL,
        stamp             timestamp NOT NULL,
        userid            text      NOT NULL,
        empname           text      NOT NULL,
        salary integer
    );
    
    CREATE OR REPLACE FUNCTION process_emp_audit() RETURNS TRIGGER AS $emp_audit$
        BEGIN
            --
            -- Create a row in emp_audit to reflect the operation performed on emp,
            -- make use of the special variable TG_OP to work out the operation.
            --
            IF (TG_OP = 'DELETE') THEN
                INSERT INTO emp_audit SELECT 'D', now(), user, OLD.*;
                RETURN OLD;
            ELSIF (TG_OP = 'UPDATE') THEN
                INSERT INTO emp_audit SELECT 'U', now(), user, NEW.*;
                RETURN NEW;
            ELSIF (TG_OP = 'INSERT') THEN
                INSERT INTO emp_audit SELECT 'I', now(), user, NEW.*;
                RETURN NEW;
            END IF;
            RETURN NULL; -- result is ignored since this is an AFTER trigger
        END;
    $emp_audit$ LANGUAGE plpgsql;
    
    CREATE TRIGGER emp_audit
    AFTER INSERT OR UPDATE OR DELETE ON emp
        FOR EACH ROW EXECUTE PROCEDURE process_emp_audit();
    
    0 讨论(0)
  • 2020-12-07 16:32

    This is a very common requirement, so the following answer is based on this article on my blog.

    SQL log

    If you are interested only in the statements that are executed, then you can simply activate the PostgreSQL statement log.

    For that, open the postgresql.conf file and set the following configuration property:

    log_statement = 'all' 
    

    Afterward, you will see the SQL statements logged in a file under the following path:

    $PG_DATA/pg_log/postgresql-YYYY-MM-DD_HHMMSS.log
    

    However, if you want to record the row-level changes, then you need an audit logging mechanism which can be implemented using triggers, as follows.

    Database tables

    Let's consider we have the following database tables:

    The book_audit_log is going to store all the changes that happen in the book table.

    The book_audit_log is created like this:

    CREATE TABLE IF NOT EXISTS book_audit_log (
        book_id bigint NOT NULL,
        old_row_data jsonb,
        new_row_data jsonb,
        dml_type dml_type NOT NULL,
        dml_timestamp timestamp NOT NULL,
        dml_created_by varchar(255) NOT NULL,
        PRIMARY KEY (book_id, dml_type, dml_timestamp)
    )
    

    The book_id column stores the identifier of the associated book table record that was inserted, updated, or deleted by the current executing DML statement.

    The old_row_data and new_row_data columns are of the JSONB type, and they will capture the state of the book row before and after the execution of the current INSERT, UPDATE, or DELETE statement.

    The dml_type column stores the type of the current executing DML statement (e.g., INSERT, UPDATE, and DELETE). The dml_type type is a PostgreSQL enumeration type, that was created like this:

    CREATE TYPE dml_type AS ENUM ('INSERT', 'UPDATE', 'DELETE')
    

    The dml_timestamp column stores the current timestamp.

    The dml_created_by column stores the application user who generated the current INSERT, UPDATE, or DELETE DML statement.

    PostgreSQL audit logging triggers

    To capture the INSERT, UPDATE, and DELETE DML statements on the book table, we need to create a trigger function that looks as follows:

    CREATE OR REPLACE FUNCTION book_audit_trigger_func()
    RETURNS trigger AS $body$
    BEGIN
       if (TG_OP = 'INSERT') then
           INSERT INTO book_audit_log (
               book_id,
               old_row_data,
               new_row_data,
               dml_type,
               dml_timestamp,
               dml_created_by
           )
           VALUES(
               NEW.id,
               null,
               to_jsonb(NEW),
               'INSERT',
               CURRENT_TIMESTAMP,
               current_setting('var.logged_user')
           );
                 
           RETURN NEW;
       elsif (TG_OP = 'UPDATE') then
           INSERT INTO book_audit_log (
               book_id,
               old_row_data,
               new_row_data,
               dml_type,
               dml_timestamp,
               dml_created_by
           )
           VALUES(
               NEW.id,
               to_jsonb(OLD),
               to_jsonb(NEW),
               'UPDATE',
               CURRENT_TIMESTAMP,
               current_setting('var.logged_user')
           );
                 
           RETURN NEW;
       elsif (TG_OP = 'DELETE') then
           INSERT INTO book_audit_log (
               book_id,
               old_row_data,
               new_row_data,
               dml_type,
               dml_timestamp,
               dml_created_by
           )
           VALUES(
               OLD.id,
               to_jsonb(OLD),
               null,
               'DELETE',
               CURRENT_TIMESTAMP,
               current_setting('var.logged_user')
           );
            
           RETURN OLD;
       end if;
         
    END;
    $body$
    LANGUAGE plpgsql;
    

    In order for the book_audit_trigger_func function to be executed after a book table record is inserted, updated or deleted, we have to define the following trigger:

    CREATE TRIGGER book_audit_trigger
    AFTER INSERT OR UPDATE OR DELETE ON book
    FOR EACH ROW EXECUTE FUNCTION book_audit_trigger_func();
    

    The dml_created_by column is set to the value of the var.logged_user PostgreSQL session variable, which was previously set by the application with the currently logged user, like this:

    SET LOCAL var.logged_user = 'Vlad Mihalcea'
    

    Testing time

    When executing an INSERT statement on the book table:

    INSERT INTO book (
        id,
        author, 
        price_in_cents, 
        publisher, 
        title
    ) 
    VALUES (
        1,
        'Vlad Mihalcea', 
        3990, 
        'Amazon', 
        'High-Performance Java Persistence 1st edition'
    )
    

    We can see that a record is inserted in the book_audit_log that captures the INSERT statement that was just executed on the book table:

    | book_id | old_row_data | new_row_data                                                                                                                                  | dml_type | dml_timestamp              | dml_created_by |
    |---------|--------------|-----------------------------------------------------------------------------------------------------------------------------------------------|----------|----------------------------|----------------|
    | 1       |              | {"id": 1, "title": "High-Performance Java Persistence 1st edition", "author": "Vlad Mihalcea", "publisher": "Amazon", "price_in_cents": 3990} | INSERT   | 2020-08-25 13:19:57.073026 | Vlad Mihalcea  |
    

    When updating the book table row:

    UPDATE book 
    SET price_in_cents = 4499 
    WHERE id = 1
    

    We can see that a new record is going to be added to the book_audit_log by the book_audit_trigger:

    | book_id | old_row_data                                                                                                                                  | new_row_data                                                                                                                                  | dml_type | dml_timestamp              | dml_created_by |
    |---------|-----------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------|----------|----------------------------|----------------|
    | 1       |                                                                                                                                               | {"id": 1, "title": "High-Performance Java Persistence 1st edition", "author": "Vlad Mihalcea", "publisher": "Amazon", "price_in_cents": 3990} | INSERT   | 2020-08-25 13:19:57.073026 | Vlad Mihalcea  |
    | 1       | {"id": 1, "title": "High-Performance Java Persistence 1st edition", "author": "Vlad Mihalcea", "publisher": "Amazon", "price_in_cents": 3990} | {"id": 1, "title": "High-Performance Java Persistence 1st edition", "author": "Vlad Mihalcea", "publisher": "Amazon", "price_in_cents": 4499} | UPDATE   | 2020-08-25 13:21:15.006365 | Vlad Mihalcea  |
    

    When deleting the book table row:

    DELETE FROM book 
    WHERE id = 1
    

    A new record is added to the book_audit_log by the book_audit_trigger:

    | book_id | old_row_data                                                                                                                                  | new_row_data                                                                                                                                  | dml_type | dml_timestamp              | dml_created_by |
    |---------|-----------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------|----------|----------------------------|----------------|
    | 1       |                                                                                                                                               | {"id": 1, "title": "High-Performance Java Persistence 1st edition", "author": "Vlad Mihalcea", "publisher": "Amazon", "price_in_cents": 3990} | INSERT   | 2020-08-25 13:19:57.073026 | Vlad Mihalcea  |
    | 1       | {"id": 1, "title": "High-Performance Java Persistence 1st edition", "author": "Vlad Mihalcea", "publisher": "Amazon", "price_in_cents": 3990} | {"id": 1, "title": "High-Performance Java Persistence 1st edition", "author": "Vlad Mihalcea", "publisher": "Amazon", "price_in_cents": 4499} | UPDATE   | 2020-08-25 13:21:15.006365 | Vlad Mihalcea  |
    | 1       | {"id": 1, "title": "High-Performance Java Persistence 1st edition", "author": "Vlad Mihalcea", "publisher": "Amazon", "price_in_cents": 4499} |                                                                                                                                               | DELETE   | 2020-08-25 13:21:58.499881 | Vlad Mihalcea  |
    
    0 讨论(0)
  • 2020-12-07 16:38

    Do you actually need the audit log of queries stored in a table? The easiest way to get a file with all the queries that have been executed is to use postgresql's built-in logging.

    In your postgresql.conf (usually in the $PG_DATA dir), set the following options appropriately:

    log_directory '/path/to/log/dir'
    log_filename = 'filename.log'
    log_statement = 'mod'
    

    That last option makes it log all the INSERT, UPDATE, DELETE, TRUNCATE, and COPY FROM statements.

    More details in the Postgres docs: http://www.postgresql.org/docs/current/static/runtime-config-logging.html

    0 讨论(0)
  • The link below should point you in the right direction.

    https://www.postgresql.org/docs/current/sql-createtrigger.html

    Depending on what you want to do, it probably is better to turn on logging.

    0 讨论(0)
  • 2020-12-07 16:46

    PostgreSQL Table Log by Andreas Scherbaum is a Postgresql extension that uses a trigger to log any INSERTs, UPDATEs and DELETEs on a specific table into another table.

    The usage is easy: you create a second table which has the same format like your table you want to keep an eye on. Plus you need some additional columns to maintain the logged data.

    The second part of tablelog is able to restore the state of the original table or of a specific row for any time in the past.

    I haven't tried it myself, but it's supposedly working.

    There's also slides from a talk about tablelog, but I can't post a proper link here due to some stackoverflow antispam weirdness :) (http :// andreas.scherbaum.la/writings/tablelog.pdf).

    http://pgfoundry.org/projects/tablelog/

    http:// andreas.scherbaum.la/blog/archives/100-Log-Table-Changes-in-PostgreSQL-with-tablelog.html

    0 讨论(0)
提交回复
热议问题