首页/新闻资讯/正文详情

MySQL字符串替换与脏数据清洗:REPLACE、批量UPDATE、正则替换

发布时间:2026/11/4 4:54:52 来源:云帆数科 栏目:资讯中心
MySQL字符串替换与脏数据清洗:REPLACE、批量UPDATE、正则替换
一条 UPDATE 语句跑了四十分钟还没结束最后不得不手动 kill 掉更让人崩溃的是表里明明看到字段中夹着空格REPLACE执行完之后空格还在。这类场景我在处理 MySQL 字符串替换时遇到过不止一次问题几乎都不出在函数本身而是出在“我以为它懂我意思”这个假设上。MySQL 里做字符串替换单一函数只有REPLACE()、REGEXP_REPLACE()那么几个可真正落到批量修数据的现场牵扯出来的是大小写敏感规则、排序规则、不可见字符的字节表示、批量更新的事务粒度、二进制日志膨胀和主从延迟这一整套东西。这篇内容我会把 REPLACE 函数、UPDATE 批量替换、正则替换这三条路线拆开讲每一条都配可复现的语句和实测结论适合手上正好有一批脏数据要清、又不想在线上翻车的开发者和运维同学参考。1. REPLACE 函数的边界比你想象的窄1.1 四个参数位各管什么NULL 怎么传染先把签名摆清楚REPLACE(str, from_str, to_str)三个参数分别是原字符串、被替换的片段、替换成的片段。它的行为是“把 str 里所有出现的 from_str 全部换成 to_str”注意关键词是全部没有“只换第一个”的选项。这一点和某些语言里的replace默认只换首个不同很多人第一次用会误以为它一次只处理一处。返回值语义上有三个必须记住的点。第一str、from_str、to_str任意一个为 NULL整个表达式返回 NULL不存在“NULL 就跳过当没写”的宽容行为。第二from_str为空字符串时MySQL 直接原样返回str不会进入死循环也不会在每两个字符之间插入内容这个设计其实救过不少人。第三REPLACE不看字符边界是纯粹的字节/字符序列匹配你在“中华人民共和国”里替换“人民”它会老老实实把中间那两个符取出来换掉不会考虑语义。NULL 传染这一条在批量更新里有个经常被忽略的后果。假设某列里既有正常文本又有 NULL你执行UPDATE t SET c REPLACE(c, #, )看起来像是在清理井号实际上 NULL 行会被写成 NULL因为REPLACE(NULL,...)返回 NULL行的update_time可能被刷新触发器和 binlog 也会照常记录。如果你的目的是只动真正含#的行WHERE 条件必须写清楚。-- 错误示范全表更新NULL 行和干净行都被重写一遍 UPDATE article SET title REPLACE(title, #, ); -- 正确姿势只捞真正需要改的行 UPDATE article SET title REPLACE(title, #, ) WHERE title LIKE %#%;顺便说一个嵌套的实用技巧。REPLACE可以层层套做多规则清理执行顺序是从内到外SELECT REPLACE(REPLACE(REPLACE(raw_text, \r, ), \n, ), \t, ) AS cleaned FROM raw_log WHERE raw_text LIKE %\r% OR raw_text LIKE %\n%;不过套超过三四层就该考虑换个思路了正则或者应用层处理会更清楚。1.2 大小写敏感这一条能让批量脚本白跑一整晚这是我在实战里踩得最深的一个坑。MySQL 的REPLACE()在查找from_str时区分大小写而LIKE ...的匹配是否区分大小写取决于列的排序规则。绝大多数线上表用的是utf8mb4_general_ci或utf8mb4_0900_ai_ci这类_cicase insensitive排序规则于是就会出现下面这种诡异的组合-- 假设 c 列的排序规则是 utf8mb4_0900_ai_ci SELECT c FROM t WHERE c LIKE %abc%; -- 命中 120 行包含 ABC、Abc、abc UPDATE t SET c REPLACE(c, abc, xyz) WHERE c LIKE %abc%; -- 实际只改了 40 行另外 80 行里是大写的 ABC 和 Abc原因很简单WHERE 用的是不区分大小写的比较把ABC也捞进来了但REPLACE内部用二进制方式比对ABC里找不到abc于是原样返回。这两套规则不一致的时候就会出现“WHERE 明明有命中替换却没生效”的假象。解决办法有两个方向。要么把函数这一侧改成不敏感要么把条件这一侧改成敏感。前者可以用LOWER()包一层但会丢失原始大小写后者更精准-- 方案一条件侧也变成区分大小写让 WHERE 和 REPLACE 口径一致 UPDATE t SET c REPLACE(c, abc, xyz) WHERE c COLLATE utf8mb4_bin LIKE %abc%; -- 方案二不区分大小写地替换需要处理大小写变体 UPDATE t SET c REPLACE(REPLACE(REPLACE(c, abc, xyz), Abc, xyz), ABC, xyz) WHERE c LIKE %abc%;方案二看起来很笨但如果你要替换的是品牌名、敏感词、链接域名这类东西把所有大小写组合穷举反而是最不容易漏的。我一般会先用SELECT DISTINCT把目标字段里含该片段的实际变体捞出来看看有多少种再决定穷举还是正则。匹配方式是否区分大小写常见排序规则下的表现REPLACE(str,a,b)区分ABCD中替换ab无效果LIKE %a%由排序规则决定_ci下命中A_bin下不命中LOCATE(a, str)由排序规则决定同 LIKEREGEXP_REPLACE默认不区分实测可用(?-i)或 BINARY 强制区分提示写批量替换脚本之前先用一条 SELECT 把“条件命中数”和“替换后真正变化数”对一下两个数不一致就说明命中了大小写变体别急着跑 UPDATE。1.3 REPLACE INTO 和 REPLACE() 撞名但完全是两回事REPLACE INTO tbl VALUES (...)是插入语句和字符串替换半点关系都没有。它的语义是先尝试插入如果主键或唯一索引冲突就把冲突的旧行删掉再插新行。这个行为在“有自增主键 有其他唯一索引”的场景下会引发很隐蔽的问题——删旧行时如果又碰上另一条唯一索引冲突可能会连带删掉第二行。我见过有同学在搜索资料时把这两者混为一谈照着REPLACE INTO的文档去写字符串替换结果自然是跑不通。判断依据很简单如果REPLACE后面紧跟的是INTO和表名它是插入语句如果后面跟的是括号和三个表达式它是字符串函数。两者唯一的共同点是都叫这个名字。社区里还有一类写法是REPLACE INTO用来做“有则更新无则插入”这个用法本身没问题但要注意它和INSERT ... ON DUPLICATE KEY UPDATE的差异前者是删除再插入自增 ID 会变触发器会分别触发 DELETE 和 INSERT 两个动作依赖 ID 稳定性的业务不建议用。这和本篇讲的字符串替换是两条线放在这里只是提醒一句别在搜索时混淆。2. UPDATE 配 REPLACE 做整表批改的落地细则2.1 先用 SELECT 摸清命中量再决定更新策略批量替换最大的风险不在于语法错而在于“改了不该改的”。所以我形成了一套固定的前置动作任何一次UPDATE ... REPLACE上线前先跑三条 SELECT。第一条数总量确认表规模和过滤条件是否合理SELECT COUNT(*) AS total FROM article;第二条数命中量看看这次替换会波及多少行。如果命中量接近总量说明条件太宽很可能误伤SELECT COUNT(*) AS hit FROM article WHERE content LIKE %旧域名%;第三条抽样看替换前后的对比尤其是长文本字段肉眼核对几行最靠得住SELECT id, SUBSTRING(content, 1, 120) AS before_snippet, SUBSTRING(REPLACE(content, 旧域名, 新域名), 1, 120) AS after_snippet FROM article WHERE content LIKE %旧域名% LIMIT 10;这三条跑完你对“影响面”和“改完长什么样”心里就有数了。命中量在几千行以内可以直接一条 UPDATE 搞定上万行就要考虑分批十万级以上就得认真设计分批策略和回滚方案了。还有一个容易被忽略的前置检查LIKE条件里的通配符。如果你的目标片段本身含有%或_比如替换某个含下划线的字段名前缀LIKE %user_name%里的下划线会匹配任意单字符命中范围会莫名扩大。这时候要么用ESCAPE显式转义要么干脆换用LOCATE或INSTR做条件-- 下划线是通配符这里会误伤 userXname SELECT id FROM t WHERE c LIKE %user_name%; -- 用 INSTR 更稳妥纯字面量查找 SELECT id FROM t WHERE INSTR(c, user_name) 0;2.2 分批更新的主键步进写法与事务粒度选择整表一条 UPDATE 的问题在于事务太大。InnoDB 虽然支持大事务但代价是 undo log 撑爆、行锁持有时间长、binlog 事件巨大、从库回放跟不上导致延迟飙升。我曾见过一条没加 WHERE 的 UPDATE 在千万行表上跑了两小时期间从库延迟涨到八千多秒业务侧读到的全是旧数据。分批的核心思路是用主键区间切片每次处理固定行数提交后再进行下一批。下面是我常用的存储过程写法按主键范围推进每批 2000 行DELIMITER $$ CREATE PROCEDURE batch_replace( IN p_from VARCHAR(255), IN p_to VARCHAR(255), IN p_batch INT ) BEGIN DECLARE v_min BIGINT DEFAULT 0; DECLARE v_max BIGINT DEFAULT 0; DECLARE v_cur BIGINT DEFAULT 0; SELECT MIN(id), MAX(id) INTO v_min, v_max FROM article; SET v_cur v_min; WHILE v_cur v_max DO UPDATE article SET content REPLACE(content, p_from, p_to) WHERE id v_cur AND id v_cur p_batch AND content LIKE CONCAT(%, p_from, %); SET v_cur v_cur p_batch; -- 给从库一点喘息时间也降低锁冲突概率 DO SLEEP(0.05); END WHILE; END$$ DELIMITER ;调用方式是CALL batch_replace(旧域名, 新域名, 2000);。几个设计意图值得说明批大小取 2000 是个经验值太小则提交次数多、总耗时长太大则单事务锁范围过宽2000 行在一般宽度的行上锁持有时间在几十毫秒级对线上影响可控WHERE里同时带LIKE是为了跳过不含目标片段的区间减少无谓的行扫描和 binlog 写入SLEEP是主动让出避免把 IO 打满。如果表没有连续自增主键比如用 UUID 或雪花 ID或者是联合主键按 ID 区间切就不合适了。这时候退而求其次用LIMIT配合游标-- 反复执行直到 affected rows 为 0 UPDATE article SET content REPLACE(content, 旧域名, 新域名) WHERE content LIKE %旧域名% ORDER BY id LIMIT 2000;注意这条语句在没加ORDER BY时行为不确定加了ORDER BY在大表上又要额外排序成本所以它只适合中小表或者本身有合适索引的场景。另一种思路是“先建新表再改名”把替换过程变成一次INSERT ... SELECT避开 UPDATE 的锁和 undo 开销CREATE TABLE article_new LIKE article; INSERT INTO article_new SELECT id, title, REPLACE(content, 旧域名, 新域名), created_at FROM article; -- 校验行数、抽样比对之后 RENAME TABLE article TO article_old, article_new TO article;这个方案在业务可以接受短暂停写的窗口期时非常干净回滚也简单——改回去就行。缺点是占用双倍磁盘且新表要重建索引上亿行的表要掂量一下时间成本。2.3 备份、回滚与上线前的检查清单批量替换是不可逆操作改错了只能靠备份或者 binlog 回溯。所以备份这一步不能省而且要根据数据量选方式。几百 MB 的表直接建一张备份表最快CREATE TABLE article_bak_20240115 AS SELECT * FROM article WHERE content LIKE %旧域名%;只备份命中的行体积可控回滚时用UPDATE ... JOIN把原值刷回去即可。上 TB 的表就不要整表备份了改成按主键区间分批备份或者依赖 binlog 的闪回能力但闪回操作对大部分人来说门槛偏高不如老老实实分批备份。上线前我一般过一遍这个清单检查项不通过的后果WHERE 条件是否精确误伤无关行最严重的一类事故是否已备份命中行改错后无法精准回滚是否分批执行长事务、锁等待、主从延迟是否有 UPDATE 触发器触发器副作用未预期binlog 格式与空间ROW 模式下 binlog 体积暴涨撑爆磁盘执行时间窗口高峰期执行影响线上读字符集与排序规则替换后出现乱码或比较行为变化其中 binlog 这一项特别值得说。当binlog_formatROW时一条更新一万行的 UPDATE 会把每一行的前镜像和后镜像都记下来如果字段本身是长文本单条 SQL 产生的 binlog 可能是几十 GB。磁盘告警往往就是这么来的。执行前先确认磁盘剩余空间必要时把批大小调小让 binlog 有机会被消费和清理。3. REGEXP_REPLACE 在 8.0 之后能干什么3.1 参数位含义与 occurrence 的取值语义REPLACE只能处理固定字面量遇到“把所有数字串换成占位符”“清掉所有 HTML 标签”这类需求就没辙了。MySQL 8.0.4 起提供了REGEXP_REPLACE(expr, pat, repl[, pos[, occurrence[, match_type]]])六个参数其中前三个常用后面三个知道存在即可pos从第几个字符开始匹配从 1 开始计数传 0 或负数会报错这点和某些语言的 index 从 0 开始不同容易错。occurrence替换第几处匹配默认 0 表示替换全部传 1 表示只替换第一处传 2 表示只替换第二处。这个参数在做“只改第一次出现的标签”时很有用。match_type匹配模式开关c区分大小写、i不区分、m多行模式、n让.匹配换行、u表示 Unix 行结尾。最常用的形态是三个参数全部替换SELECT REGEXP_REPLACE(订单号 A1234 和 B5678 已创建, [A-Z][0-9]{4}, [单号]); -- 结果订单号 [单号] 和 [单号] 已创建只替换第一处SELECT REGEXP_REPLACE(A1234-B5678-C9012, [A-Z][0-9]{4}, [单号], 1, 1); -- 结果[单号]-B5678-C9012返回值的字符集和排序规则跟输入表达式相关。如果输入是utf8mb4的列结果也是utf8mb4如果输入是二进制串结果是二进制串。导数据类型的时候要注意别把二进制结果直接塞回字符列。3.2 反斜杠在 SQL 字符串里的双重转义这是正则替换最容易翻车的地方我自己至少栽过三次。MySQL 的字符串字面量本身会把反斜杠当转义符处理而正则引擎又要用反斜杠表示元字符于是同一层反斜杠要写两遍。-- 你的意图是删掉所有数字正则写作 \d -- 但 \d 在字符串解析这一层就已经变成 d 了正则看到的是字面量 d SELECT REGEXP_REPLACE(a1b2c3, \d, ); -- 结果仍是 a1b2c3白白执行 -- 正确写法字符串层写两个反斜杠正则层才拿到 \d SELECT REGEXP_REPLACE(a1b2c3, \\d, ); -- 结果abc如果你是在应用代码里拼 SQL那还要再加一层。以 Java 字符串为例\\\\d才对应到 SQL 里的\\d。以 Python 的普通字符串为例\\\\d同样如此或者干脆用原始字符串r\\d少一次脑力换算。这个层数问题每次都要数一遍我的习惯是能用字符组就不用元字符转义——[0-9]比\\d直观得多[[:space:]]比\\s好数牺牲一点书写长度换可读性和正确率很划算。另外如果你要匹配的字符本身就是正则元字符比如点号、括号、加号必须转义。清 HTML 标签的时候.如果不转义会连正常文字一起吃掉-- 想去掉 p 标签但 . 没转义可能吃掉过多内容 SELECT REGEXP_REPLACE(html, p./p, ); -- 更稳的写法非贪婪 转义斜杠 SELECT REGEXP_REPLACE(html, p[^]*/p, );用[^]*代替.*?是因为 MySQL 的 ICU 正则虽然支持非贪婪量词但在跨多标签的长文本上[^]这种排除式写法性能更稳定也不容易因为换行问题匹配失败。3.3 大小写模式与匹配范围的控制实测下来在utf8mb4_0900_ai_ci排序规则下REGEXP_REPLACE默认是不区分大小写的这和REPLACE()的默认行为正好相反两套函数混用的时候要特别小心。-- 默认不区分大小写ABC 也会被替换 SELECT REGEXP_REPLACE(abcABC, abc, x); -- 结果xx -- 需要区分时用 match_type 的 c或者用内联开关 SELECT REGEXP_REPLACE(abcABC, abc, x, 1, 0, c); -- 结果xABC SELECT REGEXP_REPLACE(abcABC, (?-i)abc, x); -- 结果xABC不同版本和不同排序规则下这个默认值可能表现不一致最可靠的做法是在目标库上先跑一条SELECT REGEXP_REPLACE(aA,a,x)看看结果别照着文档猜。匹配范围上还有几个实用开关。n让.匹配换行符清理多行文本里的整段内容时用得上。m让^和$匹配每行的开头结尾处理按行组织的数据时有用-- 去掉每一行行首的序号比如 1. xxx SELECT REGEXP_REPLACE( 1. 第一条\n2. 第二条, (?m)^[0-9]\\.\\s*, );注意这里\\.是为了匹配字面的点号^配合(?m)才能匹配到每一行开头而不只是全文开头。这种组合在清理日志、清洗导入的表格数据时非常高频。提示正则替换的性能远低于字符串替换。能先用 WHERE 把范围缩小就一定要缩别指望REGEXP_REPLACE在千万行上全表跑。ICU 正则是 CPU 密集型操作一条没加条件的正则更新足以把实例 CPU 打满。4. 脏数据清洗看不见的字符才是真麻烦4.1 用 HEX 和 CHAR 把隐形内容揪出来最让人抓狂的场景是肉眼看到字段里有个空格REPLACE(col, , )执行完空格还在。原因通常是那个“空格”根本不是 U0020而是不换行空格 U00A0、全角空格 U3000、零宽空格 U200B 这类看着一样的字符。排查第一步是把真实字节打出来SELECT id, HEX(content) AS hex_view, LENGTH(content) AS byte_len, CHAR_LENGTH(content) AS char_len FROM article WHERE id 12345;HEX()给出的是十六进制字节序列LENGTH是字节数CHAR_LENGTH是字符数。在utf8mb4下一个汉字占 3 字节一个普通 ASCII 空格占 1 字节而 U00A0 占 2 字节C2 A0U3000 占 3 字节E3 80 80。字节数和字符数对不上的时候就说明里面有非 ASCII 内容。知道编码之后用字符集引导符加十六进制字面量精确匹配-- 干掉不换行空格 U00A0 UPDATE article SET content REPLACE(content, _utf8mb4 XC2A0, ) WHERE content LIKE CONCAT(%, _utf8mb4 XC2A0, %); -- 干掉全角空格 U3000 UPDATE article SET content REPLACE(content, _utf8mb4 XE38080, ) WHERE content LIKE CONCAT(%, _utf8mb4 XE38080, %);用CHAR(9)、CHAR(10)、CHAR(13)处理制表符和换行符也很方便因为这三种都是单字节 ASCIICHAR()返回的值直接能用在REPLACE里UPDATE article SET content REPLACE(REPLACE(REPLACE(content, CHAR(13), ), CHAR(10), ), CHAR(9), ) WHERE content LIKE CONCAT(%, CHAR(13), %) OR content LIKE CONCAT(%, CHAR(10), %) OR content LIKE CONCAT(%, CHAR(9), %);这里有个细节Windows 换行是\r\n两个字符先删\r再换\n比较稳妥如果顺序反过来把\n换成空格之后原串里会留下\r单独存在看着像空格但又不是又是一轮新的排查。4.2 全角半角与首尾空白的归一化从 Excel、Word 里粘过来的文本标点大概率是全角的。中文场景下全角标点本身没错但如果这列要拿去做英文检索或者拼接口参数就必须归一。全角字符的码位是有规律的全角是 UFF01半角!是 U0021两者相差 0xFEE0。理论上可以用正则一次转换但 MySQL 的REGEXP_REPLACE不支持回调函数没法做码位运算只能逐个映射。实际做法是用REPLACE嵌套把高频的那几个标点先干掉UPDATE article SET title REPLACE(REPLACE(REPLACE(REPLACE(title, , ,), 。, .), , :), , ;) WHERE title REGEXP [。];嵌套层数一多就难维护我的经验是超过五层就改用应用层脚本处理或者建一张映射表用存储过程循环替换。后者代码量不小但规则可配置、可复用长期维护成本反而低。首尾空白是另一个高频需求。MySQL 的TRIM()只能去掉 ASCII 空格去不掉全角空格和不换行空格所以真实项目里经常出现TRIM完看着还有空隙的情况。稳妥的写法是组合处理UPDATE article SET title TRIM(REPLACE(REPLACE(title, _utf8mb4 XC2A0, ), , )) WHERE title TRIM(REPLACE(REPLACE(title, _utf8mb4 XC2A0, ), , ));这里 就是全角空格的字符本身源码里直接写出来比写十六进制直观前提是文件编码和连接字符集都是utf8mb4否则存进去的可能又是另一个字符。我一般更倾向于写_utf8mb4 XE38080虽然不直观但绝不会有编码歧义。4.3 5.7 环境没有正则替换时的退路还在用 5.7 的环境没法用REGEXP_REPLACE这时候有三条路可走。第一条是把数据导出来处理再写回去适合一次性任务简单粗暴mysql -h host -u user -p db -e SELECT id, content FROM article WHERE content LIKE %旧域名% \ | python3 clean.py cleaned.tsvPython 脚本里用re.sub做替换生成一批UPDATE ... WHERE id ?写回去。注意要保证导出和写回之间的数据没有被业务改动最好在低峰期做或者加版本号字段做乐观校验。第二条路是在数据库里造一个替换循环函数用SUBSTRING_INDEX拆串拼接。思路是反复定位分隔符位置把两侧拼起来-- 5.7 环境下删掉所有 - 的过程化思路示意 -- 思路每轮找到第一个 -把左边和右边拼起来直到找不到为止 -- 实现上需要借助存储函数 WHILE 循环性能一般只适合小数据量用SUBSTRING_INDEX有天然的局限它按分隔符切如果你的目标片段恰好是分隔符本身还行如果是更长的字符串就不太顺手最终还是要落到循环里逐次定位。数据量超过十万行的话这条路的时间成本很难接受。第三条路是把替换逻辑放到应用层用批处理任务跑。好处是可以加日志、可以断点续跑、可以限速坏处是要占用业务代码的地方且要考虑并发更新的一致性问题。我的选择标准是一次性小批量用方案一规则频繁变动的用方案三方案二基本只在特殊情况下用。5. 一次“替换不生效”的完整排查链路5.1 症状WHERE 有命中UPDATE 显示影响 0 行说个真实复盘。某次清洗任务要把商品标题里的“包邮”两个字删掉因为这批商品要转到另一个渠道重新上架。执行的语句是UPDATE goods SET title REPLACE(title, 包邮, ) WHERE title LIKE %包邮%;返回结果Rows matched: 328, Changed: 0。命中了 328 行但一行都没变。当时第一反应是权限问题检查了一遍账号有 UPDATE 权限又怀疑触发器把更新拦了查了information_schema.TRIGGERS也没有。折腾了十几分钟才意识到方向错了。Changed: 0这个信息其实很明确值根本没有发生变化不是“没执行”是“执行了但结果和原来一样”。这时候唯一的原因就是REPLACE没找到目标片段。5.2 定位把可疑行的字节级内容打出来下一步是取样看真实内容SELECT id, title, HEX(title), LENGTH(title), CHAR_LENGTH(title) FROM goods WHERE title LIKE %包邮% LIMIT 5;结果很清楚这些标题里的“包邮”是繁体字“包郵”。LIKE之所以能命中是因为表的排序规则是utf8mb4_0900_ai_ci在这个排序规则下简体“邮”和繁体“郵”被视为等价字符比较时认为相等但REPLACE做的是字节序列比对简繁两个字在 UTF-8 编码里分别是E9 82 AE和E9 83 B5完全不同的字节自然匹配不上。这就是排序规则带来的“等价字符”陷阱除了简繁还有带重音的拉丁字母é和e在某些_ci规则下等价、德语ß和ss等。凡是排序规则认为相等的字符LIKE都可能命中而REPLACE不会。5.3 修复与回归验证知道原因后修复就很直接了把两种写法都覆盖上UPDATE goods SET title REPLACE(REPLACE(title, 包邮, ), 包郵, ) WHERE title LIKE %包邮% OR title LIKE %包郵%;为了保证以后不再踩我在替换脚本里加了一个前置统计把“条件命中数”和“实际需要变动的行数”做对比SELECT SUM(title LIKE %包邮%) AS like_hits, SUM(title LIKE %包邮% AND title REPLACE(title, 包邮, )) AS actual_changes FROM goods;如果两个数差得多就说明命中的行里有一批是“看起来命中但替换不生效”的必须把变体捞出来SELECT DISTINCT title FROM goods WHERE title LIKE %包邮% AND title REPLACE(title, 包邮, ) LIMIT 50;这条 SELECT 挖出来的就是所有“LIKE 命中但 REPLACE 无效”的样本一眼就能看出是简繁差异还是大小写差异还是别的什么。这个技巧比瞎猜快得多现在已经成为我处理类似问题的固定第一步。批量替换里还有一类问题跟字符串函数无关但经常被归咎于它。比如更新之后应用侧读到的还是旧值那属于缓存没刷更新之后从库读到旧值那是主从延迟更新之后页面乱码那是连接字符集和服务端字符集不一致导致的写入时就错了替换只是把问题暴露出来。分清楚是替换本身没生效还是替换生效了但下游没同步排查路径完全不同——前者看数据后者看链路。我现在的习惯是先SELECT确认库里数据真的变了变了就往下游查没变再回来折腾 SQL。最后分享一个小技巧关于替换后的自检。批量更新完成之后跑一条“残留检查”确认目标片段是否彻底清除SELECT COUNT(*) AS remain FROM goods WHERE title LIKE %包邮% OR title LIKE %包郵%;理论上应该返回 0如果不为 0看看剩下的这些是不是新写入的数据可以用时间字段过滤避免把业务正常写入的内容误判成没替换干净。这个检查我一般会和更新脚本放在同一个批处理里跑完自动输出结果省得第二天再回头核对。

相关推荐

具身智能清洁机器人技术解析与应用实践
具身智能清洁机器人技术解析与应用实践

1. 项目概述"具身智能清洁机器人"这个标题让我想起十年前第一次接触扫地机器人时的场景。那时候的机器还像个无头苍蝇,而如今已经进化到能识别宠物粪便、自动避开电线、甚至根据家庭布局规划最优路径的智能设备。这个领域的技术迭代速度令人惊叹&#xff… · 2026/11/4 4:54:52

AI Agent开发四大支柱:从调API到生产级系统的核心差距
AI Agent开发四大支柱:从调API到生产级系统的核心差距

1. 为什么“会调 API”离真正的 AI Agent 还差着十万八千里很多人第一次接触 AI Agent 这个概念,脑子里浮现的画面大概是这样的:写个 Python 脚本,把用户输入拼进 prompt,往大模型 API 一发,拿到返回结果,再… · 2026/11/4 4:54:52

DeepSeek Harness与Pi Harness深度辨析:执行层与决策层的本质区别
DeepSeek Harness与Pi Harness深度辨析:执行层与决策层的本质区别

1. 同名不同命:两个 Harness 被混为一谈的根源第一次看到"DeepSeek Harness"和"Pi Harness"这两个词摆在一起的时候,我下意识以为它们是同一类东西的两个实现版本——毕竟都叫 Harness,都跟 Agent 沾边,都在同… · 2026/11/4 4:54:52

Vivado/Vitis 2023.1 FPGA开发环境安装与验证全流程
Vivado/Vitis 2023.1 FPGA开发环境安装与验证全流程

1. 先搞清楚这套工具链到底装了什么装 Vivado 和 Vitis 2023.1 这件事,表面上是点几次"下一步",实际上是一次对硬盘、内存、账号、路径习惯的综合体检。我在帮新人搭环境的时候发现,绝大多数"装不上""装完打不开&qu… · 2026/11/4 5:36:16

MySQL安装与优化全攻略:从环境准备到安全加固
MySQL安装与优化全攻略:从环境准备到安全加固

1. MySQL 9.1.0安装全景指南刚接触数据库的新手可能不知道,MySQL 9.1.0这个版本号其实是个虚构的版本——目前MySQL官方最新稳定版是8.0系列。不过没关系,这个"9.1.0"正好让我们有机会全面梳理MySQL安装的完整知识体系。作为从业十余年的DBA&a… · 2026/11/4 5:36:16

ACM OJ基础题AC代码集:C++17模板与本地对拍实践
ACM OJ基础题AC代码集:C++17模板与本地对拍实践

简介:这份资料面向准备北京大学POJ在线评测、ACM算法入门与课程作业练习的学习者,汇集百余道基础与常见题目的C参考实现,可用于对照思路、排查边界条件并复盘输入输出处理。压缩包共157个文件,其中156个cpp源码文件和1个txt说明文… · 2026/11/4 5:36:16

PAT考试动态规划实战:怪物战斗问题的Java解法
PAT考试动态规划实战:怪物战斗问题的Java解法

1. 项目概述PAT(Programming Ability Test)是计算机程序设计能力考试,其中Top Level题目难度较高,主要考察算法设计与实现能力。1032题"Fighting the Monsters"是一道典型的动态规划问题,要求玩家在有限资源… · 2026/11/4 5:36:16

金融科技平台核心拆解:账户、支付、风控实战
金融科技平台核心拆解:账户、支付、风控实战

接到“financial-services”这个标题时,我第一反应是——这大概会是一场硬仗。不是谦虚,是我做了多年金融科技项目后的一种本能的警觉。你很难一句话说清金融服务是什么,因为它从来不是一个单点功能,而是一套互相咬合的规则系统。… · 2026/11/4 5:36:16

Tailwind CSS 自定义主题与多品牌风格适配实战
Tailwind CSS 自定义主题与多品牌风格适配实战

前段时间接手的一件事,是把一套跑了两年的后台系统,从单品牌改成多品牌。产品给的需求特别朴素:三个品牌,配色、圆角、字号各不一样,业务逻辑还是那一套,UI 换层皮就行。听着像是改几个变量的活&#xff0c… · 2026/11/4 5:35:16

双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 3:16:49

STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 6:04:24

DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 5:52:46

AI辅助技术栈评估:提升研发决策效率60%
AI辅助技术栈评估:提升研发决策效率60%

1. 项目背景与核心价值技术决策一直是研发团队面临的重大挑战。随着技术生态的快速演进,技术栈的选择、维护和迭代变得愈发复杂。我们团队在过去两年里,先后经历了三次重大技术栈迁移,每次都需要投入大量人力进行技术评估和风险分析。直到去年… · 2026/11/4 0:00:17

从生成Token到直接决策:Jev如何重塑AI Agent工程实践
从生成Token到直接决策:Jev如何重塑AI Agent工程实践

1. 从"生成文字"到"拍板决策":Jev 到底在解决什么问题大多数人第一次接触大模型,脑子里建立的模型是"输入问题、输出文字"。你问它今天天气怎么样,它给你一段描述;你让它写个周报,它给你… · 2026/11/4 0:00:17

32G统一内存跑大模型:内存账、TPS实测与端云分流
32G统一内存跑大模型:内存账、TPS实测与端云分流

想在一台 32G 统一内存的小主机上把大模型跑成"日常可用",这件事我前后折腾了小半年。标题里说的这台 Mac mini M6,是我们这次讨论的目标平台,具体到手规格以实物为准;但下面这套推算方法和实测思路,对任何统… · 2026/11/4 0:00:17

双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 3:16:49

STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 6:04:24

DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 5:52:46

了解更多?预约专属演示

我们的顾问将为您一对一讲解产品与方案

企业微信二维码