From f30d1a1d0abf40f2863b62473eb9cb0db4a8b950 Mon Sep 17 00:00:00 2001 From: zlt Date: Fri, 11 Sep 2026 14:01:11 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=8A=80=E6=9C=AF=E9=9D=A2=E8=90=BD?= =?UTF-8?q?=E8=A1=A8=20SQL=EF=BC=9AON=20DUPLICATE=20=E5=AD=90=E5=8F=A5?= =?UTF-8?q?=E6=94=B9=E7=94=A8=20VALUES(=E5=88=97)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 真机首验(2026-09-11)抓到: upsert_daily 的 ON DUPLICATE KEY UPDATE 用 :col 绑定参数, pymysql executemany 对 INSERT...ON DUPLICATE 做多行合并优化时只展开 VALUES 子句占位符, UPDATE 子句的 %s 不展开却仍算参数, 5000 行批量落表参数错位、SQL 留裸 % 报 1064。 改用 VALUES(列) 引用插入值。batch27 加哨兵钉住(脱库单测 mock 落表测不到)。 Co-Authored-By: Claude Opus 4.8 --- app/repo/tech_repo.py | 8 ++++++-- scripts/test_batch27_units.py | 9 +++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/app/repo/tech_repo.py b/app/repo/tech_repo.py index 0f83e63..b5d7ca6 100644 --- a/app/repo/tech_repo.py +++ b/app/repo/tech_repo.py @@ -35,8 +35,12 @@ def upsert_daily(rows: list) -> int: payload.append(d) cols = ", ".join(_COLS) + ", created_at, updated_at" vals = ", ".join(f":{c}" for c in _COLS) + ", :ts, :ts" - updates = ", ".join(f"{c} = :{c}" for c in _COLS - if c not in ("data_date", "ts_code")) + ", updated_at = :ts" + # ON DUPLICATE 子句用 VALUES(列) 引用插入值, 不再带绑定参数 (:col)。原因: pymysql 的 + # executemany 对 INSERT ... ON DUPLICATE 做多行合并优化, 只把 VALUES 子句的占位符按行 + # 展开, UPDATE 子句里的 :col (→ %s) 不展开却仍算参数, 5000 行批量时参数错位、SQL 里 + # 留下裸 % 报 1064 (2026-09-11 真机首验抓到; 脱库单测 mock 了落表测不到)。 + updates = ", ".join(f"{c} = VALUES({c})" for c in _COLS + if c not in ("data_date", "ts_code")) + ", updated_at = VALUES(updated_at)" return execute_many( f"INSERT INTO pms_tech_daily ({cols}) VALUES ({vals}) " f"ON DUPLICATE KEY UPDATE {updates}", payload) diff --git a/scripts/test_batch27_units.py b/scripts/test_batch27_units.py index 884f8bf..af57b68 100644 --- a/scripts/test_batch27_units.py +++ b/scripts/test_batch27_units.py @@ -310,6 +310,15 @@ def test_pull_ok(): assert got["rows"][0]["ts_code"] == "600000.SH" +@case("F upsert SQL: ON DUPLICATE 子句用 VALUES() 不带绑定参数(pymysql 批量陷阱, 2026-09-11 真机抓到)") +def test_upsert_odku_no_bind(): + import inspect + from app.repo import tech_repo + src = inspect.getsource(tech_repo.upsert_daily) + assert "= VALUES(" in src, "ON DUPLICATE 子句要用 VALUES(列) 引用插入值" + assert 'f"{c} = :{c}"' not in src, "UPDATE 子句不该再用 :col 绑定参数(pymysql executemany 多行合并会把它错位展开)" + + def main(): ok = 0 for name, fn in RESULTS: