-
我图省事把上万个 ID 一股脑塞进 SQL 的 WHERE id IN (...) 里去批量查询、小批量测的时候快得飞起,结果生产环境列表一大这条查询就慢成狗有时还直接报参数过多的错、DBA 看监控说这一条 SQL 把库都拖垮了,排查很久才搞懂一个在小规模下完美的 IN 写法放大到上万个值时会在解析执行计划缓存好几个地方同时崩坏的深度复盘
我有个需求要根据一批 ID 去数据库批量查记录、写得特别直白把所有 ID 拼进一个 IN 列表 SELECT * FROM orders WHERE id IN (1,2,3,...上万个)。开发测试时手头 ID 也就几十个查询飞快毫无问题顺利上线。可生产环境:某些场景 ID 列表上万个这条查询慢得离谱从毫秒飙到几秒十几秒、而同样表查单条飞快;某些数据库上 IN 列表超过某数量直接报错参数太多/表…- 0
- 0
-
我建表时一直顺手写 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
-
我图省事把 Java 枚举的 ordinal 序号存进数据库当状态值、跑了一年风平浪静,直到产品要在枚举中间插一个新状态,我加完一上线数据库里成千上万条记录的状态全错位了、原本是已支付的变成了已取消,排查很久才反应过来 ordinal 是按声明顺序排的我一插队后面全跟着挪了位的深度复盘
我有个订单状态枚举 OrderStatus { CREATED, PAID, SHIPPED, COMPLETED, CANCELLED },存数据库时图省事直接存它的 ordinal()(CREATED=0,PAID=1,SHIPPED=2,COMPLETED=3,CANCELLED=4),读出来用 values()[storedInt] 还原,跑了一年一切正常。直到产品要在已支付之后已发货之前…- 0
- 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
-
我写了个 SQL 想查出状态为空的记录、用了 WHERE status = NULL,结果一行都查不出来,我又写了个 NOT IN 子查询,这次更怪、整个结果集凭空变成了空,排查半天才明白 SQL 里的 NULL 根本不能用等号去比的深度复盘
我有张表有些记录的 status 是空(NULL),想把状态为空的记录查出来,很自然写了 WHERE status = NULL。可结果一行都没返回,哪怕表里明明有一堆 status 为 NULL 的记录。我以为数据问题,查全表那些 NULL 记录清清楚楚在。又想查 status 不在某列表里的记录,写了 NOT IN 子查询,结果更诡异:整个结果集凭空变空。直到补了 SQL 对 NULL 的处理…- 0
- 0
-
我做分页时用创建时间排序、一页页翻,本地看着没问题,可用户反馈翻页时有的数据重复出现、有的却凭空消失,排查半天才发现我排序用的那个列不唯一、值相同的行之间的顺序压根没保证的深度复盘
我做了个列表分页,按创建时间倒序、每页 20 条,ORDER BY create_time DESC LIMIT 20 OFFSET ?,一页页往后翻。本地测试数据不多翻几页都正常我就觉得稳了。可上线后用户反馈诡异的事:翻页时有的数据第 1 页见过第 2 页又冒出来一次(重复),有的明明该在列表里却怎么翻都找不到(遗漏)。我以为是数据有重复或缓存问题,核对数据都好好的,直到发现出问题那批数据有个共…- 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
-
我的订单列表页慢得离谱,抓 SQL 一看一个请求里竟然发了一百多条几乎一样的查询,我对着 ORM 懒加载在循环里逐个触发的 N+1 查询这个坑排查了大半天的复盘
一个让我对 ORM 方便的代价彻底警醒的数据库坑,隐蔽在代码读起来非常优雅面向对象毫无破绽,我只是访问了一下每个订单的用户名,可这行无辜的属性访问背后 ORM 悄悄发了上百条数据库查询。订单列表页要显示每个订单和下单用户名字,我用 ORM 写 orders = Order.query.all() 然后 for order in orders: order.user.name。页面慢得离谱,抓 SQ…- 0
- 0
-
我写了个查询所有非 active 用户的 SQL,结果状态为 NULL 的用户竟然一个都没查出来,报表数据莫名少了一大截,我对着 SQL 里 NULL 参与比较结果是 UNKNOWN 这个三值逻辑坑排查大半天的复盘
一个让我对 SQL 里的 NULL 彻底改观的坑,诡异在查询条件逻辑上看完全正确(查所有 status 不是 active 的用户),返回结果却悄悄漏掉了一部分本该符合的数据,而漏的正是 status 为 NULL 的行,这种静默漏数据在报表里尤其致命。用户表 status 有 active、inactive 和一些 NULL,我写 SELECT * FROM users WHERE status…- 0
- 0
-
我给查询字段建好了索引,一条简单的查询却慢得离谱,EXPLAIN 一看竟然全表扫描根本没走索引,我对着在索引列上用函数和隐式类型转换让索引失效这个坑排查大半天的复盘
一个让我对数据库索引从迷信到敬畏的经典坑,崩溃在索引明明就在那、查询条件明明用了那个建了索引的字段,数据库却视而不见去全表扫描。慢查询告警:users 表几百万行,phone 字段 varchar 上早建好索引 idx_phone,按手机号查的接口平时飞快,那天突然几百毫秒甚至上秒。SQL 简单得不能再简单:SELECT * FROM users WHERE phone = 13800138000…- 4
- 0
-
我用不等于条件查"未完成"的订单,结果一批 status 为 NULL 的订单全被漏掉了、数量怎么都对不上,我对着 SQL 的三值逻辑排查了大半天的复盘
我用 WHERE status != 'done' 查所有未完成订单,对账却数量对不上——所有被漏掉的订单 status 都是 NULL。NULL 明明不等于 done,为什么 != 'done' 没把它们选出来?深挖才懂是 SQL 的三值逻辑:NULL 表示"未知",拿它和任何值比较(=、!=、>、甚至 = NULL)结果都不是 …- 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
数据库
幸运之星正在降临...
点击领取今天的签到奖励!
恭喜!您今天获得了{{mission.data.mission.credit}}积分
我的优惠劵
-
¥优惠劵使用时效:无法使用使用时效:
之前
使用时效:永久有效优惠劵ID:×
没有优惠劵可用!
























