Repo .git was damaged (refs/ missing, index truncated, object store near-empty). Repaired via git init (rebuilt refs), stale index preserved as .git/index.corrupt-20260910.bak, then full worktree committed as a rollback baseline.
125 lines
6.2 KiB
SQL
125 lines
6.2 KiB
SQL
-- ============================================================================
|
||
-- TamaBox 存量时间 +8h 漂移反向修正脚本(线上执行版)
|
||
-- 背景: 先前 timestamptz 迁移用 `AT TIME ZONE 'UTC'` 把存量"上海墙钟"误标为
|
||
-- UTC,导致全部存量时间绝对时刻 +8h(页面显示比真实本地时间晚 8 小时)。
|
||
-- 本脚本: 把当前值按 UTC 还原出原墙钟,再按 Asia/Shanghai 重新解释(净效果
|
||
-- -8h 绝对时刻,会话显示回到真实本地墙钟)。与会话时区无关。
|
||
-- 影响范围: nekobox.questions / users / censor_logs / upload_images 的
|
||
-- created_at / updated_at / deleted_at(12 列,全为 timestamptz)。
|
||
-- 安全: 全程无 DROP 业务对象;仅 CREATE SCHEMA/临时快照表 + ALTER TYPE。
|
||
-- 回滚方式见 PART 4(注释)。
|
||
-- 预期: PART 3 各项判读通过即成功;异常则执行 PART 4 回滚后贴回输出。
|
||
-- ============================================================================
|
||
|
||
-- PART 0: 核对当前状态 -------------------------------------------------------
|
||
-- 确认处于"错误状态"(时间列全为 timestamptz 且显示比真实晚 8h)。
|
||
-- 应输出 12 列 timestamptz; 3089 应显示 08:01:16(错误态) 而非 00:01:16。
|
||
SET TIME ZONE 'Asia/Shanghai';
|
||
SELECT 'PART 0: 当前状态核对(仅本次修正涉及的4表12列)' AS step;
|
||
SELECT table_name, column_name, data_type
|
||
FROM information_schema.columns
|
||
WHERE table_schema = 'nekobox'
|
||
AND table_name IN ('questions','users','censor_logs','upload_images')
|
||
AND column_name IN ('created_at','updated_at','deleted_at')
|
||
ORDER BY table_name, column_name;
|
||
SELECT id, created_at AS now_display, (extract(epoch FROM created_at)*1000000000)::numeric(30,0) AS epoch_ns
|
||
FROM nekobox.questions WHERE id = 3089;
|
||
|
||
-- PART 1: 迁移前快照 ----------------------------------------------------------
|
||
CREATE SCHEMA IF NOT EXISTS diag_test;
|
||
DROP TABLE IF EXISTS diag_test.fix_before;
|
||
CREATE TABLE diag_test.fix_before(tbl text, col text, n bigint, s numeric);
|
||
DO $$
|
||
DECLARE r record;
|
||
BEGIN
|
||
FOR r IN
|
||
SELECT table_name tbl, column_name col
|
||
FROM information_schema.columns
|
||
WHERE table_schema='nekobox' AND data_type='timestamp with time zone'
|
||
AND column_name IN ('created_at','updated_at','deleted_at')
|
||
AND table_name IN ('questions','users','censor_logs','upload_images')
|
||
ORDER BY table_name, column_name
|
||
LOOP
|
||
EXECUTE format(
|
||
'INSERT INTO diag_test.fix_before SELECT %L, %L, count(%I), sum(extract(epoch FROM %I)*1000000000) FROM nekobox.%I',
|
||
r.tbl, r.col, r.col, r.col, r.tbl);
|
||
END LOOP;
|
||
END $$;
|
||
SELECT 'PART 1: 快照完成' AS step, count(*) AS cols FROM diag_test.fix_before;
|
||
|
||
-- PART 2: 反向修正 ------------------------------------------------------------
|
||
-- 核心: 原墙钟按 Asia/Shanghai 解释。净效果 = 绝对时刻 -8h, 会话显示回本地墙钟。
|
||
SELECT 'PART 2: 反向修正执行' AS step;
|
||
DO $$
|
||
DECLARE r record;
|
||
BEGIN
|
||
FOR r IN
|
||
SELECT table_name tbl, column_name col
|
||
FROM information_schema.columns
|
||
WHERE table_schema='nekobox' AND data_type='timestamp with time zone'
|
||
AND column_name IN ('created_at','updated_at','deleted_at')
|
||
AND table_name IN ('questions','users','censor_logs','upload_images')
|
||
ORDER BY table_name, column_name
|
||
LOOP
|
||
EXECUTE format(
|
||
'ALTER TABLE nekobox.%I ALTER COLUMN %I TYPE timestamptz USING (%I AT TIME ZONE ''UTC'' AT TIME ZONE ''Asia/Shanghai'')',
|
||
r.tbl, r.col, r.col);
|
||
END LOOP;
|
||
END $$;
|
||
SELECT '修正完成' AS step;
|
||
|
||
-- PART 3: 验收 -----------------------------------------------------------------
|
||
-- 3.1 关键行: 会话上海显示应回到真实本地墙钟(3089=00:01:16.678885+08, epoch 1788537676678885000)
|
||
DROP TABLE IF EXISTS diag_test.fix_after;
|
||
CREATE TABLE diag_test.fix_after(tbl text, col text, n bigint, s numeric);
|
||
DO $$
|
||
DECLARE r record;
|
||
BEGIN
|
||
FOR r IN
|
||
SELECT table_name tbl, column_name col
|
||
FROM information_schema.columns
|
||
WHERE table_schema='nekobox' AND data_type='timestamp with time zone'
|
||
AND column_name IN ('created_at','updated_at','deleted_at')
|
||
AND table_name IN ('questions','users','censor_logs','upload_images')
|
||
ORDER BY table_name, column_name
|
||
LOOP
|
||
EXECUTE format(
|
||
'INSERT INTO diag_test.fix_after SELECT %L, %L, count(%I), sum(extract(epoch FROM %I)*1000000000) FROM nekobox.%I',
|
||
r.tbl, r.col, r.col, r.col, r.tbl);
|
||
END LOOP;
|
||
END $$;
|
||
SELECT '3.1 修正后关键行(期望 3089=2026-09-05 00:01:16.678885+08 / epoch 1788537676678885000)' AS check_name;
|
||
SELECT id, created_at AS val_after, (extract(epoch FROM created_at)*1000000000)::numeric(30,0) AS epoch_ns
|
||
FROM nekobox.questions WHERE id IN (3089,3109,2411) ORDER BY id;
|
||
|
||
-- 3.2 keyset 翻页抽查(期望第2页 3088,2883..2411 共18条)
|
||
SELECT '3.2 keyset 第2页抽查' AS check_name;
|
||
SELECT q.id
|
||
FROM nekobox.questions q
|
||
WHERE q.user_id = 329 AND q.deleted_at IS NULL AND q.answer <> '' AND q.is_private = false
|
||
AND q.is_pinned = false
|
||
AND (q.created_at < TIMESTAMPTZ '2026-09-05 00:01:16.678885+08'
|
||
OR (q.created_at = TIMESTAMPTZ '2026-09-05 00:01:16.678885+08' AND q.id < 3089))
|
||
ORDER BY q.is_pinned DESC, q.created_at DESC, q.id DESC
|
||
LIMIT 20;
|
||
|
||
-- 3.3 全表差异留档(修正前后仅整体偏移, n 与结构不变; 供留档)
|
||
SELECT '3.3 修正前后列计数(应逐列一致)' AS check_name;
|
||
SELECT b.tbl, b.col, b.n AS n_before, a.n AS n_after
|
||
FROM diag_test.fix_before b JOIN diag_test.fix_after a USING (tbl,col)
|
||
WHERE b.n IS DISTINCT FROM a.n;
|
||
|
||
-- 3.4 列类型(仍应为 timestamptz, 残余 without=0)
|
||
SELECT '3.4 残余 timestamp without time zone(应0)' AS check_name;
|
||
SELECT count(*) AS remain_without
|
||
FROM information_schema.columns
|
||
WHERE table_schema='nekobox' AND data_type='timestamp without time zone'
|
||
AND column_name IN ('created_at','updated_at','deleted_at');
|
||
|
||
-- PART 4: 回滚(需要时才执行; 当前被注释) ---------------------------------------
|
||
-- 若修正后发现问题需回到"错误态"(UTC 基准):
|
||
-- 把 PART 2 的 USING 子句换成 (%I AT TIME ZONE 'Asia/Shanghai' AT TIME ZONE 'UTC')
|
||
-- 即: ALTER TABLE nekobox.xxx ALTER COLUMN c TYPE timestamptz
|
||
-- USING (c AT TIME ZONE 'Asia/Shanghai' AT TIME ZONE 'UTC');
|
||
-- 可先对比 diag_test.fix_before 快照确认还原。
|