Skip to content

SQL Server 面试

SQL Server 怎么从零学到生产可用

text
SQL Server 要按 T-SQL、表设计、页和文件、聚集/非聚集索引、执行计划、事务日志、锁和行版本、TempDB、参数嗅探和线上排查这条线学习。先理解数据存放在 MDF/NDF 数据文件中,事务日志在 LDF 中,数据按 8KB 页读写并缓存在 Buffer Pool。再理解聚集索引叶子层就是数据行,非聚集索引叶子层保存索引键和行定位器,查询缺列时可能 Key Lookup。SQL 执行时优化器根据统计信息选择计划,生产排查要看实际执行计划、逻辑读、估算和实际行数差异、等待类型、锁、TempDB 和日志。

原理:从零到生产级掌握核心全过程原理

SQL Server、MySQL、PostgreSQL、Oracle 怎么横向比较

text
SQL Server 和其他关系型数据库的共同点是都支持 SQL、索引、事务、执行计划和恢复;差异在内部实现和生态。SQL Server 常见于 .NET 和微软企业生态,重点是 MDF/NDF 数据文件、LDF 事务日志、8KB 页、Buffer Pool、聚集索引、非聚集索引、Key Lookup、锁、RCSI、TempDB、执行计划缓存和参数嗅探。MySQL InnoDB 更强调 B+Tree、聚簇索引、undo、redo、binlog;PostgreSQL 更强调 heap tuple 多版本、VACUUM、WAL 和扩展索引;Oracle 更强调实例、SGA/PGA、Undo、Redo、SCN 和企业级高可用。选型时不能只看 SQL 语法,要看团队生态、事务强度、报表 BI、HA 能力和运维经验。

追问:

  1. SQL Server 聚集索引和 MySQL InnoDB 聚簇索引有什么相似点?
  2. SQL Server Transaction Log 和 MySQL redo/binlog 有什么区别?
  3. RCSI 为什么会增加 TempDB 压力?
  4. 参数嗅探为什么是 SQL Server 高频问题?

原理:主流关系库核心差异与选型关系型数据库底层原理索引、锁与行版本原理

同一条 SQL 在不同数据库为什么性能不同

text
因为 SQL 文本只是逻辑请求,真正性能取决于表组织、索引叶子层、统计信息、执行计划、可见性判断和回表方式。SQL Server 聚集索引叶子层就是数据行,非聚集索引叶子层保存索引键和行定位器;查询字段缺失时会 Key Lookup,少量 Lookup 可以接受,大量 Lookup 会产生很多随机访问。MySQL InnoDB 二级索引可能回聚簇索引,PostgreSQL 通过 TID 回 heap 并检查可见性,Oracle 通过 ROWID 回表。所以“都走索引”不代表成本一样,还要看 logical reads、Actual Rows、Lookup 次数、Sort/Hash 是否 spill。

追问:

  1. Index Seek 为什么不一定快?
  2. Key Lookup 多了怎么优化?
  3. Estimated Rows 和 Actual Rows 差距大说明什么?
  4. 为什么覆盖索引不能无限加 include 列?

原理:同一条列表查询在四种数据库里的差异为什么走了索引还是慢索引与优化器

SQL Server 一条查询怎么执行

text
SQL Server 查询会先由查询处理器解析和绑定对象,再由优化器根据统计信息和成本选择执行计划。执行计划由 Index Seek、Index Scan、Key Lookup、Nested Loops、Hash Match、Sort 等算子组成。执行时存储引擎按计划访问数据页和索引页,优先从 Buffer Pool 读取,未命中才物理读磁盘。排查慢 SQL 要看实际执行计划、估算行数和实际行数差异、逻辑读、等待类型以及是否有大量 Key Lookup、Sort 或 Hash 溢出。

原理:核心全过程原理性能排查

聚集索引和非聚集索引区别

text
SQL Server 聚集索引的叶子层就是数据行,因此一个表只能有一个聚集索引;非聚集索引叶子层保存索引键和行定位器,通过行定位器再找到聚集索引行或堆表行。如果查询字段不在非聚集索引里,可能发生 Key Lookup。可以通过 INCLUDE 覆盖字段减少 Key Lookup。

原理:索引与优化器索引、锁与行版本原理

SQL Server 事务日志作用

text
SQL Server 事务日志记录数据修改过程,用于事务回滚、崩溃恢复、日志备份和高可用复制。事务提交时日志保证修改可恢复,数据页可以稍后刷盘。恢复时通过日志重做已提交事务并撤销未提交事务。

原理:架构与存储

SQL Server update 和 commit 过程

text
SQL Server 更新数据时,会先定位目标行并获取相应锁,然后记录事务日志,修改 Buffer Pool 中的数据页,并维护相关索引。事务提交时日志保证修改可恢复,数据页本身可以稍后由后台写回数据文件。恢复时 SQL Server 会根据事务日志重做已提交事务并撤销未提交事务,所以事务日志不仅用于回滚,也用于崩溃恢复、日志备份和高可用同步。

原理:核心全过程原理架构与存储

SQL Server 锁升级是什么

text
锁升级是 SQL Server 在大量细粒度锁带来较高管理成本时,可能把行锁或页锁升级为表锁。它能降低锁管理开销,但会扩大阻塞范围,导致并发下降。减少锁升级影响可以通过缩小事务、批量分段处理、优化索引减少扫描行数等方式。

原理:事务与锁索引、锁与行版本原理

SQL Server 参数嗅探是什么

text
参数嗅探是 SQL Server 编译执行计划时根据第一次执行的参数值估算行数并缓存计划。第一次参数返回很少时,优化器可能生成适合小结果集的计划;后续换成返回很多数据的参数却复用这个计划,就可能突然变慢。排查时看实际执行计划中估算行数和实际行数差异,结合统计信息、重新编译、Query Store、索引和 SQL 改写处理。

原理:核心全过程原理性能排查

SQL Server 慢 SQL 怎么排查

text
SQL Server 慢 SQL 排查要先定位 SQL、真实参数、调用量和会话,再看实际执行计划。重点看估算行数和实际行数是否差距大、logical reads 是否高、是否有大量 Key Lookup、Sort 或 Hash Match 是否 spill 到 TempDB。如果计划本身不明显异常,就看当前等待类型、blocking_session_id、锁、TempDB、事务日志和 IO。不能只看 Index Seek,也不能只说加索引。

追问:

  1. 为什么真实参数很重要?
  2. logical reads 比单次耗时更能说明什么?
  3. Sort/Hash spill 说明什么?

原理:性能排查:拿到SQL和参数实际执行计划和逻辑读

SQL Server 为什么 Index Seek 也可能慢

text
Index Seek 只说明使用索引定位了某个范围,不代表返回行少。如果 Seek 后返回几十万行,再做大量 Key Lookup、排序或 Join,仍然会很慢。排查时要看 Actual Rows、logical reads、Key Lookup 次数、返回字段是否被索引覆盖,以及估算行数和实际行数是否一致。

追问:

  1. Seek 和 Scan 的本质区别是什么?
  2. 为什么 Seek 后大量 Key Lookup 会慢?
  3. 覆盖索引为什么不能无限 include 字段?

原理:Key Lookup为什么会拖慢索引、锁与行版本原理

SQL Server 统计信息为什么重要

text
SQL Server 优化器依赖统计信息估算返回行数、Join 成本、排序成本和内存授予。如果统计信息过期、数据倾斜、多列相关或临时表统计不足,Estimated Rows 和 Actual Rows 会差很多,计划就可能选错。例如本该 Hash Join 却选 Nested Loops,或者内存授予太小导致 Sort/Hash 溢写 TempDB。

追问:

  1. Estimated Rows 和 Actual Rows 差很多说明什么?
  2. 大批量导入后为什么要关注统计信息?
  3. 统计信息和参数嗅探有什么关系?

原理:性能排查:估算行数为什么错统计信息为什么重要

SQL Server Sort/Hash spill 是什么

text
Sort/Hash spill 是指排序、Hash Join 或 Hash 聚合需要的中间结果超过了获得的内存授予,被迫写入 TempDB。它会把内存计算变成 TempDB 读写,查询明显变慢。常见原因是估算行数偏小、统计信息过期、参数嗅探、并发内存竞争或 SQL 一次处理中间结果太大。处理方向是修估算、减少中间结果、用索引减少排序、报表异步化或迁到报表库。

追问:

  1. Memory Grant 是什么?
  2. 为什么估算行数偏小会导致 spill?
  3. 加内存一定能解决 spill 吗?

原理:Sort、Hash溢写到TempDBTempDB为什么会成为瓶颈

RCSI 解决什么问题,有什么代价

text
RCSI 让 Read Committed 下的普通读使用行版本,减少读写阻塞。写事务更新数据时,旧版本会放到版本存储中,读事务可以按语句开始时的一致版本读取,不必等待写锁释放。代价是 TempDB 版本存储和 IO 压力增加,长事务会保留更多旧版本,写写冲突仍然需要锁,所以开启 RCSI 后仍要监控 TempDB、长事务和阻塞。

原理:从零到生产级掌握索引、锁与行版本原理

SQL Server 事务日志为什么不能随便删

text
SQL Server 事务日志不是普通文本日志,而是事务恢复机制核心。更新数据时会先记录日志,再修改 Buffer Pool 中的数据页;提交时日志保证事务可恢复。宕机恢复时 SQL Server 根据日志重做已提交事务并撤销未提交事务。Full 恢复模式下,日志还用于日志备份和时间点恢复,Always On、复制等高可用也依赖日志传递和重放。所以不能把 LDF 当成可随便删除的文件,日志暴涨要查恢复模式、日志备份、大事务和副本延迟。

原理:从零到生产级掌握架构与存储

identity 自增有什么特点

text
identity 是 SQL Server 常用自增列,数据库在插入时自动生成递增值,适合单库内部主键。它的优点是使用简单、写入定位清楚;边界是不能直接当成分布式全局 ID,也不保证业务连续无间断。事务回滚、插入失败、缓存机制都可能让 identity 出现跳号。业务订单号、资产编号这类对外编号不应直接依赖 identity 连续性。

追问:

  1. identity 跳号是不是数据库 Bug?
  2. identity 和业务编号怎么区分?
  3. 分库分表后 identity 还能不能作为全局 ID?

原理:基础 T-SQL:常用类型分布式ID

output inserted/deleted 有什么用

text
output inserted/deleted 可以在 insert、update、delete 时返回变更后的行或变更前的行。inserted 表示新值,deleted 表示旧值。它常用于插入后返回自增主键、更新时记录审计日志、删除前保存被删除数据。它比应用再查一次更直接,也能避免并发下二次查询读到变化后的其他状态。

追问:

  1. update 时 inserted 和 deleted 分别是什么?
  2. 审计日志为什么适合用 output?
  3. output 能不能替代事务?

原理:基础 T-SQL:output inserted/deleted

SQL Server 深分页为什么慢

text
offset fetch 深分页慢,是因为数据库不是直接跳到目标页,而是需要按过滤条件和排序规则找到前 N + pageSize 行,再丢弃前 N 行。页码越深,需要跳过的数据越多,排序、扫描和逻辑读越大。商业项目中消息列表、采集记录、资产流水更适合游标分页;必须任意跳页的报表要限制条件、异步导出或做汇总。

追问:

  1. offset 100000 fetch 20 内部大概做了什么?
  2. 游标分页适合什么场景?
  3. 为什么 order by 字段最好和索引配合?

原理:基础 T-SQL:深分页性能排查

SQL Server 为什么金额不能用 float

text
float 是二进制浮点类型,很多十进制金额不能精确表示,会出现精度误差。金额、费率结算、资产原值、支付金额应使用 decimal,并明确精度和小数位。float 更适合科学计算或允许误差的测量值,不适合财务金额。

追问:

  1. decimal(18,2) 里的 18 和 2 分别表示什么?
  2. Java 里金额应该用什么类型?
  3. 数据库金额和 Java 金额怎么保持一致?

原理:基础 T-SQL:常用类型Java金额精度

日期字段为什么推荐 datetime2

text
datetime2 的时间范围和精度比老的 datetime 更好,是 SQL Server 新系统里更推荐的时间类型。商业系统里 created_at、updated_at、paid_at、collected_at 这类字段用于查询、审计、排序、归档和排查,类型要稳定。日期查询建议使用范围条件,而不是对列做 convert 或格式化函数,否则可能影响索引使用。

追问:

  1. 为什么 convert(varchar, CreatedAt) 可能导致索引效率下降?
  2. 查询某一天的数据应该怎么写?
  3. 时间字段为什么对排查线上问题重要?

原理:基础 T-SQL:常见函数写法

SQL Server 为什么要带状态条件更新

text
状态条件更新是为了防止并发覆盖和重复操作。例如资产从待审核变为已审核时,update 要带 `where status = 待审核`。如果影响行数为 0,说明状态已被别人改过或数据不存在,业务应该返回失败或重试。只按 id 更新会让旧请求覆盖新状态,导致审批、支付、库存等状态机失控。

追问:

  1. 影响行数为 0 应该怎么处理?
  2. 状态条件和乐观锁有什么关系?
  3. 为什么订单支付成功更新必须防重复?

原理:基础 T-SQL:Update事务与锁

SQL Server 软删除有什么利弊

text
软删除是通过 IsDeleted、DeletedAt 等字段标记删除,而不是物理删除行。优点是可审计、可恢复、减少误删风险;缺点是所有查询都要带未删除条件,索引也要考虑 IsDeleted,数据长期累积会增加表和索引体积。核心业务表常用软删除,临时表、日志表、可重建数据可以按生命周期物理清理。

追问:

  1. 软删除后唯一约束怎么设计?
  2. 查询忘记 IsDeleted 会怎样?
  3. 数据越来越多后软删除表怎么治理?

原理:基础 T-SQL:Delete

SQL Server 日志暴涨怎么排查

text
日志暴涨要先看恢复模式。如果是 Full 模式,要确认是否定期做事务日志备份;只做完整备份不会截断日志。再看是否有大事务、长事务、索引重建、大批量导入、Always On 或复制延迟导致日志无法截断。不能直接删除 LDF,也不要把 shrink 当常规手段,应该先修复日志备份链路和事务问题,再规划合理日志文件大小。

追问:

  1. Full Backup 能不能截断日志?
  2. shrink 日志为什么不是常规方案?
  3. Always On 延迟为什么可能导致日志不能截断?

原理:备份与高可用:日志暴涨架构与存储:事务日志

SQL Server WRITELOG 等待是什么

text
WRITELOG 表示会话在等待事务日志写入完成。SQL Server 提交事务时要保证相关日志写入 LDF,才能保证宕机后可恢复。WRITELOG 高常见于日志磁盘慢、事务提交太频繁、批量导入或更新产生日志太多、日志文件自动增长、Always On 同步压力等。优化方向包括日志盘性能、合理批量提交、减少无意义小事务、预分配日志文件和检查高可用副本延迟。

追问:

  1. 为什么提交要等日志写入?
  2. 每行 commit 为什么会拖慢批处理?
  3. 日志文件自动增长为什么影响性能?

原理:事务日志写入瓶颈事务与锁:事务和日志

SQL Server 误删数据怎么恢复

text
误删数据不要优先把主库整体回退,因为会丢失误删之后的正常业务。更常见做法是用完整备份、差异备份和日志备份恢复到临时库,并通过 STOPAT 恢复到误删前时间点,然后导出误删数据,经业务校验后回灌主库。这个过程依赖 Full 恢复模式和完整日志备份链。

追问:

  1. 为什么不直接把主库恢复到误删前?
  2. STOPAT 依赖什么备份链?
  3. Always On 副本能不能防误删?

原理:备份与高可用:误删数据

SQL Server TempDB 为什么重要

text
TempDB 是 SQL Server 的临时工作区,用于临时表、排序、Hash Join、Hash 聚合、游标和行版本存储。复杂报表、RCSI、临时表滥用、大查询内存不足都可能压垮 TempDB。TempDB 慢会影响很多看似无关的业务,所以排查性能时要关注 Sort、Hash 溢写、版本存储、TempDB 空间和 IO。

追问:

  1. RCSI 为什么会增加 TempDB 压力?
  2. 大排序为什么可能用 TempDB?
  3. TempDB 慢为什么会影响全库?

原理:架构与存储:TempDB性能排查:TempDB

SQL Server Query Store 有什么用

text
Query Store 用于记录查询文本、执行计划和运行时统计,适合排查“昨天快今天慢”“发布后变慢”“参数嗅探导致计划抖动”。它可以看到同一查询的多个计划、每个计划的耗时和逻辑读,也可以在紧急情况下强制使用某个稳定计划。但强制计划只是止血手段,根因仍然要回到统计信息、索引、参数策略和业务数据分布。

追问:

  1. Query Store 和执行计划缓存有什么区别?
  2. 什么时候可以 force plan?
  3. 为什么 force plan 不是最终优化?

原理:性能排查:Query Store怎么帮助排查

SQL Server 抢任务怎么避免重复和阻塞

text
审批、采集、补偿任务这类抢单场景,不能简单先 select 再 update,否则多个执行器可能选到同一批任务。SQL Server 可以用 updlock 提前拿更新锁,用 readpast 跳过已锁行,用单条 update output 返回真正抢到的任务。这样能减少重复领取和锁等待,但必须配合状态条件、合适索引、幂等和超时重置机制。

追问:

  1. updlock 和普通 select 有什么区别?
  2. readpast 会不会跳过任务?
  3. 为什么 output inserted 适合抢任务?

原理:性能排查:审批任务抢单阻塞事务与锁

SQL Server 批量删除为什么要分批

text
一次删除几千万行会形成大事务,产生大量事务日志,持有大量锁,可能触发锁升级,阻塞在线业务,并且失败回滚时间很长。生产上通常按小批次 delete top(n),中间适当停顿,或者对日志/流水表使用分区表按时间切换分区。分批的核心是控制日志量、锁范围、回滚成本和业务影响。

追问:

  1. 大事务为什么导致日志暴涨?
  2. 锁升级为什么会扩大阻塞?
  3. 分区表为什么比大 delete 更适合生命周期清理?

原理:性能排查:批量删除导致日志暴涨和锁升级事务与锁:日志暴涨

SQL Server 支付回调为什么要状态条件更新

text
支付回调、审批流、库存扣减都属于状态机更新,update 必须带当前状态条件。例如 status='PAYING' 才能改 SUCCESS。这样数据库能原子判断状态是否仍然允许变更,影响行数为 0 时说明订单不存在、状态已变化或重复回调,应用再查询当前状态做幂等处理。只按主键更新可能让旧请求覆盖新状态。

追问:

  1. 影响行数为 0 是失败还是幂等成功?
  2. 状态条件和乐观锁 version 有什么区别?
  3. 为什么唯一索引和状态条件都重要?

原理:性能排查:支付回调更新慢基础 T-SQL:Update

SQL Server 等待类型怎么读

text
等待类型是 SQL Server 告诉你请求正在等什么。LCK_M_* 通常表示锁等待,PAGEIOLATCH_* 表示数据页物理 IO,WRITELOG 表示事务日志写入,PAGELATCH_* 表示内存页 latch 竞争,ASYNC_NETWORK_IO 可能是客户端消费结果慢。等待类型只能提供方向,不能直接等于根因,必须结合 SQL、执行计划、会话、业务场景一起判断。

追问:

  1. LCK_M_X 和死锁是一回事吗?
  2. ASYNC_NETWORK_IO 一定是网络问题吗?
  3. 为什么等待类型不能脱离 SQL 看?

原理:性能排查:等待类型怎么读