Ben*_*wis 7 postgresql timestamp transactions plpgsql sequelize.js
在 Postgres 10.10 数据库中,我有一个 tabletable1和一个AFTER INSERT触发器table1for table2:
CREATE TABLE table1 (
id SERIAL PRIMARY KEY,
-- other cols
created_at timestamp with time zone NOT NULL,
updated_at timestamp with time zone NOT NULL
);
CREATE UNIQUE INDEX table1_pkey ON table1(id int4_ops);
CREATE TABLE table2 (
id SERIAL PRIMARY KEY,
table1_id integer NOT NULL REFERENCES table1(id) ON UPDATE CASCADE,
-- other cols (not used in query)
created_at timestamp with time zone NOT NULL,
updated_at timestamp with time zone NOT NULL
);
CREATE UNIQUE INDEX table2_pkey ON table2(id int4_ops);
Run Code Online (Sandbox Code Playgroud)
此查询在应用程序启动时执行:
CREATE OR REPLACE FUNCTION after_insert_table1()
RETURNS trigger AS
$$
BEGIN
INSERT INTO table2 (table1_id, ..., created_at, updated_at)
VALUES (NEW.id, ..., 'now', 'now');
RETURN NEW;
END;
$$
LANGUAGE 'plpgsql';
DROP TRIGGER IF EXISTS after_insert_table1 ON "table1";
CREATE TRIGGER after_insert_table1
AFTER INSERT ON "table1"
FOR EACH ROW
EXECUTE PROCEDURE after_insert_table1();
Run Code Online (Sandbox Code Playgroud)
我注意到 somecreated_at和 上的updated_at值table2与table1. 事实上,table2大多具有较旧的值。
这里有 10 个连续的条目,它们显示了在几分钟内大幅波动的差异:
|table1_id|table1_created |table2_created |diff |
|---------|--------------------------|-----------------------------|----------------|
|2000 |2019-11-07 22:29:47.245+00|2019-11-07 19:51:09.727021+00|-02:38:37.517979|
|2001 |2019-11-07 22:30:02.256+00|2019-11-07 13:18:29.45962+00 |-09:11:32.79638 |
|2002 |2019-11-07 22:30:43.021+00|2019-11-07 13:44:12.099577+00|-08:46:30.921423|
|2003 |2019-11-07 22:31:00.794+00|2019-11-07 19:51:09.727021+00|-02:39:51.066979|
|2004 |2019-11-07 22:31:11.315+00|2019-11-07 13:18:29.45962+00 |-09:12:41.85538 |
|2005 |2019-11-07 22:31:27.234+00|2019-11-07 13:44:12.099577+00|-08:47:15.134423|
|2006 |2019-11-07 22:31:47.436+00|2019-11-07 13:18:29.45962+00 |-09:13:17.97638 |
|2007 |2019-11-07 22:33:19.484+00|2019-11-07 17:22:48.129063+00|-05:10:31.354937|
|2008 |2019-11-07 22:33:51.607+00|2019-11-07 19:51:09.727021+00|-02:42:41.879979|
|2009 |2019-11-07 22:34:28.786+00|2019-11-07 13:18:29.45962+00 |-09:15:59.32638 |
|2010 |2019-11-07 22:36:50.242+00|2019-11-07 13:18:29.45962+00 |-09:18:20.78238 |
Run Code Online (Sandbox Code Playgroud)
顺序条目在序列中具有相似的差异(主要是负/主要是正)和相似的数量级(主要是几分钟 vs 主要是几小时),但也有例外
以下是前 5 个最大的积极差异:
|table1_id|table1_created |table2_created |diff |
|---------|--------------------------|-----------------------------|----------------|
|1630 |2019-10-25 21:12:14.971+00|2019-10-26 00:52:09.376+00 |03:39:54.405 |
|950 |2019-09-16 12:36:07.185+00|2019-09-16 14:07:35.504+00 |01:31:28.319 |
|1677 |2019-10-26 22:19:12.087+00|2019-10-26 23:38:34.102+00 |01:19:22.015 |
|58 |2018-12-08 20:11:20.306+00|2018-12-08 21:06:42.246+00 |00:55:21.94 |
|171 |2018-12-17 22:24:57.691+00|2018-12-17 23:16:05.992+00 |00:51:08.301 |
Run Code Online (Sandbox Code Playgroud)
以下是前 5 个最大的负面差异:
|table1_id|table1_created |table2_created |diff |
|---------|--------------------------|-----------------------------|----------------|
|1427 |2019-10-15 16:03:43.641+00|2019-10-14 17:59:41.57749+00 |-22:04:02.06351 |
|1426 |2019-10-15 13:26:07.314+00|2019-10-14 18:00:50.930513+00|-19:25:16.383487|
|1424 |2019-10-15 13:13:44.092+00|2019-10-14 18:00:50.930513+00|-19:12:53.161487|
|4416 |2020-01-11 00:15:03.751+00|2020-01-10 08:43:19.668399+00|-15:31:44.082601|
|4420 |2020-01-11 01:58:32.541+00|2020-01-10 11:04:19.288023+00|-14:54:13.252977|
Run Code Online (Sandbox Code Playgroud)
负差异的数量超过正差异的 10 倍。数据库时区是 UTC。
table2.table1_id是外键,因此在插入table1完成之前应该不可能插入。
table1.created_at由 Sequelize 设置,使用timestamps: true模型上的选项。
当一行插入到 中时table1,它是在事务中完成的。从我可以找到的文档中,触发器是在同一个事务中执行的,所以我想不出这样做的原因。
我可以通过将触发器更改为使用NEW.created_at而不是“现在”来解决此问题,但我很好奇是否有人知道此错误的原因是什么?
这是用于生成上述差异表的查询:
SELECT
table1.id AS table1_id,
table1.created_at AS table1_created,
table2.created_at AS table2_created,
(table2.created_at - table1.created_at) AS diff
FROM table1
INNER JOIN table2 ON
table2.table1_id = table1.id AND (
(table2.created_at - table1.created_at) > '2 min' OR
(table1.created_at - table2.created_at) > '2 min')
ORDER BY diff;
Run Code Online (Sandbox Code Playgroud)
虽然'now'不是纯字符串,但在此上下文中也不是函数,而是特殊的日期/时间输入。手册:
...简单的符号简写,读取时将转换为普通的日期/时间值。(特别是,
now相关字符串一旦被读取就会被转换为特定的时间值。)
PL/pgSQL 函数的主体存储为字符串,每个会话的控制权第一次到达时,每个嵌套的 SQL 命令都会被解析和准备。手册:
PL/pgSQL 解释器解析函数的源文本并在第一次调用函数时(在每个会话中)生成一个内部二进制指令树。指令树完全翻译了 PL/pgSQL 语句结构,但函数中使用的单个 SQL 表达式和 SQL 命令不会立即翻译。
由于每个表达式和 SQL 命令首先在函数中执行,PL/pgSQL 解释器使用 SPI 管理器的
SPI_prepare函数解析和分析命令以创建准备好的语句。对该表达式或命令的后续访问会重用准备好的语句。
还有更多。继续阅读。但这对于我们的案例来说已经足够了:
每个会话第一次执行触发器时,'now'将转换为当前时间戳(事务时间戳)。在同一个事务中进行更多插入时,不会有任何区别,transaction_timestamp()因为这在设计上是稳定的。但是同一会话中的每个后续事务都会在 中插入相同的常量时间戳table2,而 的值table1可能是任何东西(不确定 Sequelize 在那里做了什么)。如果新值table1是当时的当前时间戳,则会导致测试中出现“负”差异。(时间戳table2会更旧。)
您真正想要的情况很少见。通常,您需要函数'now'now()(没有单引号!) - 相当于CURRENT_TIMESTAMP(标准 SQL)和transaction_timestamp(). 相关(推荐阅读!):
在您的特定情况下,我建议使用列默认值而不是在触发器中做额外的工作。如果您now()在table1and 中设置相同的默认值table2,您还可以消除INSERTtotable1可能添加的任何废话。而且您甚至不必再在插入中提及这些列:
CREATE TABLE table1 (
id SERIAL PRIMARY KEY,
-- other cols
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now() -- or leave this one NULL?
);
CREATE TABLE table2 (
id SERIAL PRIMARY KEY,
table1_id integer NOT NULL REFERENCES table1(id) ON UPDATE CASCADE,
-- other cols (not used in query)
created_at timestamptz NOT NULL DEFAULT now(), -- not 'now'!
updated_at timestamptz NOT NULL DEFAULT now() -- or leave this one NULL?
);
CREATE OR REPLACE FUNCTION after_insert_table1()
RETURNS trigger LANGUAGE plpgsql AS
$$
BEGIN
INSERT INTO table2 (table1_id) -- more columns? but not: created_at, updated_at
VALUES (NEW.id); -- more columns?
RETURN NULL; -- can be NULL for AFTER trigger
END
$$;Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
822 次 |
| 最近记录: |