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 能力和运维经验。追问:
- SQL Server 聚集索引和 MySQL InnoDB 聚簇索引有什么相似点?
- SQL Server Transaction Log 和 MySQL redo/binlog 有什么区别?
- RCSI 为什么会增加 TempDB 压力?
- 参数嗅探为什么是 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。追问:
- Index Seek 为什么不一定快?
- Key Lookup 多了怎么优化?
- Estimated Rows 和 Actual Rows 差距大说明什么?
- 为什么覆盖索引不能无限加 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,也不能只说加索引。追问:
- 为什么真实参数很重要?
- logical reads 比单次耗时更能说明什么?
- Sort/Hash spill 说明什么?
SQL Server 为什么 Index Seek 也可能慢
text
Index Seek 只说明使用索引定位了某个范围,不代表返回行少。如果 Seek 后返回几十万行,再做大量 Key Lookup、排序或 Join,仍然会很慢。排查时要看 Actual Rows、logical reads、Key Lookup 次数、返回字段是否被索引覆盖,以及估算行数和实际行数是否一致。追问:
- Seek 和 Scan 的本质区别是什么?
- 为什么 Seek 后大量 Key Lookup 会慢?
- 覆盖索引为什么不能无限 include 字段?
原理:Key Lookup为什么会拖慢、索引、锁与行版本原理。
SQL Server 统计信息为什么重要
text
SQL Server 优化器依赖统计信息估算返回行数、Join 成本、排序成本和内存授予。如果统计信息过期、数据倾斜、多列相关或临时表统计不足,Estimated Rows 和 Actual Rows 会差很多,计划就可能选错。例如本该 Hash Join 却选 Nested Loops,或者内存授予太小导致 Sort/Hash 溢写 TempDB。追问:
- Estimated Rows 和 Actual Rows 差很多说明什么?
- 大批量导入后为什么要关注统计信息?
- 统计信息和参数嗅探有什么关系?
SQL Server Sort/Hash spill 是什么
text
Sort/Hash spill 是指排序、Hash Join 或 Hash 聚合需要的中间结果超过了获得的内存授予,被迫写入 TempDB。它会把内存计算变成 TempDB 读写,查询明显变慢。常见原因是估算行数偏小、统计信息过期、参数嗅探、并发内存竞争或 SQL 一次处理中间结果太大。处理方向是修估算、减少中间结果、用索引减少排序、报表异步化或迁到报表库。追问:
- Memory Grant 是什么?
- 为什么估算行数偏小会导致 spill?
- 加内存一定能解决 spill 吗?
原理:Sort、Hash溢写到TempDB、TempDB为什么会成为瓶颈。
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 连续性。追问:
- identity 跳号是不是数据库 Bug?
- identity 和业务编号怎么区分?
- 分库分表后 identity 还能不能作为全局 ID?
原理:基础 T-SQL:常用类型、分布式ID。
output inserted/deleted 有什么用
text
output inserted/deleted 可以在 insert、update、delete 时返回变更后的行或变更前的行。inserted 表示新值,deleted 表示旧值。它常用于插入后返回自增主键、更新时记录审计日志、删除前保存被删除数据。它比应用再查一次更直接,也能避免并发下二次查询读到变化后的其他状态。追问:
- update 时 inserted 和 deleted 分别是什么?
- 审计日志为什么适合用 output?
- output 能不能替代事务?
原理:基础 T-SQL:output inserted/deleted。
SQL Server 深分页为什么慢
text
offset fetch 深分页慢,是因为数据库不是直接跳到目标页,而是需要按过滤条件和排序规则找到前 N + pageSize 行,再丢弃前 N 行。页码越深,需要跳过的数据越多,排序、扫描和逻辑读越大。商业项目中消息列表、采集记录、资产流水更适合游标分页;必须任意跳页的报表要限制条件、异步导出或做汇总。追问:
- offset 100000 fetch 20 内部大概做了什么?
- 游标分页适合什么场景?
- 为什么 order by 字段最好和索引配合?
原理:基础 T-SQL:深分页、性能排查。
SQL Server 为什么金额不能用 float
text
float 是二进制浮点类型,很多十进制金额不能精确表示,会出现精度误差。金额、费率结算、资产原值、支付金额应使用 decimal,并明确精度和小数位。float 更适合科学计算或允许误差的测量值,不适合财务金额。追问:
- decimal(18,2) 里的 18 和 2 分别表示什么?
- Java 里金额应该用什么类型?
- 数据库金额和 Java 金额怎么保持一致?
日期字段为什么推荐 datetime2
text
datetime2 的时间范围和精度比老的 datetime 更好,是 SQL Server 新系统里更推荐的时间类型。商业系统里 created_at、updated_at、paid_at、collected_at 这类字段用于查询、审计、排序、归档和排查,类型要稳定。日期查询建议使用范围条件,而不是对列做 convert 或格式化函数,否则可能影响索引使用。追问:
- 为什么
convert(varchar, CreatedAt)可能导致索引效率下降? - 查询某一天的数据应该怎么写?
- 时间字段为什么对排查线上问题重要?
原理:基础 T-SQL:常见函数写法。
SQL Server 为什么要带状态条件更新
text
状态条件更新是为了防止并发覆盖和重复操作。例如资产从待审核变为已审核时,update 要带 `where status = 待审核`。如果影响行数为 0,说明状态已被别人改过或数据不存在,业务应该返回失败或重试。只按 id 更新会让旧请求覆盖新状态,导致审批、支付、库存等状态机失控。追问:
- 影响行数为 0 应该怎么处理?
- 状态条件和乐观锁有什么关系?
- 为什么订单支付成功更新必须防重复?
原理:基础 T-SQL:Update、事务与锁。
SQL Server 软删除有什么利弊
text
软删除是通过 IsDeleted、DeletedAt 等字段标记删除,而不是物理删除行。优点是可审计、可恢复、减少误删风险;缺点是所有查询都要带未删除条件,索引也要考虑 IsDeleted,数据长期累积会增加表和索引体积。核心业务表常用软删除,临时表、日志表、可重建数据可以按生命周期物理清理。追问:
- 软删除后唯一约束怎么设计?
- 查询忘记 IsDeleted 会怎样?
- 数据越来越多后软删除表怎么治理?
原理:基础 T-SQL:Delete。
SQL Server 日志暴涨怎么排查
text
日志暴涨要先看恢复模式。如果是 Full 模式,要确认是否定期做事务日志备份;只做完整备份不会截断日志。再看是否有大事务、长事务、索引重建、大批量导入、Always On 或复制延迟导致日志无法截断。不能直接删除 LDF,也不要把 shrink 当常规手段,应该先修复日志备份链路和事务问题,再规划合理日志文件大小。追问:
- Full Backup 能不能截断日志?
- shrink 日志为什么不是常规方案?
- Always On 延迟为什么可能导致日志不能截断?
SQL Server WRITELOG 等待是什么
text
WRITELOG 表示会话在等待事务日志写入完成。SQL Server 提交事务时要保证相关日志写入 LDF,才能保证宕机后可恢复。WRITELOG 高常见于日志磁盘慢、事务提交太频繁、批量导入或更新产生日志太多、日志文件自动增长、Always On 同步压力等。优化方向包括日志盘性能、合理批量提交、减少无意义小事务、预分配日志文件和检查高可用副本延迟。追问:
- 为什么提交要等日志写入?
- 每行 commit 为什么会拖慢批处理?
- 日志文件自动增长为什么影响性能?
原理:事务日志写入瓶颈、事务与锁:事务和日志。
SQL Server 误删数据怎么恢复
text
误删数据不要优先把主库整体回退,因为会丢失误删之后的正常业务。更常见做法是用完整备份、差异备份和日志备份恢复到临时库,并通过 STOPAT 恢复到误删前时间点,然后导出误删数据,经业务校验后回灌主库。这个过程依赖 Full 恢复模式和完整日志备份链。追问:
- 为什么不直接把主库恢复到误删前?
- STOPAT 依赖什么备份链?
- Always On 副本能不能防误删?
原理:备份与高可用:误删数据。
SQL Server TempDB 为什么重要
text
TempDB 是 SQL Server 的临时工作区,用于临时表、排序、Hash Join、Hash 聚合、游标和行版本存储。复杂报表、RCSI、临时表滥用、大查询内存不足都可能压垮 TempDB。TempDB 慢会影响很多看似无关的业务,所以排查性能时要关注 Sort、Hash 溢写、版本存储、TempDB 空间和 IO。追问:
- RCSI 为什么会增加 TempDB 压力?
- 大排序为什么可能用 TempDB?
- TempDB 慢为什么会影响全库?
SQL Server Query Store 有什么用
text
Query Store 用于记录查询文本、执行计划和运行时统计,适合排查“昨天快今天慢”“发布后变慢”“参数嗅探导致计划抖动”。它可以看到同一查询的多个计划、每个计划的耗时和逻辑读,也可以在紧急情况下强制使用某个稳定计划。但强制计划只是止血手段,根因仍然要回到统计信息、索引、参数策略和业务数据分布。追问:
- Query Store 和执行计划缓存有什么区别?
- 什么时候可以 force plan?
- 为什么 force plan 不是最终优化?
SQL Server 抢任务怎么避免重复和阻塞
text
审批、采集、补偿任务这类抢单场景,不能简单先 select 再 update,否则多个执行器可能选到同一批任务。SQL Server 可以用 updlock 提前拿更新锁,用 readpast 跳过已锁行,用单条 update output 返回真正抢到的任务。这样能减少重复领取和锁等待,但必须配合状态条件、合适索引、幂等和超时重置机制。追问:
updlock和普通 select 有什么区别?readpast会不会跳过任务?- 为什么 output inserted 适合抢任务?
原理:性能排查:审批任务抢单阻塞、事务与锁。
SQL Server 批量删除为什么要分批
text
一次删除几千万行会形成大事务,产生大量事务日志,持有大量锁,可能触发锁升级,阻塞在线业务,并且失败回滚时间很长。生产上通常按小批次 delete top(n),中间适当停顿,或者对日志/流水表使用分区表按时间切换分区。分批的核心是控制日志量、锁范围、回滚成本和业务影响。追问:
- 大事务为什么导致日志暴涨?
- 锁升级为什么会扩大阻塞?
- 分区表为什么比大 delete 更适合生命周期清理?
原理:性能排查:批量删除导致日志暴涨和锁升级、事务与锁:日志暴涨。
SQL Server 支付回调为什么要状态条件更新
text
支付回调、审批流、库存扣减都属于状态机更新,update 必须带当前状态条件。例如 status='PAYING' 才能改 SUCCESS。这样数据库能原子判断状态是否仍然允许变更,影响行数为 0 时说明订单不存在、状态已变化或重复回调,应用再查询当前状态做幂等处理。只按主键更新可能让旧请求覆盖新状态。追问:
- 影响行数为 0 是失败还是幂等成功?
- 状态条件和乐观锁 version 有什么区别?
- 为什么唯一索引和状态条件都重要?
原理:性能排查:支付回调更新慢、基础 T-SQL:Update。
SQL Server 等待类型怎么读
text
等待类型是 SQL Server 告诉你请求正在等什么。LCK_M_* 通常表示锁等待,PAGEIOLATCH_* 表示数据页物理 IO,WRITELOG 表示事务日志写入,PAGELATCH_* 表示内存页 latch 竞争,ASYNC_NETWORK_IO 可能是客户端消费结果慢。等待类型只能提供方向,不能直接等于根因,必须结合 SQL、执行计划、会话、业务场景一起判断。追问:
LCK_M_X和死锁是一回事吗?ASYNC_NETWORK_IO一定是网络问题吗?- 为什么等待类型不能脱离 SQL 看?
原理:性能排查:等待类型怎么读。
