准备牛客面经的时候数据库锁和日志几乎是必被翻牌的两块硬骨头。很多人八股背得滚瓜烂熟什么共享锁、排他锁、redo log、undo log张口就来但面试官一旦把问题拽到线上场景——比如“数据库并发锁导致接口超时了怎么查”“金仓数据库如何查看锁表情况”“数据库日志跟踪怎么做”立刻就露怯。这篇文章不是给你复述教科书而是把锁和日志放在一起串着讲因为它们本来就是一体的两面锁解决并发日志解决可靠。搞清楚这两件事面试和排障都会顺很多。1. 先搞懂锁的底层分类面试中最容易答串的几个概念很多候选人被问倒不是因为不知道锁有哪些而是把锁粒度、锁模式、隔离级别三个维度混在一起讲显得毫无框架感。我建议你脑内先立一个坐标系锁按粒度分有行锁、页锁、表锁、间隙锁按模式分有共享锁、排他锁、更新锁、意向锁按策略分有悲观锁和乐观锁。三者不是互斥关系而是从不同角度描述同一把锁。1.1 锁粒度行锁、表锁、页锁与间隙锁锁粒度决定并发程度和加锁成本。行锁并发最高但管理开销也最大表锁并发最差胜在开销小页锁则介于两者之间SQL Server和MySQL的BDB引擎里比较常见。面试官最爱问的其实是间隙锁Gap Lock它只在InnoDB的可重复读隔离级别下默认生效作用是锁住索引记录之间的间隙防止幻读。这里有个很多新手记错的点间隙锁不是锁住某一行而是锁住一个范围比如SELECT * FROM user WHERE id BETWEEN 10 AND 20 FOR UPDATE它会把10到20之间不存在的记录位置也锁住别人想往这个区间插入数据就会被阻塞。1.2 锁模式共享锁、排他锁、更新锁、意向锁共享锁和排他锁的兼容性表是必背的共享锁之间兼容共享锁和排他锁互斥排他锁之间也互斥。更新锁容易被忽略它介于两者之间加更新锁之后事务真正要写数据时会升级为排他锁这样设计是为了避免两个事务同时拿到共享锁后又都想升级排他锁而造成的死锁。意向锁则是InnoDB里一个很容易被忽视的存在。它的作用是快速判断一个事务是否锁住了某个表里的行这样加表锁的时候不用逐行扫描。比如事务A给表里某行加了排他锁数据库会自动在该表上加意向排他锁事务B想给整张表加排他锁看到表上有意向排他锁就知道已经有行级锁存在直接等待就行。这个过程可以用一个生活场景类比你想敲门进屋里某个房间先看看门口有没有挂“屋内有人”的牌子而不是挨个房间推门看。1.3 隔离级别如何影响加锁行为隔离级别和锁的关系是面试追问的重灾区。读未提交下写操作加排他锁但读操作不加锁所以能读到未提交数据读已提交下每次查询都会生成新的快照同时普通读通过MVCC实现不加锁但如果是SELECT ... FOR UPDATE这类当前读会对命中记录加锁并且不再使用间隙锁可重复读下普通读的快照在事务第一次读取时生成之后整个事务都读这个快照同时当前读会加行锁和间隙锁可串行化则是所有读都变成当前读相当于锁的范畴被极限放大。面试时你可以主动补一句“MVCC让快照读不加锁但当前读仍然需要锁”这就能把MVCC和锁串成一条线比零散背概念强得多。1.4 悲观锁与乐观锁的实现差异悲观锁就是依赖数据库的SELECT ... FOR UPDATE、LOCK IN SHARE MODE这类机制全程持锁适合并发竞争严重的场景。乐观锁则是在应用层用版本号或时间戳实现更新时UPDATE t SET value ?, version version 1 WHERE id ? AND version ?影响行数为0就说明版本冲突需要重试。乐观锁并不是不用锁而是把锁粒度做成应用层的一次校验更轻量。需要注意的是很多人面试时说“乐观锁性能更好”这是不严谨的。如果冲突率高乐观锁会频繁重试反而拉低吞吐冲突率低时才划算。所以回答时要带一句“选型取决于对冲突概率的预估”面试官眼神会不一样。2. 线上锁等待排查从通用SQL到金仓、高斯数据库的实操八股背完面试官下一个动作往往是抛场景题“线上有个表被锁了你怎么查”很多人只背过SHOW PROCESSLIST但现实中不同数据库的排查命令完全不同。这里我把通用思路和两款国产数据库的实操都过一遍。2.1 通用查询查看数据库表是否被锁、找阻塞源头先说通用方法。MySQL里最常用的是查information_schema三张表innodb_trx当前事务、innodb_lock_waits锁等待关系、innodb_locks锁信息。一条经典SQL就能拼出阻塞链SELECT r.trx_id AS waiting_trx_id, r.trx_mysql_thread_id AS waiting_thread, b.trx_id AS blocking_trx_id, b.trx_mysql_thread_id AS blocking_thread, b.trx_query AS blocking_query FROM information_schema.innodb_lock_waits w JOIN information_schema.innodb_trx r ON w.requesting_trx_id r.trx_id JOIN information_schema.innodb_trx b ON w.blocking_trx_id b.trx_id;拿到阻塞线程ID后用KILL thread_id结束阻塞事务。如果现场当时没抓全还可以用SHOW ENGINE INNODB STATUS看LATEST DETECTED DEADLOCK段里面有死锁事务的SQL和持有锁的关系。2.2 金仓数据库如何查看锁表情况金仓数据库KingbaseES是国产数据库里出镜率很高的一个。它兼容PostgreSQL所以查看锁表情况基本可以沿用PG的思路核心是两张系统视图sys_locks有的版本叫pg_locks和sys_stat_activity或pg_stat_activity。金仓的官方文档里通常保留了PG兼容视图这一点在生产环境踩过坑的人最有体会——很多人上来就找v$lock结果视图不存在。一条很实用的锁冲突查询SELECT a.pid, a.datname, a.usename, a.state, a.wait_event_type, a.wait_event, a.query_start, a.query FROM sys_stat_activity a WHERE a.state idle AND a.pid IN ( SELECT pid FROM sys_locks WHERE NOT granted );这个查询会返回所有正在等待锁的会话。想查阻塞源头可以借sys_blocking_pids(pid)函数它直接返回阻塞当前进程的PID列表比手动join两张视图清爽很多。SELECT pid, sys_blocking_pids(pid) AS blocked_by FROM sys_stat_activity WHERE state idle;2.3 高斯数据库查看锁与等待事件高斯数据库openGauss和普通PG系稍有点不同它提供了统一等待视图pg_stat_activity但锁等待的定位更推荐从两个方向查一是pg_locks二是等待事件。openGauss 3.x以后有一个dbe_perf.wait_events视图或gs_wait_events可以看到具体卡在什么事件上。实际排查时一般先看pg_stat_activity里哪些会话处于active状态但wait_event_type LockSELECT pid, state, wait_event_type, wait_event, current_query FROM pg_stat_activity WHERE wait_event_type Lock;拿到PID后再看pg_locks里这个会话持有和等待的锁对象SELECT pid, locktype, relation::regclass AS rel, mode, granted FROM pg_locks WHERE pid pid ORDER BY granted DESC;granted为false的那一行就是它正在等待的锁。顺着这个锁去找谁持有所有持锁的granted true的PID都要过一遍。高斯上也可以直接用pg_blocking_pids()函数找阻塞源逻辑和金仓类似。2.4 锁冲突处理与预防处理锁冲突的基本链条是找到等待会话找到阻塞会话评估阻塞会话的事务是否还能结束不能结束就SELECT pg_terminate_backend(pid)PostgreSQL系或KILLMySQL系终止它。但这里有个很重要的坑直接杀会话可能造成长事务回滚如果事务已经执行了很久回滚成本很高。所以生产上要先确认事务是不是僵尸事务比如state idle in transaction这种通常可以直接结束如果是正在执行的复杂查询最好先和相关开发确认。预防锁冲突的思路更值得在面试里提到业务SQL统一走索引减少锁范围控制事务时长避免长事务长时间持锁把innodb_lock_wait_timeout设置成合理值比如5秒而不是默认的50秒批量更新时按主键排序执行降低死锁概率。3. 数据库日志三大件redo、undo、binlog的作用与面试考点锁解决并发问题日志解决可靠性问题。面试中日志相关的题基本就围绕redo log、undo log、binlog这三类文件展开但很多人把它们的职责背串。3.1 redo log为什么崩溃恢复不会丢数据redo log是物理日志记录的是“某个数据页被修改成了什么样子”。InnoDB的buffer pool在内存里先改数据页再定期刷盘这个机制叫WALWrite-Ahead Logging预写日志事务提交前先把redo log写到磁盘保证即使数据页还没刷盘数据库崩溃后也能通过redo log重放恢复。关于redo log面试里有一个高频连环问为什么不直接把数据页刷盘非要先写日志答案是因为数据页是离散的随机写而redo log是顺序写顺序写比随机写快一到两个数量级所以叫“性能换可靠性”的典型做法。还要能说上LSNLog Sequence Number的概念。LSN是日志序列号每个数据页上有page_lsnredo log里有lsn崩溃恢复时只重放大于页面page_lsn的日志避免重复应用。现在InnoDB还引入了组提交group commit多个事务的redo log一起刷盘进一步提升吞吐。3.2 undo log回滚与MVCC的底层支撑undo log正好相反它是逻辑日志记录的是“怎么做能撤销当前修改”。比如插入了一行undo里就记录这行的主键回滚时按主键删除更新了一行undo里就记录修改前的值回滚时把旧值写回去。undo log还有一个职责是支撑MVCC。InnoDB通过行上的DB_ROLL_PTR指针把多个版本串成一条版本链事务开启时根据隔离级别生成一个ReadView读取时沿着版本链找第一个对当前事务可见的版本。这就是为什么读已提交下每次查询都生成新ReadView而可重复读只在第一次查询时生成。曾经有个同事特别经典地混淆了redo和undo他觉得崩溃恢复要靠undo。其实正好说反了——崩溃恢复依赖redoundo承担的是事务回滚和一致性读虽然崩溃恢复时未提交事务的undo也会被用来做回滚清理但“从不丢已提交数据”这个核心保障是redo给的。3.3 binlog与归档日志复制与恢复binlog是MySQL server层的逻辑日志记录的是SQL语句或行变更主要用于主从复制和时间点恢复PITR。InnoDB的redo log和binlog之间有一个两阶段提交机制事务提交时先写redo log并标记为prepare状态然后写binlog最后把redo log标记为commit。这样能保证两份日志的一致性如果写完binlog之前崩溃恢复时发现redo prepare但binlog没有对应记录就回滚如果binlog已经写了恢复时则重放事务。Oracle世界里对应归档日志archive log承担的是类似职责数据库运行在ARCHIVELOG模式下切换联机重做日志redo log时后台进程把联机日志复制为归档日志。有了归档日志才能做全量备份加归档的介质恢复支持恢复到任意时间点如果是NOARCHIVELOG模式只能做崩溃恢复不能做介质恢复。面试时如果被问“归档日志和redo log有什么区别”一句话就能讲透redo log是数据库内存循环写入的联机日志容量有限不断覆盖归档日志是redo log切换时产生的历史副本用于长期保存和数据恢复。3.4 数据库日志跟踪确认日志在工作“数据库日志跟踪”这个词在不同场景下含义不同但面试或运维里最常见的诉求是确认日志在正常写入、切不发生异常。MySQL里看错误日志SHOW VARIABLES LIKE log_error看binlog状态SHOW BINARY LOGS看redo log文件列表SHOW ENGINE INNODB STATUS里的Log sequence numberOracle里则经常查V$LOG、V$LOG_HISTORY和V$ARCHIVED_LOG来确认联机日志切换和归档是否正常。一个实际案例某系统归档目录空间被撑满数据库直接hang住连查询都进不去了。排查时就是先看V$LOG发现日志一直卡在ACTIVE状态无法切换再看磁盘发现归档目录满了。根因是归档日志长期没清理而RMANOracle的备份恢复工具的备份保留策略也没有及时归档清理。这就是日志跟踪没做到位的典型后果。4. 日志文件管理删除归档、日志收缩与存储时间查询日志相关的面试题里有三道题几乎年年出现而且全部带着“网上的答案别乱信”的属性这里逐一说清楚。4.1 删除归档日志需要关闭数据库吗先说结论**绝大多数场景下不需要关闭数据库。**归档日志只是redo log切换后生成的副本文件不是数据库运行所必需的联机文件在线状态下可以直接清理。Oracle里典型的做法是用RMAN删除比如DELETE ARCHIVELOG ALL COMPLETED BEFORE SYSDATE-7;RMAN会同步维护控制文件里的归档记录避免出现“文件删了但控制文件还认为它存在”的脏数据。但要注意一个特殊情况如果归档日志正在被归档进程写——也就是说当前redo log刚好切换了archive进程还在把日志拷贝到归档目录这时候你去删或者覆盖这个文件就会触发归档中断严重时数据库会hang住。所以稳妥的做法是先查一下当前归档状态SELECT * FROM V$ARCHIVED_LOG WHERE STATUS A ORDER BY FIRST_TIME DESC;然后确认ARCHIVELOG进程没有正在写文件再执行删除。如果是手工rm文件而不是用RMAN删完后还必须执行CROSSCHECK ARCHIVELOG ALL;同步控制文件信息否则备份恢复时会报文件找不到。4.2 SQL Server日志文件备份后会自动收缩吗这个问题的准确答案是**默认不会除非显式配置了AUTO_SHRINK。**很多面试者以为“日志备份完成了日志文件体积就该降下来”这是混淆了日志备份和日志截断两者的关系。SQL Server备份事务日志的行为通常是备份完成后日志文件中已经备份过的部分被标记为可复用inactive日志逻辑上截断但物理文件大小不会立刻变化。数据文件就更明显——删除数据后mdf物理文件也不会自动收缩。要让文件真正缩小需要手动执行DBCC SHRINKFILE或者开启数据库选项AUTO_SHRINK ON但AUTO_SHRINK在生产上一般不建议开因为它会周期性触发文件收缩和索引碎片整理造成IO抖动。我见过不止一次运维同学以为是“备份后自动收缩”结果日志文件涨到几百GB磁盘告警。正确做法是把自动收缩关掉写一个维护计划定期执行DBCC SHRINKFILE同时搭配合理的日志备份频率让日志长度保持稳定。4.3 查看Oracle数据库日志存储时间Oracle里查看日志存储时间常查V$ARCHIVED_LOG和V$LOG_HISTORY。V$ARCHIVED_LOG记录每一个归档日志文件的创建时间、首次修改时间和归档完成时间查存储时间范围可以用SELECT THREAD#, SEQUENCE#, NAME, FIRST_TIME, NEXT_TIME, BLOCKS FROM V$ARCHIVED_LOG ORDER BY FIRST_TIME DESC;FIRST_TIME是日志中最早的重做记录产生时间NEXT_TIME是下一个日志的开始时间两者之间的差值基本就是这个归档日志覆盖的时间区间。如果只关心最近产生的归档日志还可以查V$LOG_HISTORY它更轻量记录了每条日志的切换时间和对应SCN。还有一个容易被忽略的点V$ARCHIVED_LOG里同一条日志可能有多行重复记录因为控制文件里会保留备份产生的副本记录排查时先做DISTINCT再去算时间不然统计结果会翻倍。4.4 归档日志清理与容灾备份的平衡生产环境里归档日志删除前要做两件事确认备份策略覆盖了这些日志确认PITR恢复目标。比如规定“数据库可以恢复到最近7天任意时间点”那么至少要保持7天的归档日志把这7天之前的归档清理掉。如果V$ARCHIVED_LOG显示日志已经备份到带库或云存储本地就不需要长期保留了。判断是否已备份Oracle里有一种常用查法SELECT SEQUENCE#, NAME, BACKUP_COUNT FROM V$ARCHIVED_LOG WHERE BACKUP_COUNT 0 AND DELETED NO;如果某条日志BACKUP_COUNT为0说明它从未被备份过这时候贸然删本地文件容灾链就断了。很多事故就是这么出的——磁盘空间不够直接清归档结果没过多久需要做时间点恢复发现中间档全没了。5. 把“八股”答出区分度面试追问与实战经验牛客面经刷多了你会发现面试官早已对标准答案免疫。同样的八股怎么答出区分度是决定过不过的关键。5.1 回答锁和日志问题的几个主线逻辑我自己的经验是回答这类题千万别按教科书顺序“锁有…分为…”而是先用一句话点明本质再展开。比如“锁的本质是让并发操作在冲突时排队日志的本质是把随机写变成顺序写让崩溃后可重放”这句话一出来面试官就知道你不是死背的。另外善用对比redo vs binlog共享锁 vs 排他锁表锁 vs 行锁悲观锁 vs 乐观锁。对比式回答不仅信息密度高还天然为后面的追问留了钩子。比如我回答隔离级别时会主动补一句“其实可重复读下也可能出现幻读因为MVCC快照读躲过了普通的幻读但当前读还是会被间隙锁约束”这句话基本能引导面试官往间隙锁、幻读这个方向深挖而我正好准备了例子。5.2 常见追问死锁排查、恢复场景、锁等待超时死锁排查几乎是必考题。我的回答框架是先讲死锁四必要条件互斥、持有并等待、不可剥夺、循环等待再讲InnoDB的处理策略——通过等待图检测死锁回滚undo量较小的事务并抛出Deadlock found错误。最后一定要落到线上排查SHOW ENGINE INNODB STATUS看LATEST DETECTED DEADLOCK里面会打印两个事务各自的SQL和持有锁类型。锁等待超时的题要能说出innodb_lock_wait_timeout和死锁检测是两个不同的机制。锁等待超时是事务等待锁的时间超过阈值就自动放弃死锁检测是发现循环等待后主动回滚一方。调参思路上超时时间设短能快速释放会话但业务会频繁报错设长又可能在高峰期拖垮连接池。比较合理的方案是先确认SQL是否走了索引把锁范围缩小再配合监控定位真正的热点行。5.3 生产环境踩坑记录从日志定位问题到锁冲突分享一个真实的排查经历。某天系统报“锁等待超时”我第一反应是查information_schema.innodb_trx发现确实有一个长事务卡在那里但奇怪的是它并没有锁住任何行。后来看日志发现这个事务已经idle in transaction接近20分钟它之前执行过的UPDATE锁了一大片记录事务一直没提交锁自然不会释放。后续所有对同一段范围数据的更新全部排队。这就是线上锁冲突里非常典型的场景不是并发太高而是长事务未提交。解决方法是先和业务确认这个事务是否还能继续不能就KILL掉再在应用层加超时控制避免事务悬挂。事后复盘时我们给应用层加了强制事务最大执行时间超过就自动回滚同时建了个监控脚本每5分钟扫一次information_schema.innodb_trx发现time超过阈值或者state为idle的事务立刻告警。5.4 给冲刺面试者的最后建议如果你正在准备牛客面经我的建议是不要只背答案要动手指一遍。自己搭个MySQL实例开两个会话手动BEGIN后执行SELECT ... FOR UPDATE再用另一个会话去更新同一行分别体验行锁、间隙锁、死锁再模拟一次kill -9数据库进程看redo log怎么把数据恢复回来。这些实验做完你对锁和日志的理解会比刷一百道题都扎实。面试时如果遇到没准备过的场景题不要慌用“定位现象→缩小范围→找到根因→给出方案”这个框架现场推理结合你手动实验得出的细节比硬背的八股可信得多。