Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
P
pac
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
ai-tools
pac
Commits
73cdfb2e
Commit
73cdfb2e
authored
Jul 28, 2026
by
luoqi
Browse files
Options
Browse Files
Download
Plain Diff
merge: fix/migrate-concurrently-txn → test(解 migrate 卡死:多语句迁移不能写 CONCURRENTLY)
parents
765f70c1
8dba340f
Pipeline
#3465
failed in 0 seconds
Changes
3
Pipelines
1
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
45 additions
and
23 deletions
+45
-23
apps/pac-service/prisma/migrations/20260727070000_add_patient_transactions_sync_log_id_idx/migration.sql
+7
-3
apps/pac-service/prisma/migrations/20260727110000_add_patient_transactions_host_subject_patient_idx/migration.sql
+3
-2
apps/pac-service/prisma/migrations/20260728020000_persona_features_tag_filter_indexes/migration.sql
+35
-18
No files found.
apps/pac-service/prisma/migrations/20260727070000_add_patient_transactions_sync_log_id_idx/migration.sql
View file @
73cdfb2e
...
@@ -20,9 +20,13 @@
...
@@ -20,9 +20,13 @@
-- 故用 CONCURRENTLY(不锁写)。
-- 故用 CONCURRENTLY(不锁写)。
--
--
-- ⚠️ 关于「Prisma migrate 有事务包裹所以跑不了 CONCURRENTLY」——
-- ⚠️ 关于「Prisma migrate 有事务包裹所以跑不了 CONCURRENTLY」——
-- 那是 20260701060607 里的说法,**在当前 Prisma 6.19 已不成立**:2026-07-27 本地实测
-- 本条实测能随 migrate deploy 跑通,但**别把它总结成「Prisma 新版已经没事务包裹了」**
-- `prisma migrate deploy` 直接把本文件跑通、索引建出、plan 生效。所以本条**可以**随
-- (本文件原来就是这么写的,错的归因,2026-07-28 已订正)。
-- migrate deploy 走,不需要 `migrate resolve` 那套。
-- 真正的分界线是**本文件里的语句条数**:Prisma 把整份 migration.sql 用一次 simple query
-- 发给 Postgres,而 Postgres 只对「一个查询串里多条语句」隐式开事务块。
-- 本文件只有 1 条语句 → 没有隐式事务 → CONCURRENTLY 跑得通;
-- 多条语句的迁移里写 CONCURRENTLY 必挂 25001,还会把 _prisma_migrations 堵死(P3018)。
-- 2026-07-28 的 20260728020000 就是这么炸的,那份文件头有完整复盘。
--
--
-- 但**推荐仍手动先建**,理由与事务无关,是停机时间:
-- 但**推荐仍手动先建**,理由与事务无关,是停机时间:
-- deploy 时 pac-service 通过 depends_on 等 pac-migrate 退出才启动,而 33/64GB 建索引要几分钟
-- deploy 时 pac-service 通过 depends_on 等 pac-migrate 退出才启动,而 33/64GB 建索引要几分钟
...
...
apps/pac-service/prisma/migrations/20260727110000_add_patient_transactions_host_subject_patient_idx/migration.sql
View file @
73cdfb2e
...
@@ -22,8 +22,9 @@
...
@@ -22,8 +22,9 @@
--
--
-- 【CONCURRENTLY 与上线方式】同 20260727070000(sync_log_id 那条):
-- 【CONCURRENTLY 与上线方式】同 20260727070000(sync_log_id 那条):
-- 普通 CREATE INDEX 会 ACCESS EXCLUSIVE 锁全表数分钟,摄入与 push 全挂,故用 CONCURRENTLY。
-- 普通 CREATE INDEX 会 ACCESS EXCLUSIVE 锁全表数分钟,摄入与 push 全挂,故用 CONCURRENTLY。
-- 当前 Prisma 6.19 可以直接 `migrate deploy` 跑通(20260701060607 里"有事务包裹跑不了"的
-- 本条可以直接 `migrate deploy` 跑通 —— 但原因是**本文件只有 1 条语句**(Postgres 只对
-- 说法已不成立,2026-07-27 实测)。但**推荐部署前手动先建**:deploy 时 pac-service 要等
-- 多语句查询串隐式开事务块),不是「Prisma 新版没有事务包裹了」。原注释那句错误归因
-- 2026-07-28 已订正,详见 20260727070000 与 20260728020000 的文件头。但**推荐部署前手动先建**:deploy 时 pac-service 要等
-- pac-migrate 退出才启动,几分钟索引构建 = 白等的停机。手动建好后本条 IF NOT EXISTS 空跑:
-- pac-migrate 退出才启动,几分钟索引构建 = 白等的停机。手动建好后本条 IF NOT EXISTS 空跑:
-- docker exec pac-postgres-1 psql -U pac -d pac -c \
-- docker exec pac-postgres-1 psql -U pac -d pac -c \
-- 'CREATE INDEX CONCURRENTLY IF NOT EXISTS "patient_transactions_host_id_subject_type_patient_id_idx" \
-- 'CREATE INDEX CONCURRENTLY IF NOT EXISTS "patient_transactions_host_id_subject_type_patient_id_idx" \
...
...
apps/pac-service/prisma/migrations/20260728020000_persona_features_tag_filter_indexes/migration.sql
View file @
73cdfb2e
...
@@ -45,10 +45,27 @@
...
@@ -45,10 +45,27 @@
-- 生产实测 14 条合计 **220MB**(表 5.4GB,占 4%)。写入侧同理:插一行特征至多多维护 1 条索引,
-- 生产实测 14 条合计 **220MB**(表 5.4GB,占 4%)。写入侧同理:插一行特征至多多维护 1 条索引,
-- 画像重算不会被拖垮。
-- 画像重算不会被拖垮。
--
--
-- 【CONCURRENTLY 与上线方式】同 20260727110000:普通 CREATE INDEX 会 ACCESS EXCLUSIVE 锁全表,
-- 【⚠️ 上线方式:大表环境必须部署前手工 CONCURRENTLY 先建】
-- 画像重算与摄入全挂,故用 CONCURRENTLY。**推荐部署前手动先建**(deploy 时 pac-service 要等
-- 本文件里写的是**普通** CREATE INDEX(会 ACCESS EXCLUSIVE 锁全表),这是被迫的,见下条。
-- pac-migrate 退出,14 条索引串行构建 = 白等的停机);手动建好后本文件全部 IF NOT EXISTS 空跑。
-- 所以对**已有数据的库**,部署前必须先手工建好,让本文件的 IF NOT EXISTS 空跑:
-- 生产实测单条 4-17s,14 条共 **88 秒**(不锁表,期间业务照常);测试服磁盘慢,单条约 42s。
-- docker exec pac-postgres-1 psql -U pac -d pac -c "CREATE INDEX CONCURRENTLY IF NOT EXISTS ..."
-- (托管 PG 无 postgres 容器,用 pac-service 容器里的 node + prisma $executeRawUnsafe 发同样的 DDL)
-- 生产实测单条 4-17s、14 条共 88 秒;测试服盘慢,单条约 42s、共 238s。全程不锁表,业务照常。
-- 从零建库的环境不用管:那时 persona_features 是空表,普通 CREATE INDEX 瞬间完成。
--
-- 【为什么不能在这里写 CONCURRENTLY —— 2026-07-28 踩爆过,别再改回去】
-- Prisma 把整份 migration.sql 用**一次 simple query** 发给 Postgres,而 Postgres 对
-- 「一个查询串里多条语句」会隐式开事务块;CONCURRENTLY 明确禁止在事务块内跑:
-- ERROR: CREATE INDEX CONCURRENTLY cannot run inside a transaction block (25001)
-- 分界线是**语句条数,不是 Prisma 版本**:
-- 20260701060607 / 20260727070000 / 20260727110000 各只有 1 条语句 → 无隐式事务 → 跑得通
-- 本文件 14 条 → 隐式事务 → 必挂
-- 20260727110000 里那句「Prisma 6.19 已能直接跑通 CONCURRENTLY」是**错的归因**(单条侥幸),
-- 我照抄进本文件,结果测试服 deploy 挂在 migrate 步,pac-service 起不来。
-- 更坑的是失败后 _prisma_migrations 留下一条 finished_at/rolled_back_at 全空、
-- applied_steps_count=0 的记录,Prisma 从此拒绝执行**任何**后续迁移(P3018)——
-- 整条部署流水线被堵死,跟部署什么内容无关。解法:
-- npx prisma migrate resolve --rolled-back 20260728020000_persona_features_tag_filter_indexes
--
--
-- 【上线后实测】生产 2026-07-28 已手动建完,同口径复测(count + findMany 并发,墙钟):
-- 【上线后实测】生产 2026-07-28 已手动建完,同口径复测(count + findMany 并发,墙钟):
-- contraindication:surgery 23,379ms → **84ms** (278×,命中 1,347 单)
-- contraindication:surgery 23,379ms → **84ms** (278×,命中 1,347 单)
...
@@ -62,33 +79,33 @@
...
@@ -62,33 +79,33 @@
-- 禁忌 4,210ms → **344ms**。那台盘慢,绝对值别拿去跟生产比,看倍数即可。
-- 禁忌 4,210ms → **344ms**。那台盘慢,绝对值别拿去跟生产比,看倍数即可。
-- ── 标量维度(equals):btree(表达式, persona_id),第二列盖住 EXISTS 的输出列 ──────────────
-- ── 标量维度(equals):btree(表达式, persona_id),第二列盖住 EXISTS 的输出列 ──────────────
CREATE
INDEX
CONCURRENTLY
IF
NOT
EXISTS
"persona_features_rfm_segment_idx"
CREATE
INDEX
IF
NOT
EXISTS
"persona_features_rfm_segment_idx"
ON
"persona_features"
(((
data
#>
'{segment}'
::
text
[])),
"persona_id"
)
WHERE
"key"
=
'rfm'
;
ON
"persona_features"
(((
data
#>
'{segment}'
::
text
[])),
"persona_id"
)
WHERE
"key"
=
'rfm'
;
CREATE
INDEX
CONCURRENTLY
IF
NOT
EXISTS
"persona_features_lifecycle_stage_stage_idx"
CREATE
INDEX
IF
NOT
EXISTS
"persona_features_lifecycle_stage_stage_idx"
ON
"persona_features"
(((
data
#>
'{stage}'
::
text
[])),
"persona_id"
)
WHERE
"key"
=
'lifecycle_stage'
;
ON
"persona_features"
(((
data
#>
'{stage}'
::
text
[])),
"persona_id"
)
WHERE
"key"
=
'lifecycle_stage'
;
CREATE
INDEX
CONCURRENTLY
IF
NOT
EXISTS
"persona_features_urgency_level_level_idx"
CREATE
INDEX
IF
NOT
EXISTS
"persona_features_urgency_level_level_idx"
ON
"persona_features"
(((
data
#>
'{level}'
::
text
[])),
"persona_id"
)
WHERE
"key"
=
'urgency_level'
;
ON
"persona_features"
(((
data
#>
'{level}'
::
text
[])),
"persona_id"
)
WHERE
"key"
=
'urgency_level'
;
CREATE
INDEX
CONCURRENTLY
IF
NOT
EXISTS
"persona_features_age_bracket_bracket_idx"
CREATE
INDEX
IF
NOT
EXISTS
"persona_features_age_bracket_bracket_idx"
ON
"persona_features"
(((
data
#>
'{bracket}'
::
text
[])),
"persona_id"
)
WHERE
"key"
=
'age_bracket'
;
ON
"persona_features"
(((
data
#>
'{bracket}'
::
text
[])),
"persona_id"
)
WHERE
"key"
=
'age_bracket'
;
CREATE
INDEX
CONCURRENTLY
IF
NOT
EXISTS
"persona_features_gender_gender_idx"
CREATE
INDEX
IF
NOT
EXISTS
"persona_features_gender_gender_idx"
ON
"persona_features"
(((
data
#>
'{gender}'
::
text
[])),
"persona_id"
)
WHERE
"key"
=
'gender'
;
ON
"persona_features"
(((
data
#>
'{gender}'
::
text
[])),
"persona_id"
)
WHERE
"key"
=
'gender'
;
CREATE
INDEX
CONCURRENTLY
IF
NOT
EXISTS
"persona_features_family_structure_structure_idx"
CREATE
INDEX
IF
NOT
EXISTS
"persona_features_family_structure_structure_idx"
ON
"persona_features"
(((
data
#>
'{structure}'
::
text
[])),
"persona_id"
)
WHERE
"key"
=
'family_structure'
;
ON
"persona_features"
(((
data
#>
'{structure}'
::
text
[])),
"persona_id"
)
WHERE
"key"
=
'family_structure'
;
CREATE
INDEX
CONCURRENTLY
IF
NOT
EXISTS
"persona_features_referral_champion_type_idx"
CREATE
INDEX
IF
NOT
EXISTS
"persona_features_referral_champion_type_idx"
ON
"persona_features"
(((
data
#>
'{type}'
::
text
[])),
"persona_id"
)
WHERE
"key"
=
'referral_champion'
;
ON
"persona_features"
(((
data
#>
'{type}'
::
text
[])),
"persona_id"
)
WHERE
"key"
=
'referral_champion'
;
-- ── 数组维度(array_contains → @>):GIN jsonb_path_ops ────────────────────────────────────
-- ── 数组维度(array_contains → @>):GIN jsonb_path_ops ────────────────────────────────────
CREATE
INDEX
CONCURRENTLY
IF
NOT
EXISTS
"persona_features_contraindication_domains_idx"
CREATE
INDEX
IF
NOT
EXISTS
"persona_features_contraindication_domains_idx"
ON
"persona_features"
USING
gin
(((
data
#>
'{domains}'
::
text
[]))
jsonb_path_ops
)
WHERE
"key"
=
'contraindication'
;
ON
"persona_features"
USING
gin
(((
data
#>
'{domains}'
::
text
[]))
jsonb_path_ops
)
WHERE
"key"
=
'contraindication'
;
CREATE
INDEX
CONCURRENTLY
IF
NOT
EXISTS
"persona_features_entitlement_status_types_idx"
CREATE
INDEX
IF
NOT
EXISTS
"persona_features_entitlement_status_types_idx"
ON
"persona_features"
USING
gin
(((
data
#>
'{types}'
::
text
[]))
jsonb_path_ops
)
WHERE
"key"
=
'entitlement_status'
;
ON
"persona_features"
USING
gin
(((
data
#>
'{types}'
::
text
[]))
jsonb_path_ops
)
WHERE
"key"
=
'entitlement_status'
;
CREATE
INDEX
CONCURRENTLY
IF
NOT
EXISTS
"persona_features_treatment_history_types_idx"
CREATE
INDEX
IF
NOT
EXISTS
"persona_features_treatment_history_types_idx"
ON
"persona_features"
USING
gin
(((
data
#>
'{types}'
::
text
[]))
jsonb_path_ops
)
WHERE
"key"
=
'treatment_history'
;
ON
"persona_features"
USING
gin
(((
data
#>
'{types}'
::
text
[]))
jsonb_path_ops
)
WHERE
"key"
=
'treatment_history'
;
CREATE
INDEX
CONCURRENTLY
IF
NOT
EXISTS
"persona_features_treatment_sensitivity_types_idx"
CREATE
INDEX
IF
NOT
EXISTS
"persona_features_treatment_sensitivity_types_idx"
ON
"persona_features"
USING
gin
(((
data
#>
'{types}'
::
text
[]))
jsonb_path_ops
)
WHERE
"key"
=
'treatment_sensitivity'
;
ON
"persona_features"
USING
gin
(((
data
#>
'{types}'
::
text
[]))
jsonb_path_ops
)
WHERE
"key"
=
'treatment_sensitivity'
;
CREATE
INDEX
CONCURRENTLY
IF
NOT
EXISTS
"persona_features_special_attention_types_idx"
CREATE
INDEX
IF
NOT
EXISTS
"persona_features_special_attention_types_idx"
ON
"persona_features"
USING
gin
(((
data
#>
'{types}'
::
text
[]))
jsonb_path_ops
)
WHERE
"key"
=
'special_attention'
;
ON
"persona_features"
USING
gin
(((
data
#>
'{types}'
::
text
[]))
jsonb_path_ops
)
WHERE
"key"
=
'special_attention'
;
CREATE
INDEX
CONCURRENTLY
IF
NOT
EXISTS
"persona_features_time_preference_types_idx"
CREATE
INDEX
IF
NOT
EXISTS
"persona_features_time_preference_types_idx"
ON
"persona_features"
USING
gin
(((
data
#>
'{types}'
::
text
[]))
jsonb_path_ops
)
WHERE
"key"
=
'time_preference'
;
ON
"persona_features"
USING
gin
(((
data
#>
'{types}'
::
text
[]))
jsonb_path_ops
)
WHERE
"key"
=
'time_preference'
;
CREATE
INDEX
CONCURRENTLY
IF
NOT
EXISTS
"persona_features_potential_treatment_types_idx"
CREATE
INDEX
IF
NOT
EXISTS
"persona_features_potential_treatment_types_idx"
ON
"persona_features"
USING
gin
(((
data
#>
'{types}'
::
text
[]))
jsonb_path_ops
)
WHERE
"key"
=
'potential_treatment'
;
ON
"persona_features"
USING
gin
(((
data
#>
'{types}'
::
text
[]))
jsonb_path_ops
)
WHERE
"key"
=
'potential_treatment'
;
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment