-
我建表时一直顺手写 CHARSET=utf8 自以为这就是万国通吃的 UTF-8 什么字都存得下,直到有用户把昵称改成带了个笑脸 emoji,插入直接报 Incorrect string value 整条失败、宽松模式下昵称还被从 emoji 那里齐刷刷截断,排查很久才知道 MySQL 的 utf8 根本不是完整 UTF-8 而是只支持三字节的 utf8mb3 存不下四字节 emoji 的深度复盘
我建数据库表时一直习惯性地写 CHARSET=utf8,心里笃定 utf8 就是大名鼎鼎的 UTF-8、万国码、什么语言什么字符都能存,中英日韩这么多年也确实一直好好的。直到有个用户把昵称改成带了一个笑脸 emoji,保存直接炸了:严格 sql_mode 下报 ERROR 1366 Incorrect string value 'xF0x9Fx98x80' for column…- 3
- 0
-
我的一张日志表用 int 做自增主键、跑了两三年相安无事,某天起所有 INSERT 突然全部失败、报主键重复,我反复确认插入的数据没有重复主键百思不得其解,最后才惊觉这张表的自增值已经悄悄爬到了 int 的上限二十一亿再也加不上去了的深度复盘
我有一张写入很频繁的表(日志/流水/消息记录),主键是 id INT AUTO_INCREMENT,建表时想 int 能存二十多亿够用了,跑了两三年一直稳稳的。可某天监控告警:所有写入开始批量失败、日志里全是 Duplicate entry 2147483647 for key PRIMARY。我一看主键重复就懵了:插的是新数据根本没指定主键、让它自增的,哪来重复?反复检查插入语句、有没有人手动塞…- 0
- 0
-
我在一个数据库事务里先改了几张表的数据、又顺手执行了一条建临时表的 DDL,本以为整段都在事务保护下出错能一起回滚,结果后面一步失败我 rollback 时,前面改的数据竟然怎么都退不回去、已经实实在在落库了,排查很久才知道那条 DDL 早就把我的事务偷偷提交掉了的深度复盘
我有个操作需要在一个事务里完成好几步:先更新 A 表几行、再更新 B 表几行,中间为做点临时计算顺手 CREATE TEMPORARY TABLE 建了张临时表,最后再写 C 表。我用 START TRANSACTION 开了事务,心想万一哪步出错 ROLLBACK 一下所有改动就都干净撤销了。结果写 C 表那步因约束冲突失败,我老老实实 ROLLBACK,可回头一查 A 表 B 表的改动还在一行…- 0
- 0
-
我在一个事务里反复查同一行数据,想等它被别的流程改成功就继续,结果别人明明早就改了、也提交了,我这边却怎么查都还是旧值、活活卡死在那里,排查半天发现是可重复读隔离级别下的快照读在作怪的深度复盘
我有段业务逻辑:开一个事务、做了些前置操作,然后轮询数据库里的某一行,等另一个流程把它的状态改成已完成就继续。我想当然认为别人改了并提交后我再查一次自然就能查到最新值。可上线后这段逻辑诡异卡死:我明明能用另一个连接直接查到状态早就是已完成、事务也提交成功了,可我这个事务里的轮询无论查多少次读到的永远是那个旧的处理中,像被冻住,死活等不到、最后超时。我一度怀疑缓存、主从延迟、别人没提交,逐一排除都对…- 0
- 0
-
我的分页接口每次都查一下总共有多少条数据好显示总页数,小表时秒回,数据涨到几千万后这个 COUNT 比查数据本身还慢、直接拖垮了接口的深度复盘
我有个分页列表接口,每次都做两件事:查当前页数据(带 LIMIT)和 SELECT COUNT(*) 查总条数(为了显示共 N 条、共 M 页)。数据量小时一切秒回,可这张表涨到几千万行后接口越来越慢,一查傻眼:慢的不是查当前页(有索引、LIMIT 很快),而是那个看起来最简单的 COUNT(*)——它居然比查数据本身还慢得多、一次要好几秒,拖垮了整个接口。复盘才想明白:InnoDB 不像 MyI…- 0
- 0
-
我为了防止插入重复数据,代码里先查一下存不存在、不存在才插入,单机测试一切正常,可一上并发就冒出了重复记录,因为先检查再插入这两步之间有个窗口、并发请求全挤了进来的深度复盘
我有个场景要防止插入重复(同一用户名只能注册一个、同一订单号只能下一单),写得很合理:先 SELECT 查这条记录在不在、不在才 INSERT。单机低并发测试一切正常、防重也生效。可一上线并发一高就冒出重复记录:数据库里出现两条一模一样的记录,先查再插形同虚设。复盘才想明白:先检查(SELECT)再行动(INSERT)这个 check-then-act 模式在并发下根本不是原子的——两步之间有时间…- 2
- 0
-
我为了保证原子性把几万条记录的批量处理塞进了一个数据库事务,结果这个事务跑了好几分钟,期间长时间持锁阻塞了其他业务、占满了连接池、还把回滚段撑得老大、主从延迟飙升:一次大事务拖垮整个库的深度复盘
我有个批量任务要更新几万条记录,想着这些操作得保证原子性、要么全成功要么全回滚,就放进一个事务里:开启事务→循环几万次逐条 update→提交。功能没问题,可一上线跑,整个数据库就开始抖:其他业务大面积报锁等待超时、连接池被占满、主从延迟从毫秒飙到几十秒、DBA 告警 undo 暴涨。复盘才理解大事务的危害:一个事务在开始到提交的整个期间会持有它改过的行的锁、占用一个连接、在 undo log 里…- 0
- 0
-
我明明给表建了联合索引,有些查询却还是慢得像全表扫描,EXPLAIN 一看根本没走索引,因为我的查询条件不符合最左前缀:一次数据库联合索引最左前缀的深度复盘
我有张订单表,经常按用户、状态、时间查,于是建了个联合索引 (user_id, status, created_at),以为这几个字段的查询都能走它、飞快。可线上有些查询慢得像全表扫描,EXPLAIN 一看傻眼:它们根本没走这个联合索引(type=ALL)。那些慢查询要么只按 status 查(没带最左的 user_id),要么 status+created_at(跳过了 user_id)。查清才…- 0
- 0
-
两笔转账并发执行,一笔从 A 转 B、一笔从 B 转 A,数据库突然报 Deadlock found 把其中一笔回滚了:一次数据库死锁、加锁顺序不一致循环等待的深度复盘
我的转账逻辑一个事务里更新两个账户(扣转出方、加转入方),平时好好的,可线上偶发数据库报 Deadlock found、其中一笔被回滚。把两笔并发转账的加锁顺序画出来才看明白:事务 T1 从 A 转 B 先锁 A 再要锁 B,事务 T2 从 B 转 A 先锁 B 再要锁 A——T1 锁住 A 等 B、T2 锁住 B 等 A,互相等对方释放、循环等待、谁也动不了,数据库检测到死锁就挑一个回滚。根因是…- 0
- 0
-
我扣库存的逻辑是先查出当前库存、减一、再写回,平时好好的,一上量就超卖、库存对不上账:一次数据库并发更新丢失、读改写非原子导致超卖的深度复盘
我扣库存的逻辑写得很直白——先 SELECT 查出当前库存,代码里减一,再 UPDATE 写回。平时流量小一直没问题,活动一放量并发一高就出事了:商品明明只有 100 件却卖出 100 多件超卖,对账时库存扣减数量和卖出订单数对不上。推演两个并发请求才看明白:这是更新丢失——A 查出 stock=100,B 也查出 100(都读到旧值),A 算 99 写回,B 也算 99 写回,B 把 A 的扣减…- 0
- 0
-
列表页前几页飞快、翻到几千页后接口直接超时,我用 LIMIT 一百万逗号二十去查那一页才发现数据库默默扫描并丢弃了前一百万行:一次深度分页 LIMIT offset 性能塌陷的深度复盘
我做了个数据列表页支持翻页,后端分页用最常见的 SELECT * FROM orders ORDER BY id LIMIT offset, size。前几页飞快,可用户翻到很靠后的页(第几千页)接口就慢到超时 500。抓出那条慢 SQL 一看 offset 已到百万级。EXPLAIN 才发现:LIMIT 1000000, 20 不是直接跳到第 1000000 行取 20 条,而是从头扫描并丢弃前…- 0
- 0
-
两个并发事务因为以不同的顺序去更新两条记录,互相等着对方手里的锁,撞成了死锁、被 MySQL 强行回滚了一个:一次数据库死锁的深度复盘
转账接口高并发时偶发 Deadlock found,部分请求失败。根因是一个事务要更新两条记录,而不同请求加锁顺序不同:A→B 的请求先锁 A 再锁 B、B→A 的先锁 B 再锁 A,并发时事务1持有A等B、事务2持有B等A,互相等待形成循环等待的环、谁也走不了,MySQL 检测到死锁就回滚其中一个。本文讲透死锁怎么形成和它的四个必要条件,给出固定加锁顺序(破坏循环等待,最有效)、缩小事务/缩短持…- 0
- 0
-
一条 NOT IN 子查询的 SQL,因为子查询里混进了一个 NULL,把本该返回几千行的结果集变成了空,我栽进了 SQL 三值逻辑的坑:一次 NULL 处理的深度复盘
想查所有从没被订单引用过的商品,WHERE id NOT IN (SELECT product_id FROM orders),本该返回几千行却返回空、还不报错。根因是子查询的 product_id 列里混进了 NULL:SQL 是三值逻辑(TRUE/FALSE/UNKNOWN),NULL 表示未知、任何和它的比较都得 UNKNOWN,id NOT IN(含 NULL)等价于 ...AND id!…- 0
- 0
-
一个用 LIMIT offset 做分页的接口,翻到第几万页时一条查询要跑十几秒,把数据库拖垮:一次深分页性能的深度复盘与游标分页正解
列表接口用最常规的 LIMIT offset 分页,前几页飞快,可爬虫脚本一页页翻到第 5 万页时,同样查 20 条却要十几秒、数据库 CPU 打满。根因是 LIMIT 1000000,20 并不是直接跳到第 100 万行,而是从头扫描出前 1000020 行、丢弃前 100 万行只返回 20 行,代价随 offset 线性增长,SELECT * 还要百万次回表。本文讲透 LIMIT offset…- 0
- 0
-
一个 varchar 手机号字段被用数字去查,MySQL 偷偷做隐式类型转换让索引彻底失效:一次慢查询拖垮数据库的深度排查与类型对齐正解
phone 字段是 varchar 且建了索引,一条 WHERE phone = 13800138000(少了引号,数字)却全表扫描三百多万行、跑了 8 秒、把数据库 CPU 打满。根因是 MySQL 隐式类型转换:字符串和数字比较时,它把字符串列转成数字,等于对每行 phone 做 CAST 运算,索引随之失效。本文从 EXPLAIN 看出 type=ALL/key=NULL 讲起,剖析隐式转换…- 0
- 0
-
我给查询字段建好了索引,一条简单的查询却慢得离谱,EXPLAIN 一看竟然全表扫描根本没走索引,我对着在索引列上用函数和隐式类型转换让索引失效这个坑排查大半天的复盘
一个让我对数据库索引从迷信到敬畏的经典坑,崩溃在索引明明就在那、查询条件明明用了那个建了索引的字段,数据库却视而不见去全表扫描。慢查询告警:users 表几百万行,phone 字段 varchar 上早建好索引 idx_phone,按手机号查的接口平时飞快,那天突然几百毫秒甚至上秒。SQL 简单得不能再简单:SELECT * FROM users WHERE phone = 13800138000…- 4
- 0
-
我用 NOT IN 子查询过滤数据,结果返回了空集、明明该有很多行,还有个 != 查询莫名其妙漏了一批数据,我对着 SQL 里 NULL 的三值逻辑排查了大半天的复盘
两段普通 SQL:一段 NOT IN 子查询排除用户,一段 WHERE status != active 查非活跃用户。结果诡异:NOT IN 那段明明该返回一大批却返回空集;!= 那段查出的非活跃用户竟漏掉了一批 status 为 NULL 的(它们明明也不是 active)。盯着 SQL 反复看逻辑没毛病甚至怀疑是数据库 bug,排查大半天才理解让无数人栽跟头的概念——NULL 的三值逻辑:N…- 0
- 0
-
用户昵称带个 emoji 就插入数据库失败、报 Incorrect string value,我数据库明明设的是 utf8 字符集,我对着 MySQL 的"假 utf8"排查了大半天的复盘
做用户系统一切正常,直到有用户昵称里加了个 emoji,保存瞬间接口就报 Incorrect string value: 'xF0x9Fx98x80' for column nickname。一脸困惑:数据库字符集明明设的是 utf8,UTF-8 不是号称能编码所有字符吗怎么连个 emoji 都存不下?排查大半天才得知一个大跌眼镜的真相:MySQL 里的 utf8 根本不是真正…- 0
- 0
-
我的列表接口前几页飞快、翻到几十万页却慢到超时,同样是查 20 条,凭什么越往后越慢,我对着深分页的 LIMIT offset 排查了大半天的复盘
做了个支持翻页的列表接口,用经典的 LIMIT offset, size 分页,上线一切正常,直到有用户(或爬虫)翻到很后面的页码,监控告警炸了:同样每页查 20 条,第 1 页几毫秒、翻到几十万页却要好几秒甚至超时。一脸问号——不都查 20 条吗凭什么后面慢成这样?排查大半天才理解深分页那个致命陷阱:LIMIT 1000000, 20 不是"直接跳到第100万行取20条",而…- 0
- 0
-
我给表建了 a、b、c 的联合索引,以为查这三列里哪一个都能走索引,结果按 b 单独查时全表扫描慢成狗,我对着最左前缀排查了大半天的复盘
我给一张大表建了 (a,b,c) 的联合索引,想当然以为查 a、查 b、查 c 都能用上它,结果上线后按 b 单独过滤的查询慢得要命,EXPLAIN 一看 type=ALL、key=NULL,索引完全没生效、走的全表扫描。我对着这个"明明建了索引却用不上"的现象排查了大半天,才彻底搞懂联合索引的最左前缀原则:(a,b,c) 这个索引,是先按 a 排序、a 相同再按 b、b 相同…- 0
- 0
-
我的转账接口在高峰期偶尔会报 Deadlock found、事务莫名其妙被回滚,我对着这个时有时无的数据库死锁排查了大半天才搞懂加锁顺序的复盘
我的转账接口在一个事务里扣 A 加 B,平时没事,一到高峰并发大就偶发 Deadlock found、事务被强行回滚,时有时无、并发越高越频繁。把并发事务的加锁顺序画在纸上一对照才懂:这是死锁,根源是加锁顺序不一致——A 转 B 的事务先锁 A 再锁 B,而同时 B 转 A 的事务先锁 B 再锁 A,于是事务1锁住A等B、事务2锁住B等A,各自持有对方想要的锁、互相死等,形成循环等待;InnoDB…- 0
- 0
-
我的列表分页接口翻到前几页飞快、翻到几十万页却越来越慢直到超时,我盯着那条带巨大 OFFSET 的 SQL 排查了大半天才搞懂深分页的真相
我的列表接口用经典 LIMIT offset,size 分页,前几页几十毫秒、翻到第 1000 页 200 毫秒、翻到几十万页(LIMIT 1000000,20)要好几秒甚至超时。明明每页都只取 20 条,凭什么越翻越慢?EXPLAIN 后才懂:LIMIT offset,size 不是"直接跳到第 offset 行",而是从头扫描出 offset+size 行、把前 offse…- 0
- 0
-
我刚写入数据库的数据,紧接着一查却说查不到,我一口咬定是事务没提交、对着事务代码排查了好几天,最后才发现是读写分离的主从延迟在作怪的深度复盘
我们为扛读流量做了读写分离:写走主库,读分摊到从库。可一个诡异 bug 折磨了我好几天——用户提交一条数据,代码先写库、紧接着立刻查它,结果那条刚写进去的数据竟然"查不到"、或查到旧值!而且时有时无,测试环境几乎不出,一到高峰就频繁冒。我一口咬定是事务没提交,查了好几天事务代码,最后才醒悟:写走主库、紧接着的读却走了从库,而主从复制是异步的、有复制延迟,数据还没同步到从库就被读…- 0
- 0
-
我的查询明明在手机号字段上建了索引,却慢得像在全表扫描,EXPLAIN 一看果然没走索引,折腾半天发现罪魁祸首竟是一个隐式类型转换的深度复盘
一张几百万行的用户表,我早早在 phone 字段建了索引,可"按手机号查用户"的 SQL 慢得动辄上千毫秒,跟全表扫描没区别。我一度想重建索引、加配置,最后老老实实 EXPLAIN 一看:type=ALL、key=NULL,根本没走索引!细看才发现:phone 是 varchar,我却写成 WHERE phone = 13800138000 把它当数字传了。MySQL 比较字符…- 0
- 0
MySQL
幸运之星正在降临...
点击领取今天的签到奖励!
恭喜!您今天获得了{{mission.data.mission.credit}}积分
我的优惠劵
-
¥优惠劵使用时效:无法使用使用时效:
之前
使用时效:永久有效优惠劵ID:×
没有优惠劵可用!
























