目 录CONTENT

文章目录

【MySQL】PIPES_AS_CONCAT-性能影响分析验证报告

DarkAthena
2026-09-05 / 0 评论 / 0 点赞 / 4 阅读 / 0 字

MySQL sql_mode=PIPES_AS_CONCAT 性能影响分析验证报告

  • 测试日期:2026-09-03
  • 测试人:DarkAthena
  • 更新:2026-09-03 补充 §5.7(|| 链式拼接展开方式的源码与实验验证)、§5.8(金融证券业务风险场景实测)、§5.9(ORM 方言处理调研);并根据同行评审意见修订:§5.4 归因(RTT 分摊而非预热)、§5.1 拼接净边际口径、行数/过程数等事实性数据、§5.8 用例 1/3 描述;第二轮评审:RTT 采样固化为 60 轮×20 次并记录采样参数、§3.2/§3.3 交叉引用更正、「链式比较」措辞精确化、§5.5 payload 表述更正;第三轮评审:RTT 生成入口固化(bench_rtt.py,ping 一并留念),全文 RTT 口径统一为 ≈50ms 并重算 §5.4 扣除表
  • 数据库:MySQL Community Server 8.0.11(Docker,192.168.163.227:3307)

1. 摘要

本次通过服务端存储过程循环计时(消除网络往返干扰)+客户端大结果集/并发压测,共执行 200 万+ 次拼接运算,覆盖 2/3/8 操作数、字段拼字段、常量拼字段、8B~64KB 拼接长度、10 万行全表扫描、1~32 并发等场景。核心结论:

PIPES_AS_CONCAT 对运行时性能的影响可以忽略(绝大多数场景差异 <5%,且在 Run-to-Run 噪声范围内)。真正消耗 CPU 的是"拼接"这个动作本身(内存分配与拷贝),而不是 || 这个语法入口。

另外一个比性能更重要的工程发现:MySQL 存储过程/函数会绑定创建时刻的 sql_mode,调用方的会话 sql_mode 对例程内的表达式不生效,迁移改造时必须重建例程,否则会留下语义炸弹(详见 §3.2、§7)。

2. 背景与原理

PIPES_AS_CONCAT 开启后,MySQL 将 || 运算符从逻辑 OR 重新解释为字符串拼接,用于兼容 Oracle/PostgreSQL 的写法:

-- 默认模式
SELECT 'a' || 'b';   -- 0(逻辑 OR:'a'→0,'b'→0,即 0 OR 0 = 0)
-- PIPES_AS_CONCAT
SET sql_mode = '...,PIPES_AS_CONCAT';
SELECT 'a' || 'b';   -- 'ab'

从实现层面,|| 在解析阶段即被重写为 CONCAT() 内部表达式树,其类型推导、字符集聚合、NULL 传播规则与 CONCAT() 完全一致(本次已逐一实测验证,见 §6.1)。因此预期的性能特征为:

  • 解析/重写开销:一次性的,只在 prepare/optimize 阶段发生;
  • 求值开销:与 CONCAT() 共用同一条代码路径,理论上无差别;
  • 需要关注的其实是拼接本身的代价:结果字符串的内存分配、拷贝、char_length 换算,以及结果集 payload 变大带来的网络与临时表压力。

3. 测试设计

3.1 网络开销的消除(关键方法)

实测链路 RTT 情况:

指标
ping 延迟52~56ms(均值 54ms;4 次,记录于 env.json)
SELECT 1 应用层往返(60 轮×每轮 20 次采样,每轮独立连接、每轮取均值作为样本;采样参数与原始值记录于 env.json,由 scripts/bench_rtt.py 生成)min 46.8ms / median 49.9ms / p95 53.7ms

单次拼接运算的服务端耗时是微秒级的,约 50ms 的往返噪声比它大 4 个数量级。客户端逐条循环测短 SQL 完全不可行。因此采用以下方法:

维度方法
单条表达式/点查耗时服务端存储过程内部循环 N 次SELECT <expr> INTO trash),全部计算在服务端完成,客户端只测 CALL 总时长(1 次往返;计时窗口按用例成本标定在 0.3~7.1s,约 50ms 的 RTT 在核心用例占比最高约 16% 且为固定偏移,组间对比互抵;n 较小的规模维度场景已按 RTT 分摊定量扣除,见 §5.4)
全表扫描存储过程外层循环 × SELECT SUM(CHAR_LENGTH(...)) 聚合(结果不回客户端)
结果集传输维度客户端 fetchall 拉取完整结果集,此时网络是被测对象之一,刻意保留
基线对照每个用例配"无拼接"对照组(同形状 SQL、同数据路径),隔离拼接的边际开销
重复性核心用例 3 轮取最小值;规模/传输维度 2~3 轮取最小值;规模维度按 3k/12k/48k 循环数交叉验证

3.2 一个绕不开的坑:存储过程 sql_mode 绑定(实测验证)

MySQL 会记录创建时的 sql_mode,并在每次 CALL 时切换到该模式执行,与调用方会话的 sql_mode 无关。实测:

-- PIPES 模式下创建的过程
CREATE PROCEDURE p_bind_test() SELECT 'a' || 'b' AS r;
-- 切回默认模式调用 → 仍返回 'ab'

-- 默认模式创建的过程
CREATE PROCEDURE p_bind_test2() SELECT a || b AS r FROM t_bind;   -- a=1,b=1
-- 切到 PIPES 模式调用 → 仍返回 1(逻辑 OR 的结果)!

这意味着:

  1. 测试方法论上,|| 版与 CONCAT() 版过程必须分别在两种 sql_mode 会话下创建,保证解析行为固定、不受调用时模式干扰(本次 42 个基准过程:PIPES 会话创建 14 个 || 版,默认会话创建 14 个 CONCAT() 版 + 14 个对照版,均按此处理);
  2. 生产上,开启/关闭 PIPES_AS_CONCAT 不会即时改变已有存储过程的语义,必须 DROP + CREATE 重建才能生效(反过来,升级脚本对例程的改造也可能悄悄"锁死"旧语义);
  3. 同一系统里两个会话看到同一过程返回不同数据形态的风险存在——迁移改造切记全量重建例程

3.3 对比组设计

表达式形态创建/调用 sql_mode
PIPESa || b|| 写法)默认 + PIPES_AS_CONCAT
CONCATCONCAT(a, b)实例默认(不含 PIPES_AS_CONCAT)
对照无拼接的相同 SQL两种模式等价(|| 不出现)

其余 sql_mode 保持实例默认值:ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION

注:默认模式下 a || b 是逻辑 OR,不能与拼接语义同 SQL 对比;故"PIPES 开 vs 关"的性能对比只能通过"同语义的 || vs CONCAT()"两条等价路径进行——这恰好也是迁移改造时 SQL 改造前后的真实对比。

3.4 测试数据(合计约 31MB,全部缓存在 128MB buffer pool 内)

行数用途
t_small100,000(information_schema.TABLES.TABLE_ROWS 估算值为 99,324,实测 COUNT(*)=100,000)c1/c2/c3 VARCHAR(32),MD5 值字段拼接、扫描
t_len20,000v8/v64/v512拼接长度维度
t_utf850,000u1/u2 utf8mb4 中文多字节字符维度
t_bind1intsql_mode 绑定验证

4. 环境信息

版本MySQL 8.0.11(Community,Linux 容器)
字符集utf8mb4 / utf8mb4_0900_ai_ci
InnoDB buffer pool128MB(测试数据全量可缓存)
max_connections151;performance_schema ON
客户端Windows + PyMySQL 2.2.8,RTT ≈50ms
测试库concat_bench(独立库,含 42 个基准过程 + 8 个嵌套验证过程 + 2 个 sql_mode 绑定验证过程)

5. 测试场景与结果

5.1 A组:纯值表达式(常量与 REPEAT() 生成的长值,无表访问,循环 3~5 万次;对照组为同长度不拼接表达式)

用例PIPES ||CONCAT()无拼接对照PIPES vs CONCAT
2 操作数(12B)7.454 μs7.159 μs6.895 μs+4.1%
3 操作数7.223 μs7.164 μs6.784 μs+0.8%
8 操作数8.383 μs7.463 μs6.784 μs+12.3%
2 操作数(4KB)22.31 μs21.83 μs21.25 μs+2.2%
2 操作数(64KB)236.4 μs237.5 μs233.5 μs−0.5%

image-DjPx.png

解读:

  • 拼接的净边际开销(相对同构对照组)随结果长度温和增长:12B +0.56μs、4KB +1.06μs、64KB +2.96μs。真正的拼接成本(内存分配+拷贝)始终很小,与语法入口无关;长串用例的总耗时(22/236μs)大头是 REPEAT() 求值与缓冲区操作,对照组不拼接同样承担(21.25/233.5μs),不能归因到拼接头上——该口径与 §5.7 长串实验(三者趋平)互为印证;
  • || 相对 CONCAT() 的差距普遍 <1μs/次;唯一明显点出现在 8 操作数(+0.9μs/+12%)。该差异已从源码与实验两个层面落实:|| 链在解析器里被展开为嵌套二元 CONCAT,而多参数 CONCAT() 是单个函数调用,详见 §5.7;但绝对值仍在亚微秒级;
  • 64KB 大串场景两者完全持平——注意这只能说明拼接入口无差异;长串总耗时被 REPEAT() 长值生成与缓冲区操作主导,拼接边际(~3μs)占比仅 1% 量级,不可见。

5.2 B组:字段拼字段(主键点查,t_small 100k 行 × 1.2 万次循环)

用例PIPES ||CONCAT()无拼接对照PIPES vs CONCAT
c1||c2(2 列 32B)29.56 μs29.04 μs28.79 μs+1.8%
c1||c2||c3(3 列)29.14 μs29.41 μs28.40 μs−0.9%
常量||列30.13 μs29.06 μs28.34 μs+3.7%
v8||v828.74 μs30.31 μs28.48 μs−5.2%(噪声)
v64||v6428.30 μs28.33 μs28.36 μs−0.1%
v512||v51229.93 μs29.33 μs29.03 μs+2.1%
u1||u2(utf8mb4 中文)30.62 μs30.09 μs28.10 μs+1.8%

image-Rzau.png
解读:

  • 点查场景绝对大头是行获取(buffer pool 命中+字段解码)≈28.5μs,拼接只贡献 0.3~1.3μs,占比 <5%;
  • 各长度档(8B~512B)下 ||CONCAT() 互有胜负且都在噪声带内——可以认为完全等价
  • 中文 utf8mb4 与 ASCII 无显著差异(本例均为同序 utf8mb4_0900_ai_ci,字符集聚合无额外换算)。

5.3 C组:全表扫描(10 万行,聚合不返回数据,外层循环 20 次取均值)

用例PIPES ||CONCAT()无拼接对照PIPES vs CONCAT
SUM(LENGTH(c1||c2))44.07 ms43.77 ms29.12 ms+0.7%
SUM(LENGTH(c1||c2||c3))57.82 ms55.34 ms29.06 ms+4.5%

image-MOip.png

解读:

  • 拼接本身的成本在扫描场景才真正可见:10 万行加一次双字段拼接,扫描从 29ms → 44ms(+51%);三字段 → 57ms(+99%)。拼接 ≈ 15ms/10万行 ≈ 0.15μs/行/次拼接,与 A 组短串边际开销吻合;
  • ||CONCAT() 差距 ≤4.5%,属临界噪声;真正要省性能,应该减少拼接次数或下推/物化,而不是纠结语法形态

5.4 D组:规模稳定性(多轮不同循环次数)

循环次数PIPES μs/opCONCAT μs/op对照 μs/op
3,00040.6642.3039.38
12,00030.0728.8229.52
48,00026.1526.2625.72

image-eMNH.png

解读:PIPES 与 CONCAT 两组在三档规模下始终贴合。三档原始 per-op 的差异完全由 CALL 往返固定开销(≈50ms)随循环次数摊薄造成,与所谓预热无关(MySQL 8.0 存储过程为解释执行,也不存在 JIT)。以 PIPES 组为例、按 RTT 中位数 49.9ms 扣除后:

循环次数 n原始 μs/op49.9ms 分摊扣除 RTT 后 μs/op
3,00040.6616.624.0
12,00030.074.225.9
48,00026.151.025.1

扣除后三档高度一致(CONCAT 与对照组同样如此,均落在 23~26μs),说明单位耗时与循环规模无关,无"规模放大后 PIPES 劣化"的迹象。同时这也是方法学边界的一个好的证明:n 较小时 RTT 占比显著升高(n=3,000 时约 40%),跨规模比较必须先扣除固定往返开销,直接比较原始值会得出错误结论。

5.5 E组:结果集传输维度(客户端拉取 10 万行)

查询形态最佳耗时
SELECT c1,c2,c3(3 列不拼)3,126 ms
SELECT c1 || '-' || c2 || '-' || c32,564 ms
SELECT CONCAT(c1,'-',c2,'-',c3)3,517 ms

解读:约 10MB 结果在约 50ms RTT 链路上跑 2.5~3.5 秒,传输时间与拼接方式完全脱钩(该维度两次运行间波动比两种语法差异还大)。顺带观察到拼成单列后耗时反而略低,但这不是 payload 变小:按 MySQL 文本行协议,3 列×32B 与单列 98B 的逐行 payload 相同(值 + 长度前缀均约 99B/行),差异仅在于结果集头部少 2 个列定义包(一次性、百字节级),不足以解释 500ms 级波动,应归为运行噪声。收益不确定,不建议作为优化手段。

5.6 F组:并发压测(每线程 8 次 10 万行扫描)

线程数PIPES QPSCONCAT QPS
17.17.2
848.150.7
1684.381.9
32116.7117.8

image-ccGe.png

解读:1~32 并发下两组吞吐曲线完全拟合,CPU 争用不会放大 || 的微小差异。

5.7 G组:|| 链 = 嵌套 CONCAT 的落实验证(§5.1 异常点溯源)

源码层证据(MySQL 8.0.11 源码):

  1. 词法层 sql/sql_lex.cc:831-834:扫描到 || 时按会话 sql_mode 发不同 token——开启 PIPES_AS_CONCAT 时返回 OR_OR_SYM,否则返回 OR2_SYM(进入逻辑 OR 规则):
    if ((symbol->tok == OR_OR_SYM) &&
        !(lip->m_thd->variables.sql_mode & MODE_PIPES_AS_CONCAT))
      return OR2_SYM;
    
  2. 语法层 sql/sql_yacc.yy:9215-9218simple_expr OR_OR_SYM simple_expr 每次归约只构造一个二元 Item_func_concat
    | simple_expr OR_OR_SYM simple_expr
      {
        $$= NEW_PTN Item_func_concat(@$, $1, $3);
      }
    
  3. 结合性 sql/sql_yacc.yy:1240%left OR_OR_SYM ... 左结合 → a||b||...||h 归约为 CONCAT(CONCAT(...(a,b)...),h)7 层嵌套;而 CONCAT(a,...,h) 是单个 8 参数调用。

行为层验证:把操作数全部换成本地变量(彻底排除常量折叠影响),对比四种形态(各 3 轮取最小):

形态32B 短串 μs/次512B 长串 μs/次
v1||v2(2 操作数控制组)7.913
CONCAT(v1,v2)(2 操作数控制组)7.902
v1||...||v8(8 连 ||9.70417.20
手写 7 层嵌套 CONCAT9.99117.69
扁平 CONCAT(v1,...,v8)8.94717.37

image-yhTr.png

解读:

  • 2 操作数控制组完全相等(差 0.14%),说明 a||bCONCAT(a,b) 就是同一个 Item_func_concat
  • 8 操作数下 8 连 || ≈ 手写 7 层嵌套(9.70 vs 9.99μs,差 2.9% 噪声内),均明显慢于扁平 CONCAT(8.95μs)——假设坐实:差距来自函数调用深度(7 层嵌套逐层 fix_fields/求值/类型聚合),而非单个拼接更慢;
  • 长串(512B)下三者趋平:逐层中间串的额外拷贝在现代内存带宽下仅约 0.3μs,占比过小,短串时函数调用开销才是主要矛盾;
  • 附带一个重要语法细节:拼接规则定义在 simple_expr 产生式内,使得拼接态 ||实际结合优先级高于比较运算符与 AND/OR(与 %left 声明的观感相反)。探针实测:SELECT 1 = 1 || 2 返回 0,即解析为二元比较 1 = (1||2)(右侧拼接为 '12'1 = '12' 不成立);而默认的 OR 语义下同一 SQL 解析为 (1=1) OR 2,结果为 1。这意味着含 || 的谓词在切换模式后不仅语义变、括号结构也变(被重新分组),风险比想象中大(见 §5.8 用例 1/5)。

工程含义:多字段拼接若追求极限性能,改写为扁平 CONCAT(a,b,c,...) 可同时消除语义与性能两处差异;但差距量级为亚微秒/次,且仅在 ≥4 级链式拼接时出现,常规业务无需为此改写存量 SQL。

5.8 H组:金融证券业务场景下的 || 风险(已实测)

以下用例全部在测试库 concat_bench 中按两种模式真实执行,表结构:委托表 t_order(资金账号/营业部号/子账户序号/委托金额/委托数量/状态)、清算表 t_settle(本息 DOUBLE)。

用例 1:风控告警查询——谓词被重新分组,结果静默漂移

-- 意图:大额委托 或 状态异常的委托
SELECT COUNT(*) FROM t_order
WHERE amount > 500000 || order_status = 'X';
模式实测结果机理
默认2,命中 {1, 2}(正确:1 笔大额 + 1 笔异常)(amount>500000) OR (order_status='X')
PIPES3,命中 {2, 3, 4}:不仅多召回 3、4 两笔小额委托,而且漏掉了唯一的大额委托(order_id=1,60 万元)拼接态 || 优先级高于比较运算符,实际解析为 (amount > (500000||order_status)) = 'X'(MySQL 比较运算为左结合的二元运算,先算左侧比较再与 'X' 比较),已用等效 SQL 逐行验证吻合

告警规则从 SQL 迁移/下发时一旦发生模式漂移,风控漏报/误报且不会报错——对风控而言,漏掉唯一一笔大额委托的后果远比误报小额严重。

用例 2:主键拼接(清算对账编号)——OR 值写入对账字段

SELECT order_id, branch_no || acct_no AS full_acct FROM t_order;
模式实测结果
默认1(所有行 full_acct = 1,逻辑 OR 值)
PIPES0188001234(营业部+资金账号拼接)

Oracle 迁移过来的对账 SQL 拿到默认模式 MySQL 上执行不报错、每行返回 1——对账编号全乱但流程照跑。反向同理:默认模式的过滤写进 PIPES 会话则返回拼接字符串。

用例 3:三要素账号拼接——NULL 传播导致整行主键丢失

SELECT order_id,
       acct_no || '-' || branch_no || '-' || acct_seq AS k FROM t_order;

PIPES 模式实测:三笔正常返回(88001234-01-01 / 88001235-01-01 / 88001237-01-02,各不相同),子账户序号为 NULL 的第 3 笔整串变 NULL。对账文件出现空关键字/漏行。补救:CONCAT_WS('-', ...) 自动跳过 NULL,与 ||/CONCAT 语义不同,迁移时不能无脑替换。

用例 4:清算差异描述拼接——DOUBLE 精度污染监管报送

SELECT acct_no, '息差:' || (principal - 15000000) AS d
FROM t_settle WHERE acct_no = '88001234';

PIPES 模式实测返回 息差:0.7400000002235174。本意是 0.74 元的文字说明,DOUBLE 存储的本金做差后浮点尾差被拼接直接固化进展示文本;金额字段应用 DECIMAL,拼接前显式 CAST(... AS DECIMAL(18,2))FORMAT()

用例 5:比较与逻辑混排——结果集直接清空

SELECT order_id, qty FROM t_order
WHERE order_status = 'X' || qty > 5000;
模式实测结果
默认2 行(正确)
PIPES0 行(实际解析为 (order_status = ('X'||qty)) > 5000,逐行验证吻合,恒不成立)

告警/稽核类查询直接返空,比多返更隐蔽——因为“查不到问题”本身就是问题。

小结:用例 1/5 揭示的括号重组是最危险的一类——SQL 文本没变、不报错,结果集却按照另一套括号逻辑计算;用例 2/3/4 则是迁移改造时最常见的事故形态。

5.9 I组:ORM 框架对 MySQL || 语义的处理(调研)

问题:“ORM 会不会把数据库识别成 MySQL 却错误使用 || 语义?”核查结论:主流 ORM 的方言层都把 MySQL 正确渲染为 CONCAT() 函数调用,反而不会用 ||

框架拼接的渲染方式证据
DjangoConcatPair.as_mysql() 显式走 CONCAT(...) 函数,仅 SQLite 等后端用 ||django 1.8 django/db/models/functions.py 源码
SQLAlchemyMySQL方言将 concat 运算符编译为 CONCAT()|| 仅用于 PG/SQLite/Oracle 方言方言编译器实现
Hibernate/JPQLJPQL 标准拼接符是 ||,但方言层按目标库翻译,MySQLDialect 渲染 concat(...)官方方言架构
jOOQDSL.concat() 按方言渲染,MySQL → CONCAT()官方文档

真正的踩坑点不在“识别成 MySQL”,而在 ORM 管不着的通道

  1. Native SQL / MyBatis XML(金融存量最常见)@Query(nativeQuery = true)、MyBatis XML、text()literal_column() 都绕过方言翻译直发服务端。Oracle 迁移项目把 '01' || acct_no 抄进 XML,唯一“省事”方案就是开 PIPES_AS_CONCAT——这类存量代码正是本报告 §5.8 风险的高发区。
  2. 方言自动识别错配(经中间件或兼容库时):Hibernate 6 自动方言从 JDBC metadata 推断。经 ShardingSphere-Proxy、MyCat 或连接 TiDB/OceanBase(MySQL 模式租户)时,上报的都是“MySQL”,但服务端对 || 的解释不同:ShardingSphere 用自己的 SQL 解析器按 MySQL 方言把 || 判成 OR;OceanBase 的 sql_mode 有自己独立的实现与取值范围。ORM 渲染的 CONCAT 依旧安全,但任何绕过 ORM 的原生拼接 SQL 在经过这些链路时语义不可移植
  3. 连接池/读写分离下的 sql_mode 漂移:主库参数组开了 PIPES、报表从库没开(或连接串 sessionVariables 不一致),连接池随机命中两套语义,表现为“时好时坏”的灵异 bug——这也是建议用 SET PERSIST 统一持久化并做全节点巡检的原因。

结论:升级 ORM 解决不了 || 风险,唯一的根治手段是全量排查直发 SQL(含 XML、存储过程、视图、触发器、事件、调度脚本)并统一各节点 sql_mode。

6. 正确性旁证(开启后行为是否忠实)

6.1 语义一致性

检查项结果
'a' || 'b''ab'
1 || 'a''1a'(隐式转字符串)✓ 与 CONCAT 一致
'x' || NULLNULL ✓ 与 CONCAT 一致
CHARSET/COLLATION 聚合常量、列、utf8mb4 中文场景均与 CONCAT 完全一致(utf8mb4 / utf8mb4_0900_ai_ci)✓

6.2 需要警惕的功能性风险

WHERE a || b 在开启后语义从逻辑 OR 变成拼接,而残留拼接串在布尔上下文中按前导数字转数值判断真假('abc'→0 为假、'1abc'→1 为真),判断结果几乎任意——历史 SQL 一旦混入这种写法会静默错查。开启前必须全量扫描 SQL(含视图、触发器、存储过程、事件)确保无歧义用法;已有的例程还需重建(§3.2)。

7. 结论与建议

  1. 性能结论:放心开。 PIPES_AS_CONCAT 只在解析期多一次重写,求值与 CONCAT() 同路径;在 2~8 操作数、8B~64KB 长度、点查/扫描/并发、小中大规模的全部组合下,||CONCAT() 差异普遍 ≤5%、绝大多数在噪声带内;唯一可复现的例外是 ≥4 级链式拼接(嵌套 CONCAT 展开所致,机制与数据见 §5.7),极限差距 8~12%,但绝对值仅亚微秒/次。不存在"PIPES_AS_CONCAT 拖慢数据库"的问题。
  2. 拼接本身才是成本:短串净边际 ~0.6μs/行,64KB 大串的拼接净边际也仅 ~3μs/次(长串总耗时的大头是 REPEAT() 求值,不是拼接本身);但在全表扫描下拼接被逐行放大——10 万行扫描加一次拼接的增量成本 ≈+51%。优化方向永远是少拼、短拼、结果物化,与 sql_mode 无关。
  3. 工程上的真正风险是语义而非性能(§5.8 全部实测复现):
    • 历史 ||(OR 语义)SQL 会被静默改变——且不仅仅是 OR 变拼接,拼接态 || 优先级高于比较运算符,谓词括号会被重新分组,结果集静默漂移而不报错,开启前必须全量排查;
    • 拼接的 NULL 传播(整串为 NULL)、DOUBLE 精度污染、对账主键完整等问题需按场景加固(CONCAT_WSCAST);
    • 存储过程/函数/触发器/视图绑定创建时 sql_mode——切换模式后必须重建才能生效;
    • 主流 ORM(Django/SQLAlchemy/Hibernate/jOOQ)对 MySQL 正确渲染 CONCAT(),风险集中在 native SQL / MyBatis XML 等绕过方言的直发通道,以及经中间件/兼容库(ShardingSphere、TiDB、OceanBase)时的方言识别错配(§5.9);
    • 建议在 VM/容器镜像层的初始化脚本里统一 SET GLOBAL sql_mode 并持久化(SET PERSIST),并巡检全部从库/代理节点,避免节点间配置漂移。
  4. 适用建议:Oracle/PostgreSQL 迁移场景、ORM 生成 || 的场景可直接开启;新项目更推荐显式写 CONCAT()(可移植、无模式依赖)。

8. 复现方法

scripts/
  dbutil.py          # 连接配置、RTT 测量、客户端 bench 工具
  setup.py           # 建库装数(seq/t_small/t_len/t_utf8)
  verify.py          # 语义与 sql_mode 绑定验证
  build_procs.py     # 构建 42 个基准存储过程(按模式成对:PIPES 14 + 默认 28)
  calibrate.py       # 循环次数标定
  bench_core.py      # 核心场景(服务端循环计时)
  bench_extra.py     # 规模/传输/并发
  verify_nested.py   # §5.7 嵌套CONCAT验证实验
  demo_finance_risks.py # §5.8 金融风险场景实测
  make_charts.py     # 生成图表
data/
  env.json / results_core.json / results_extra.json

9. 附录:原始数据

文件内容
../data/results_core.json14 个用例 × 3 变体 × 3 轮完整计时
../data/results_extra.json规模/传输/并发原始结果
../data/results_nested.json§5.7 嵌套验证原始结果
../data/results_finance_risks.json§5.8 金融风险场景两种模式逐行结果
../data/env.json实例参数与 RTT 基线

测试脚本及结果数据打包下载:
PIPES_AS_CONCAT-test-scripts.zip

0
  1. 支付宝打赏

    qrcode alipay
  2. 微信打赏

    qrcode weixin
博主关闭了所有页面的评论