如何使用Postgres触发器存储更改

jDe*_*ter 16 postgresql logging triggers

是否可以在PostgreSQL中使用触发器来创建更新并插入已发生在表中的更改的sql语句,并将它们记录到文件中以便以后执行.

这只是暂时使用,所以只是快速和肮脏的东西.

nos*_*nos 32

来自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();
Run Code Online (Sandbox Code Playgroud)

  • 请注意,`TG_OP = 'DELETE'` 等必须使用大写的 `'DELETE'` 等。我把所有内容都小写了,想知道哪里出了问题。 (2认同)

Dav*_*dge 10

您是否确实需要存储在表中的查询的审核日志?获取包含所有已执行查询的文件的最简单方法是使用postgresql的内置日志记录.

在postgresql.conf中(通常在$ PG_DATA目录中),适当地设置以下选项:

log_directory '/path/to/log/dir'
log_filename = 'filename.log'
log_statement = 'mod'
Run Code Online (Sandbox Code Playgroud)

最后一个选项使它记录所有INSERT,UPDATE,DELETE,TRUNCATE和COPY FROM语句.

Postgres文档中的更多细节:http://www.postgresql.org/docs/current/static/runtime-config-logging.html


Vla*_*cea 7

SQL日志

如果您只对执行的语句感兴趣,那么您可以简单地激活 PostgreSQL 语句日志。

为此,打开postgresql.conf文件并设置以下配置属性:

log_statement = 'all' 
Run Code Online (Sandbox Code Playgroud)

之后,您将看到记录在以下路径下的文件中的 SQL 语句:

$PG_DATA/pg_log/postgresql-YYYY-MM-DD_HHMMSS.log
Run Code Online (Sandbox Code Playgroud)

但是,如果您想记录行级别的更改,那么您需要一个可以使用触发器实现的审计日志记录机制,如下所示。

数据库表

假设我们有以下数据库表:

book 和 book_audit_log 表

book_audit_log将要存储在所发生的所有变化book表。

book_audit_log创建这样的:

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)
)
Run Code Online (Sandbox Code Playgroud)

book_id列存储由当前执行的 DML 语句插入、更新或删除的关联书表记录的标识符。

old_row_datanew_row_data列是JSONB类型,他们将捕获的书行的状态之前和当前的INSERT,UPDATE或DELETE语句执行之后。

dml_type列存储当前正在执行的 DML 语句的类型(例如,INSERT、UPDATE 和 DELETE)。dml_type 类型是一个 PostgreSQL 枚举类型,它是这样创建的:

CREATE TYPE dml_type AS ENUM ('INSERT', 'UPDATE', 'DELETE')
Run Code Online (Sandbox Code Playgroud)

dml_timestamp列存储当前时间戳。

dml_created_by列存储生成当前 INSERT、UPDATE 或 DELETE DML 语句的应用程序用户。

PostgreSQL 审计日志触发器

为了捕获 book 表上的 INSERT、UPDATE 和 DELETE DML 语句,我们需要创建一个如下所示的触发器函数:

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;
Run Code Online (Sandbox Code Playgroud)

为了在book_audit_trigger_func插入、更新或删除图书表记录后执行该函数,我们必须定义以下触发器:

CREATE TRIGGER book_audit_trigger
AFTER INSERT OR UPDATE OR DELETE ON book
FOR EACH ROW EXECUTE FUNCTION book_audit_trigger_func();
Run Code Online (Sandbox Code Playgroud)

dml_created_by列设置为var.logged_userPostgreSQL 会话变量的值,该值之前由应用程序使用当前登录的用户设置,如下所示:

SET LOCAL var.logged_user = 'Vlad Mihalcea'
Run Code Online (Sandbox Code Playgroud)

测试时间

在表上执行 INSERT 语句时book

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

我们可以看到在book_audit_log捕获刚刚在book表上执行的INSERT语句的记录中插入了一条记录:

| 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  |
Run Code Online (Sandbox Code Playgroud)

更新book表格行时:

UPDATE book 
SET price_in_cents = 4499 
WHERE id = 1
Run Code Online (Sandbox Code Playgroud)

我们可以看到,一个新的记录将被添加到book_audit_logbook_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  |
Run Code Online (Sandbox Code Playgroud)

删除book表格行时:

DELETE FROM book 
WHERE id = 1
Run Code Online (Sandbox Code Playgroud)

一个新的记录添加到book_audit_logbook_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  |
Run Code Online (Sandbox Code Playgroud)

  • 这是一个非常好的问题。即使列是“jsonb”并且我使用“row_to_json”,它也能正常工作,如[GitHub 上的测试用例](https://github.com/vladmihalcea/high-performance-java-persistence/blob) 所示/b9631f1714828d002a9e8ff42fada3cfd87fabe8/core/src/test/java/com/vladmihalcea/book/hpjp/hibernate/audit/trigger/PostgreSQLTriggerBasedJsonAuditLogTest.java#L98)。我也会尝试使用“to_jsonb()”。 (2认同)

小智 5

下面的链接应该为您指明正确的方向。

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

根据您想要做什么,最好打开日志记录。