From 1eaa203eba2cbf41e47d57345961ca99b38020f7 Mon Sep 17 00:00:00 2001 From: yueli Date: Fri, 18 Sep 2026 09:27:17 +0800 Subject: [PATCH] =?UTF-8?q?fix(db):=20=E4=BF=AE=E6=AD=A3=E5=AD=98=E9=87=8F?= =?UTF-8?q?=E3=80=8C=E5=BD=92=E8=BF=98=E6=97=B6=E9=97=B4/=E6=8A=A5?= =?UTF-8?q?=E5=BA=9F=E6=97=B6=E9=97=B4=E3=80=8D=E7=9A=84=208=20=E5=B0=8F?= =?UTF-8?q?=E6=97=B6=E6=97=B6=E5=8C=BA=E5=81=8F=E5=B7=AE=EF=BC=88=E9=99=84?= =?UTF-8?q?=E8=87=AA=E6=A3=80=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 问题 ---- process_return 与 scrap_sources.deduct 原先用 datetime.now()(容器本地时间, Docker 默认 UTC)写 return_time / operation_time,而同一行的 borrow_time 由 beijing_time() 写入 —— 同一行两个时钟,差 8 小时。 实测(开发库)trans_borrow 有 53 行呈现「归还时间早于借出时间」这种物理上 不可能的数据,例如 id=80 借出 09-09 11:04、归还 09-09 03:46。 代码侧已修(f7c49f4),新数据已是北京时间 —— 本次只处理存量。 ★ 脚本刻意做了「先自检、后修正」的两段式 服务器 API 容器的 TZ 未必是 UTC(若本就是 Asia/Shanghai,datetime.now() 一直是北京时间,则**根本无需修正**)。若不做自检直接 +8h,会把正确的时间 改错。故第 0 步先输出:异常行数、以及按分界切出的「UTC 段/空档/北京段」 三段计数,由使用人据结果决定是否执行第 2 步。 ★ 分界与幂等性都在脚本里写明 · 分界 = 修复代码在该服务器**部署生效的时刻**(不是提交时刻),需按实际调整; · 脚本**不幂等** —— 重复执行会再加 8 小时,已显式警示并给出回滚写法。 开发库已按此逻辑修正:53 行 trans_borrow + 1 行 trans_scrap, 修正后「归还早于借出」为 0,抽样时间落在正常工作时段。 --- db_migrations/phase9_fix_return_time_utc.sql | 112 +++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 db_migrations/phase9_fix_return_time_utc.sql diff --git a/db_migrations/phase9_fix_return_time_utc.sql b/db_migrations/phase9_fix_return_time_utc.sql new file mode 100644 index 0000000..6af12f5 --- /dev/null +++ b/db_migrations/phase9_fix_return_time_utc.sql @@ -0,0 +1,112 @@ +-- ============================================================================= +-- 修正存量「归还时间 / 报废时间」的 8 小时时区偏差 +-- +-- 问题 +-- process_return 与 scrap_sources.deduct 原先用 datetime.now() 写 return_time / +-- operation_time,取的是**容器本地时间**(Docker 默认 UTC);而同一行的 +-- borrow_time 由 beijing_time() 写入。同一行两个时钟,差 8 小时。 +-- 实测(开发库):53 行 trans_borrow 出现「归还时间早于借出时间」这种 +-- 物理上不可能的数据,例如 id=80 借出 09-09 11:04、归还 09-09 03:46。 +-- +-- 代码侧已修 +-- 提交 f7c49f4(2026-09-17 09:18 北京时间)起改用 beijing_time(), +-- **新写入的数据已经是北京时间**(已验证 TransReturn / TransBorrowReturn / +-- TransScrap 三张表的模型默认值均为北京)。本脚本只处理存量。 +-- +-- --------------------------------------------------------------------------- +-- ★★ 执行前必读:请先跑「第 0 步 自检」,据其结果决定是否执行 ★★ +-- +-- 1) 若自检的「异常行数」为 0,且 UTC 区间计数为 0 —— 说明**本服务器无此问题** +-- (例如 API 容器的 TZ 本就是 Asia/Shanghai,datetime.now() 一直是北京时间), +-- **不要执行第 2 步**,否则会把正确的时间又 +8 小时。 +-- +-- 2) 分界时间 @boundary 的含义:**修复代码在本服务器部署生效的时刻**。 +-- · 该时刻之前写入的行 → UTC,需 +8h; +-- · 该时刻之后写入的行 → 北京时间,不能动。 +-- 开发库用的是提交时间 2026-09-17 09:18(北京)= 01:18(UTC), +-- 两组数据之间通常有一段空档,请用自检查看空档是否存在。 +-- 若你的部署时间不同,请替换下方两处 '2026-09-17 01:18:00'。 +-- +-- 幂等性 +-- ⚠ 本脚本**不幂等** —— 重复执行会把已经修正过的时间再加 8 小时。 +-- 请在执行前确认已备份,且只执行一次。 +-- +-- 回滚 +-- 把两处 `+ interval '8 hours'` 改成 `- interval '8 hours'`,用同一条件再执行一次。 +-- ============================================================================= + + +-- ============================================================================= +-- 第 0 步:自检(只读,先跑这一段看结果) +-- ============================================================================= +SELECT '=== 0.1) 物理上不可能的「归还早于借出」行数 ===' AS "核对项"; +SELECT count(*) AS 异常行数 + FROM trans_borrow + WHERE return_time IS NOT NULL AND borrow_time IS NOT NULL + AND return_time < borrow_time; + +SELECT '=== 0.2) 按分界切分:UTC 段 / 空档 / 北京段 ===' AS "核对项"; +SELECT + count(*) FILTER (WHERE return_time < '2026-09-17 01:18:00') AS UTC段_需修正, + count(*) FILTER (WHERE return_time >= '2026-09-17 01:18:00' + AND return_time < '2026-09-17 09:18:00') AS 空档_应为0, + count(*) FILTER (WHERE return_time >= '2026-09-17 09:18:00') AS 北京段_不可动 + FROM trans_borrow + WHERE return_time IS NOT NULL; + +SELECT '=== 0.3) trans_scrap 同样切分 ===' AS "核对项"; +SELECT + count(*) FILTER (WHERE operation_time < '2026-09-17 01:18:00') AS UTC段_需修正, + count(*) FILTER (WHERE operation_time >= '2026-09-17 01:18:00' + AND operation_time < '2026-09-17 09:18:00') AS 空档_应为0, + count(*) FILTER (WHERE operation_time >= '2026-09-17 09:18:00') AS 北京段_不可动 + FROM trans_scrap; + +-- 判读: +-- · 0.1 为 0 且 0.2/0.3 的 UTC 段也为 0 → 无需修正,停止。 +-- · 0.1 大于 0 → 确有此问题,可继续第 2 步。 + + +-- ============================================================================= +-- 第 1 步:备份(强烈建议) +-- ============================================================================= +-- CREATE TABLE trans_borrow_bak_20260918 AS +-- SELECT id, return_time, return_operator FROM trans_borrow; +-- CREATE TABLE trans_scrap_bak_20260918 AS +-- SELECT id, operation_time FROM trans_scrap; + + +-- ============================================================================= +-- 第 2 步:修正(确认第 0 步结果后再执行) +-- ============================================================================= +BEGIN; + +UPDATE trans_borrow + SET return_time = return_time + interval '8 hours' + WHERE return_time IS NOT NULL + AND return_time < '2026-09-17 01:18:00'; -- ← 替换为你的部署时刻(UTC 口径) + +UPDATE trans_scrap + SET operation_time = operation_time + interval '8 hours' + WHERE operation_time < '2026-09-17 01:18:00'; -- ← 同上 + +COMMIT; + + +-- ============================================================================= +-- 第 3 步:执行后核对 +-- ============================================================================= +SELECT '=== 3.1) 异常行数应为 0 ===' AS "核对项"; +SELECT count(*) AS 异常行数 + FROM trans_borrow + WHERE return_time IS NOT NULL AND borrow_time IS NOT NULL + AND return_time < borrow_time; + +SELECT '=== 3.2) 抽样:归还时间应晚于借出时间,且落在工作时段 ===' AS "核对项"; +SELECT id, + to_char(borrow_time, 'MM-DD HH24:MI') AS 借出, + to_char(return_time, 'MM-DD HH24:MI') AS 归还 + FROM trans_borrow + WHERE return_time IS NOT NULL + ORDER BY return_time DESC + LIMIT 8;