使用 PostgreSQL 触发器,是否可以记录由于 INSERT 或 UPDATE SQL 语句而对表发生的更改并将它们记录到文件中以供以后执行。
这只是暂时使用,所以只是一些快速和肮脏的东西会做。
使用 PostgreSQL 触发器,是否可以记录由于 INSERT 或 UPDATE SQL 语句而对表发生的更改并将它们记录到文件中以供以后执行。
这只是暂时使用,所以只是一些快速和肮脏的东西会做。
来自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();
你真的需要存储在表中的查询的审计日志吗?获取包含所有已执行查询的文件的最简单方法是使用 postgresql 的内置日志记录。
在您的 postgresql.conf 中(通常在 $PG_DATA 目录中),适当地设置以下选项:
log_directory '/path/to/log/dir'
log_filename = 'filename.log'
log_statement = 'mod'
最后一个选项使它记录所有 INSERT、UPDATE、DELETE、TRUNCATE 和 COPY FROM 语句。
Postgres 文档中的更多详细信息:http ://www.postgresql.org/docs/current/static/runtime-config-logging.html
如果您只对执行的语句感兴趣,那么您可以简单地激活 PostgreSQL 语句日志。
为此,打开postgresql.conf
文件并设置以下配置属性:
log_statement = 'all'
之后,您将看到 SQL 语句记录在以下路径下的文件中:
$PG_DATA/pg_log/postgresql-YYYY-MM-DD_HHMMSS.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)
)
该book_id
列存储由当前执行的 DML 语句插入、更新或删除的关联书表记录的标识符。
和列是 JSONB 类型old_row_data
,new_row_data
它们将捕获当前 INSERT、UPDATE 或 DELETE 语句执行前后书行的状态。
该dml_type
列存储当前执行的 DML 语句的类型(例如,INSERT、UPDATE 和 DELETE)。dml_type 类型是 PostgreSQL 枚举类型,它是这样创建的:
CREATE TYPE dml_type AS ENUM ('INSERT', 'UPDATE', 'DELETE')
该dml_timestamp
列存储当前时间戳。
该dml_created_by
列存储生成当前 INSERT、UPDATE 或 DELETE DML 语句的应用程序用户。
要捕获 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;
为了在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();
该dml_created_by
列设置为var.logged_user
PostgreSQL 会话变量的值,该变量先前由应用程序使用当前登录的用户设置,如下所示:
SET LOCAL var.logged_user = 'Vlad Mihalcea'
在book
表上执行 INSERT 语句时:
INSERT INTO book (
id,
author,
price_in_cents,
publisher,
title
)
VALUES (
1,
'Vlad Mihalcea',
3990,
'Amazon',
'High-Performance Java Persistence 1st edition'
)
我们可以看到插入了一条记录,该记录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 |
更新book
表格行时:
UPDATE book
SET price_in_cents = 4499
WHERE id = 1
我们可以看到一条新记录将被添加book_audit_log
到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 |
删除book
表格行时:
DELETE FROM book
WHERE id = 1
一条新记录被添加book_audit_log
到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 |
Andreas Scherbaum 的 PostgreSQL Table Log 是一个 Postgresql 扩展,它使用触发器将特定表上的任何 INSERT、UPDATE 和 DELETE 记录到另一个表中。
用法很简单:您创建第二个表格,其格式与您要关注的表格相同。另外,您需要一些额外的列来维护记录的数据。
tablelog 的第二部分能够恢复原始表或过去任何时间特定行的状态。
我自己没有尝试过,但它应该有效。
还有关于 tablelog 的讨论的幻灯片,但由于一些 stackoverflow 反垃圾邮件的怪异,我无法在此处发布正确的链接 :) (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