基础题
1. 主键、外键、超键、候选键的区别和用途
- 超键:在关系中能唯一标识元组的属性集称为关系模式的超键。一个属性可以作为一个超键,多个属性组合在一起也可以作为一个超键。超键包含候选键和主键。
- 候选键:是最小超键,即没有冗余元素的超键。
- 主键:数据库表中对储存数据对象予以唯一和完整标识的数据列或属性的组合。一个数据列只能有一个主键,且主键的取值不能缺失,即不能为空值(Null)。
- 外键:在一个表中存在的另一个表的主键称此表的外键。
2. 为什么使用自增列作为主键?
自增列作为主键可以简化数据的插入操作,避免因插入非顺序的主键值导致的索引分裂和碎片化,从而提高数据库性能。自增列也易于分配和管理,且不会与其他记录的主键冲突。
3. 触发器的作用是什么?
触发器是一种特殊的存储过程,它在特定数据库操作(如INSERT、UPDATE、DELETE)执行之前或之后自动触发执行。触发器可以用于维护数据完整性、实施复杂的业务规则、自动更新表中的数据等。
4. 什么是存储过程?使用什么来调用?
存储过程是一组为了执行特定任务而预编译的SQL语句。它们可以提高性能,因为只需编译一次,之后可以重复调用。存储过程可以通过SQL命令直接调用,也可以被应用程序通过特定的API调用来执行。
5. 存储过程的优缺点有哪些?
存储过程的优点包括提高性能(预编译)、减少网络传输、增强安全性(需要特定权限才能执行)、便于代码复用。缺点包括移植性差,因为它们通常与特定的数据库系统紧密相关。
6. 存储过程与函数的区别是什么?
存储过程是一系列为了完成特定功能的SQL语句集合,可以通过参数传递数据,并且可以有多个返回值。函数通常返回一个单一的数据值,并且在使用时作为表达式的一部分。存储过程使用更灵活,而函数则更适用于需要返回特定数据结构的场景。
7. 视图是什么?游标是什么?
视图是基于SQL查询的虚拟表,它像实际的表一样可以进行查询和更新操作,但是不存储数据,而是在查询视图时动态生成结果。游标是一种数据库对象,用于逐行处理查询结果集,常用于需要对结果集进行循环处理的场景。
8. 视图的优缺点有哪些?
视图的优点包括简化复杂的查询、提高数据安全性、实现数据逻辑抽象。缺点包括可能影响性能(尤其是在复杂的视图上执行查询时),以及在某些情况下限制了数据的更新操作。
9. 什么是临时表?临时表什么时候删除?
临时表是在当前会话或事务中创建的表,仅对当前会话可见。当会话结束或事务提交时,临时表及其数据会自动删除。
10. 非关系型数据库和关系型数据库的区别和优势比较是什么?
非关系型数据库(NoSQL)和关系型数据库在数据模型、查询方式、扩展性等方面有本质区别。非关系型数据库通常提供更高的扩展性和灵活性,适合处理大规模分布式数据。关系型数据库则在数据一致性、复杂查询和事务管理方面表现更好。
11. 数据库范式是什么?如何根据某个场景设计数据表?
数据库范式是一套用于指导数据库设计的规范,包括第一范式(1NF)、第二范式(2NF)、第三范式(3NF)等,目的是减少数据冗余和提高数据完整性。设计数据表时,应根据业务需求和数据关系来确定表结构,确保满足相应的范式要求。
12. 内连接、外连接、交叉连接、笛卡尔积等的区别是什么?
内连接只返回两个表中匹配的行;外连接(左外连接、右外连接)会返回一个表的全部行,另一个表中匹配的行,不匹配的行用NULL填充;交叉连接返回两个表的笛卡尔积,即每行与另一个表中每行的组合;笛卡尔积是两个集合所有可能的组合。
13. varchar和char的使用场景是什么?
VARCHAR适用于长度可变的数据,如用户输入的评论或描述,因为它可以根据实际内容长度存储,节省空间。CHAR适用于长度固定的数据,如性别或国家代码,因为它可以提供更快的存取速度,但会使用固定长度的存储空间。
14. SQL语言分类有哪些?
SQL语言主要分为数据查询语言(DQL),数据操纵语言(DML),数据定义语言(DDL)和数据控制语言(DCL)。DQL用于查询数据,如SELECT;DML用于数据的增删改,如INSERT、UPDATE、DELETE;DDL用于数据库对象的定义,如CREATE、ALTER、DROP;DCL用于控制数据库访问权限,如GRANT、REVOKE。
15. like '%xxx%'和’xxx%'的区别是什么?
LIKE '%xxx%'表示匹配包含xxx的任意字符串,无论xxx出现在哪一部分。LIKE 'xxx%'表示匹配以xxx结尾的字符串。两者在模糊匹配时使用不同的通配符,%代表任意字符出现任意次数,而_仅代表单个字符。
16. count(*)、count(1)、count(column)的区别是什么?
COUNT()用于计算表中的总行数,包括NULL值。COUNT(1)是COUNT()的等价操作,用于计算行数。COUNT(column)用于计算特定列中非NULL值的数量。
17. 最左前缀原则是什么?
最左前缀原则是索引创建和使用的一个重要原则,它指的是在多列索引中,数据库查询优化器只会使用索引的最左部分列。这意味着如果查询条件没有使用到索引的第一个列,那么即使后面的列被使用到,索引也可能不会被利用。
18. 索引的作用是什么?它的优点和缺点有哪些?
索引的作用是加快数据检索速度,排序和分组数据,以及保证数据的唯一性。优点包括提高查询速度、加速表连接、支持数据的排序和分组。缺点包括增加存储空间、降低数据更新(INSERT、UPDATE、DELETE)的速度,以及维护索引本身需要额外的开销。
19. 什么样的字段适合建索引?
适合建索引的字段包括经常需要搜索的列、作为主键的列、经常用于连接的列、经常需要进行范围搜索的列、经常需要排序的列,以及经常使用在WHERE子句中的列。
20. 聚集索引和非聚集索引的区别是什么?
聚集索引决定了表中数据的物理存储顺序,使得相关列的数据在物理上连续存放,查询效率较高,但修改数据时可能较慢。非聚集索引指定了表中数据的逻辑顺序,但物理存储顺序与索引可能不一致,通常用于频繁更新的数据列。
21. SQL注入式攻击是什么?
SQL注入式攻击是一种网络安全攻击手段,攻击者通过在Web表单输入域或页面请求的查询字符串中插入恶意SQL命令,欺骗服务器执行这些命令,从而获取、篡改或删除数据库中的数据。
22. 如何防范SQL注入式攻击?
防范SQL注入式攻击的方法包括:对用户输入进行过滤和验证,替换或转义特殊字符;使用预处理语句(参数化查询);限制数据库权限,使用最小权限原则;使用存储过程;以及在服务器端进行输入验证等。
23. 内存泄漏是什么?
内存泄漏是指在程序运行过程中,由于未能适当释放不再使用的内存,导致随着程序的持续运行,可用内存逐渐减少的现象。在动态内存分配的语言中,如C或C++,如果使用new分配了内存,却忘记使用delete释放,就可能发生内存泄漏。
24. 维护数据库的完整性和一致性,使用触发器还是自写业务逻辑?
维护数据库的完整性和一致性,通常首选使用数据库提供的约束,如CHECK、PRIMARY KEY、FOREIGN KEY等。其次是使用触发器,因为它们可以自动执行,确保数据的完整性和一致性,无论哪种业务逻辑访问数据库。最后考虑自写业务逻辑,但这种方法编程复杂,效率较低。
25. 什么是事务?什么是锁?
事务是一系列操作,它们作为一个整体被执行,以确保数据的完整性。如果事务中的任何操作失败,整个事务将回滚到执行前的状态。锁是数据库管理系统用来保证事务的隔离性和并发控制的一种机制,它可以防止多个事务同时修改同一数据,从而避免数据冲突。
26. 过多索引对数据库性能的影响
过多的索引虽然可以提高查询速度,但在数据的插入、更新和删除操作时,数据库引擎需要更多的时间来维护这些索引,这可能会导致性能下降。因此,需要在索引创建时进行权衡,以确保数据库操作的整体性能。
27. 相关子查询是什么?如何使用这些查询?
相关子查询是一种特殊类型的子查询,它在查询中使用外部查询的值。这种子查询通常用于WHERE或HAVING子句中,可以基于外部查询的结果来动态地定义查询条件。
28. 操作会使⽤到TempDB
TempDB是SQL Server的一个系统数据库,用于存储临时数据,如临时表和表变量。许多操作,包括创建表时的临时数据、执行某些类型的JOIN操作、使用游标以及存储过程和批处理中的一些操作,都可能会用到TempDB。
29. 如果TempDB异常变大,可能的原因是什么,该如何处理?
TempDB异常变大可能是由于大量使用临时表或返回的记录集过大造成的。处理方法包括优化查询以减少返回的数据量,使用分批处理,或者调整TempDB的大小和配置。
30. Index有哪些类型,它们的区别和实现原理是什么,索引有什么优点和缺点
索引类型主要包括聚集索引和非聚集索引。聚集索引决定了表中数据的物理存储顺序,非聚集索引则不改变数据的物理存储顺序。索引的优点包括提高查询速度、确保数据的唯一性和排序。缺点是增加了存储空间和维护成本,降低了数据更新的速度。
31. Job信息可以通过哪些表获取;系统正在运行的语句可以通过哪些视图获取;如何获取某个T-SQL语句的IO、Time等信息
Job信息可以通过SQL Server的msdb数据库中的表,如sysjobs和sysjobhistory获取。系统正在运行的语句可以通过动态管理视图如sys.dm_exec_requests获取。要获取某个T-SQL语句的IO和Time等信息,可以使用SQL Server Profiler或相关的动态管理视图。
确保字段只接受特定范围内的值
可以通过在字段上设置CHECK约束来确保只接受特定范围内的值。CHECK约束允许定义字段值的范围或条件,确保插入或更新数据时满足这些条件。
32. CHAR、VARCHAR、NCHAR 和 NVARCHAR 的区别是什么?
- CHAR(n): 固定长度,非 Unicode 字符数据。无论实际内容多长,它都会占用
n个字节的存储空间。适合存储长度相对固定的数据(如身份证号、电话号码)。 - VARCHAR(n): 可变长度,非 Unicode 字符数据。它只占用实际数据长度 + 2 个字节(用于存储长度信息)的存储空间。适合存储长度变化较大的数据。
- NCHAR(n): 固定长度,Unicode 字符数据。存储 Unicode 字符(如中文、日文等),每个字符占用 2 个字节。长度为
n,表示最多可存储n个字符(无论中英文)。 - NVARCHAR(n): 可变长度,Unicode 字符数据。同样存储 Unicode 字符,每个字符 2 字节,但只占用(实际字符数 * 2) + 2 字节的空间。
核心区别: CHAR/VARCHAR 用于非 Unicode,一个英文字符占1字节,一个中文字符可能占2字节(取决于编码)。NCHAR/NVARCHAR 用于 Unicode,任何字符都占2字节,能全球通用。
33. TRUNCATE、DELETE 和 DROP 的区别?
| 特性 | DELETE | TRUNCATE | DROP |
|---|---|---|---|
| 类型 | DML(数据操作语言) | DDL(数据定义语言) | DDL(数据定义语言) |
| 条件 | 可以带 WHERE 子句 | 不能带条件,清空所有数据 | 删除整个表(结构和数据) |
| 事务 | 操作会被记录在事务日志中,可回滚 | 操作记录最少,不可回滚 | 操作不可回滚 |
| 触发器 | 会触发 DELETE 触发器 | 不会触发触发器 | - |
| 标识列 | 不影响标识列的当前值 | 重置标识列的种子值 | - |
| 性能 | 较慢(逐行删除并记录日志) | 非常快(直接释放数据页) | 快 |
| 锁 | 行级锁 | 表锁 | 表锁 |
34. 什么是索引?聚集索引和非聚集索引的区别?
- 索引:相当于书籍的目录,它能帮助数据库引擎快速找到数据,而无需扫描整个表。
- 聚集索引:
- 决定了表中数据的物理存储顺序。一张表只能有一个聚集索引。
- 叶子节点存储的是实际的数据行。
- 例如,在主键上默认创建的通常是聚集索引。
- 非聚集索引:
- 不影响数据的物理存储顺序。一张表可以有多个非聚集索引。
- 叶子节点存储的是索引键值 + 指向数据行的指针(聚集索引键或RID)。
- 查询时需要先查非聚集索引,再通过指针去查找实际数据,这个过程称为 “键查找” 或 “书签查找”。
35. 内连接(INNER JOIN)和外连接(OUTER JOIN)的区别?
- 内连接:返回两个表中连接条件匹配的所有行。不匹配的行不会出现在结果中。
- 外连接:
- 左外连接(LEFT JOIN):返回左表的所有行,以及右表中连接条件匹配的行。如果右表无匹配,则右表部分为 NULL。
- 右外连接(RIGHT JOIN):返回右表的所有行,以及左表中连接条件匹配的行。如果左表无匹配,则左表部分为 NULL。
- 全外连接(FULL JOIN):返回左表和右表中的所有行。当某一行在另一个表中没有匹配时,另一个表的部分为 NULL。
36. 什么是执行计划?如何查看和分析?
- 执行计划:是 SQL Server 查询优化器生成的、关于如何执行一个查询的“路线图”。它显示了数据获取的步骤、使用的索引、连接类型、数据量估计和成本等。
- 查看方法:
- 在 SSMS 中,在查询前按下
Ctrl + M(显示实际执行计划)或Ctrl + L(显示估计执行计划),然后执行查询。 - 使用 SET 语句:
SET SHOWPLAN_TEXT ON或SET STATISTICS PROFILE ON。
- 在 SSMS 中,在查询前按下
- 分析要点:
- 高成本操作:找到成本最高的步骤。
- 表扫描(Table Scan):警惕!这通常意味着没有合适的索引。
- 索引扫描(Index Scan) vs 索引查找(Index Seek): Seek 效率远高于 Scan。Scan 意味着遍历了整个索引。
- 键查找(Key Lookup):如果开销很大,考虑创建覆盖索引。
- 警告标志:如转换警告(隐式类型转换)等。
37. 什么是覆盖索引?
一个覆盖索引是指一个非聚集索引,它包含了查询中需要的所有字段。当查询的所有列都包含在索引的键或包含列中时,引擎可以直接从索引页中获取数据,而无需再去查找数据页,从而避免昂贵的键查找操作,极大提升性能。
创建覆盖索引示例:
CREATE INDEX IX_Covering ON Orders (CustomerID) INCLUDE (OrderDate, TotalAmount);
-- 对于查询: SELECT OrderDate, TotalAmount FROM Orders WHERE CustomerID = @ID
-- 这个索引就是覆盖索引。
38. 什么是索引碎片?如何维护?
- 索引碎片:当索引页的逻辑顺序与物理顺序不匹配,或者页的数据填充度很低时,就产生了碎片。碎片会导致更多的物理 I/O,降低查询性能。
- 类型:
- 外部碎片:页的逻辑顺序与物理顺序不符。
- 内部碎片:页中存在大量空闲空间。
- 维护方法:
- 重组(REORGANIZE):对叶级页以物理方式重新排序,并压缩索引页。是在线操作,干扰小。适用于轻度碎片。
- 重建(REBUILD):删除旧索引并创建一个新的索引。可以最大限度地减少碎片,是离线操作(在 Enterprise 版中可以在线)。适用于重度碎片。
39. 什么时候不适合创建索引?
- 表非常小(数据量很少)。
- 列的值重复度很低(如性别列,只有‘男’,‘女’),索引效果不佳。
- 列经常被频繁进行 INSERT/UPDATE/DELETE 操作,因为维护索引需要开销。
- 不会在查询的 WHERE 或 JOIN 条件中使用的列。
40. 谈谈 ACID 属性。
- 原子性(Atomicity):事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。
- 一致性(Consistency):事务必须使数据库从一个一致性状态变换到另一个一致性状态。
- 隔离性(Isolation):一个事务的执行不能被其他事务干扰。
- 持久性(Durability):一旦事务提交,它对数据库中数据的改变就是永久性的。
41. SQL Server 的隔离级别有哪些?脏读、不可重复读、幻读分别是什么?
- 读未提交(Read Uncommitted):可以读取其他事务未提交的数据。会导致脏读。
- 读已提交(Read Committed):只能读取其他事务已提交的数据。这是 SQL Server 的默认级别。避免了脏读,但可能导致不可重复读。
- 可重复读(Repeatable Read):保证在同一个事务中,多次读取同一数据的结果是一致的。避免了脏读和不可重复读,但可能导致幻读。
- 快照(Snapshot):在事务开始时提供数据的一个一致性版本。读取的是事务开始时的数据快照,不会阻塞写操作。避免了脏读、不可重复读和幻读。
- 可序列化(Serializable):最高隔离级别,强制事务串行执行。避免了所有并发问题,但性能最差。
名词解释:
- 脏读:事务A读取了事务B未提交的修改数据,之后B回滚了,A读到的就是脏数据。
- 不可重复读:事务A多次读取同一数据,在此期间事务B修改并提交了该数据,导致A多次读取的结果不一致。
- 幻读:事务A多次读取一个范围的数据,在此期间事务B插入或删除了该范围内的数据并提交,导致A多次读取时发现“凭空”多出或少了一些行。
42. 什么是死锁?如何避免和解决?
- 死锁:两个或更多事务相互等待对方释放资源,导致它们都无法继续执行的状态。
- 避免:
- 以相同的顺序访问表。
- 保持事务简短,尽快提交。
- 使用较低的隔离级别(如 Read Committed)。
- 使用
LOCK_TIMEOUT设置。
- 解决:SQL Server 内置的死锁监视器会检测到死锁,并选择一个作为“牺牲品”将其回滚,从而让其他事务继续进行。牺牲品事务会收到 1205 错误。
43. 谈谈 CTE(公用表表达式)、临时表和表变量。
- CTE:
- 更像一个临时的视图,只在查询期间存在。
- 可读性好,特别适合递归查询。
- 不能创建索引。
- 临时表(#Temp):
- 存储在 TempDB 中,存在于会话或嵌套作用域中。
- 可以创建索引和统计信息。
- 适合存储较大的中间结果集。
- 表变量(@Table):
- 也存储在 TempDB 中,存在于批处理/函数/存储过程的作用域中。
- 通常认为它更快(对于小数据量),因为它没有统计信息,导致优化器总是假设它只有1行。
- 不能创建索引(除了主键和唯一约束)。
44. 行版本控制和乐观并发控制是什么?
这是基于快照隔离级别的机制。当数据被修改时,SQL Server 会在 TempDB 中保存被修改行的旧版本。其他正在读取的事务可以从 TempDB 中读取这个旧版本,从而不会与写事务发生阻塞。READ_COMMITTED_SNAPSHOT 和 ALLOW_SNAPSHOT_ISOLATION 数据库选项与此相关。
45. SQL Server 的高可用性方案有哪些?
- AlwaysOn 故障转移集群实例(FCI):基于 Windows 故障转移集群,共享存储。实例级别的高可用。
- AlwaysOn 可用性组(AG):SQL Server 的核心高可用和灾难恢复解决方案。数据库级别,不共享存储,可读副本,功能最强大。
- 数据库镜像(已弃用,被AG取代):主库和镜像库之间同步数据。
- 日志传送:通过定期备份主数据库的事务日志并还原到辅助服务器来实现。恢复时间较长。
46. 你知道哪些 SQL Server 的新特性?(根据面试公司使用的版本准备)
- JSON 支持:
FOR JSON PATH/AUTO,OPENJSON等。 - STRING_AGG 函数:将多行字符串值合并成一个字符串。
- 查询存储(Query Store):用于跟踪查询执行计划、性能历史,并强制特定计划。
- 时态表(Temporal Tables):自动跟踪和管理数据的历史变化。
- 内存优化表(In-Memory OLTP):将表和存储过程放入内存,极大提升性能。
47. 存储过程和函数的区别是什么?
| 特性 | 存储过程 | 函数 |
|---|---|---|
| 返回值 | 可以没有返回值,或通过 OUTPUT 参数返回多个值 | 必须有返回值(标量或表) |
| 使用场景 | 执行业务逻辑、数据处理 | 计算并返回一个值,或在查询中作为表使用 |
| 在 SELECT 中调用 | 不可以 | 可以 |
| DML 操作 | 可以对表进行所有 DML 操作 | 在函数内部不能执行 DML 操作(除了表变量) |
| 事务管理 | 可以在内部使用事务(BEGIN TRANSACTION) | 不能在函数内使用事务 |
| 执行方式 | EXEC/EXECUTE 过程名 | SELECT dbo.函数名() |
48. 什么时候应该使用存储过程?什么时候应该使用函数?**
-
使用存储过程:
- 执行复杂的业务逻辑
- 需要返回多个结果集
- 需要进行 DML 操作(INSERT/UPDATE/DELETE)
- 需要事务控制
- 性能要求高(预编译、执行计划重用)
-
使用函数:
- 封装可重用的计算逻辑
- 在查询中作为列使用
- 简化复杂的 JOIN 或 WHERE 条件
- 返回表值供 FROM 子句使用
49. 什么是触发器?INSTEAD OF 和 AFTER 触发器的区别?**
-
触发器:一种特殊的存储过程,在特定数据库事件(INSERT/UPDATE/DELETE)发生时自动执行。
-
AFTER 触发器(FOR 触发器):
- 在 DML 操作执行完成后 触发
- 可以访问
inserted和deleted魔术表 - 常用于审计、日志记录、数据一致性检查
-
INSTEAD OF 触发器:
- 取代 原始的 DML 操作执行
- 在约束检查之前触发
- 常用于实现复杂的视图更新逻辑,或对不可更新视图进行更新
50. inserted 和 deleted 魔术表是什么?**
这两个是触发器中的特殊内存表:
- inserted:包含 INSERT 或 UPDATE 操作的新数据
- deleted:包含 DELETE 或 UPDATE 操作的旧数据
-- 在 UPDATE 触发器中
CREATE TRIGGER trg_AuditUpdate
ON Employees
AFTER UPDATE
AS
BEGIN
INSERT INTO AuditTable (EmployeeID, OldSalary, NewSalary)
SELECT d.EmployeeID, d.Salary, i.Salary
FROM deleted d
INNER JOIN inserted i ON d.EmployeeID = i.EmployeeID
WHERE d.Salary <> i.Salary;
END;
51. ROW_NUMBER()、RANK()、DENSE_RANK() 的区别?**
这三个都是窗口函数,用于为结果集的行分配排名:
- ROW_NUMBER():为每一行分配一个唯一的连续序号(1, 2, 3, 4…)
- RANK():相同的值获得相同排名,但会跳过后续排名(1, 2, 2, 4…)
- DENSE_RANK():相同的值获得相同排名,但不跳过后续排名(1, 2, 2, 3…)
SELECT
Name, Score,
ROW_NUMBER() OVER (ORDER BY Score DESC) as RowNum,
RANK() OVER (ORDER BY Score DESC) as Rank,
DENSE_RANK() OVER (ORDER BY Score DESC) as DenseRank
FROM Students;
52. 什么是公用表表达式(CTE)的递归查询?
递归 CTE 用于处理层次结构数据(如组织结构、菜单树等):
-- 查询某个部门及其所有子部门
WITH DepartmentCTE AS (
-- 锚定成员:根节点
SELECT DepartmentID, DepartmentName, ParentDepartmentID
FROM Departments
WHERE DepartmentID = @RootDepartmentID
UNION ALL
-- 递归成员:子节点
SELECT d.DepartmentID, d.DepartmentName, d.ParentDepartmentID
FROM Departments d
INNER JOIN DepartmentCTE cte ON d.ParentDepartmentID = cte.DepartmentID
)
SELECT * FROM DepartmentCTE;
53. 什么是参数嗅探问题?如何解决?
-
参数嗅探:SQL Server 在编译存储过程时,使用第一次执行时的参数值来生成执行计划。如果后续执行的参数值数据分布差异很大,可能导致性能问题。
-
解决方案:
- 使用
OPTION (RECOMPILE):每次执行都重新编译 - 使用
OPTION (OPTIMIZE FOR UNKNOWN):使用平均数据分布 - 使用局部变量:将参数赋值给局部变量,在查询中使用局部变量
- 使用
WITH RECOMPILE选项创建存储过程
- 使用
54. 如何查找和优化慢查询?
-
查找慢查询:
- 使用 SQL Server Profiler
- 使用扩展事件(Extended Events)
- 查询动态管理视图(DMV):
-- 查找最耗时的查询 SELECT TOP 10 total_elapsed_time/execution_count AS avg_elapsed_time, execution_count, SUBSTRING(st.text, (qs.statement_start_offset/2)+1, ((CASE qs.statement_end_offset WHEN -1 THEN DATALENGTH(st.text) ELSE qs.statement_end_offset END - qs.statement_start_offset)/2) + 1) AS statement_text FROM sys.dm_exec_query_stats qs CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) st ORDER BY avg_elapsed_time DESC; -
优化方法:
- 添加合适的索引
- 重写查询逻辑
- 避免在 WHERE 子句中对字段进行函数操作
- 减少不必要的列查询
55. 数据库的三大范式是什么?
- 第一范式(1NF):每个列都是原子的,不可再分
- 第二范式(2NF):满足 1NF,且非主属性完全依赖于主键(消除部分依赖)
- 第三范式(3NF):满足 2NF,且非主属性之间没有传递依赖
56. 什么时候应该反范式化?
虽然范式化减少了数据冗余,但在以下情况可以考虑反范式化:
- 频繁的 JOIN 操作影响性能时
- 需要提高查询性能的读密集型场景
- 数据仓库或报表数据库
- 历史数据表,数据不再变更
57. 完整备份、差异备份和事务日志备份的区别?
- 完整备份:备份整个数据库,是其他备份的基础
- 差异备份:只备份自上次完整备份以来发生变化的数据页
- 事务日志备份:备份事务日志,允许时间点恢复
恢复场景示例:
完整备份 (周日) → 差异备份 (周一) → 日志备份 (周二 10:00) → 日志备份 (周二 11:00)
如果周二 11:30 发生故障,可以恢复到:周日完整备份 + 周一差异备份 + 周二 10:00 日志 + 周二 11:00 日志
58. 简单恢复模式 vs 完整恢复模式
-
简单恢复模式:
- 不备份事务日志
- 不能进行时间点恢复
- 日志空间自动回收
- 适合测试环境或可接受数据丢失的场景
-
完整恢复模式:
- 需要定期备份事务日志
- 支持时间点恢复
- 可以防止数据丢失
- 生产环境推荐使用
59. 如何设计一个支持软删除的系统?
-- 在表中添加删除标记字段
ALTER TABLE Products ADD IsDeleted BIT NOT NULL DEFAULT 0;
ALTER TABLE Products ADD DeletedDate DATETIME NULL;
-- 使用视图过滤已删除的记录
CREATE VIEW vw_ActiveProducts AS
SELECT * FROM Products WHERE IsDeleted = 0;
-- 使用 INSTEAD OF DELETE 触发器实现软删除
CREATE TRIGGER trg_SoftDeleteProduct
ON Products
INSTEAD OF DELETE
AS
BEGIN
UPDATE Products
SET IsDeleted = 1, DeletedDate = GETDATE()
WHERE ProductID IN (SELECT ProductID FROM deleted);
END;
60. 如何处理数据库中的循环引用?
-
方案1:使用延迟约束检查
ALTER TABLE TableA ADD CONSTRAINT FK_TableA_TableB FOREIGN KEY (BID) REFERENCES TableB(BID) -- 在某些版本中可以使用 DEFERRABLE -
方案2:允许 NULL 值,先插入部分数据再更新
-
方案3:使用触发器代替外键约束
-
方案4:重新设计表结构,消除循环引用
61. 如何实现数据库的审计功能?
- 方法1:使用触发器记录数据变更
- 方法2:使用 SQL Server 的变更数据捕获(CDC)功能
- 方法3:使用 SQL Server Audit 功能(企业版)
- 方法4:在应用层实现审计逻辑
62. SQL Server 2019 的新特性有哪些?
- 智能查询处理:自适应连接、行模式内存授予反馈等
- 数据虚拟化:通过 PolyBase 查询外部数据源
- Java 语言扩展:在 SQL Server 中执行 Java 代码
- 加速数据库恢复:大幅减少数据库恢复时间
- 列存储索引增强:可更新的非聚集列存储索引
63. 什么是内存优化表?适用场景?
内存优化表将数据完全存储在内存中,提供极高的吞吐量:
-
适用场景:
- 高频读写的高并发场景
- 会话状态管理
- 实时数据处理
- 需要亚毫秒级响应的应用
-
创建示例:
CREATE TABLE dbo.SessionState
(
SessionID nvarchar(64) NOT NULL PRIMARY KEY NONCLUSTERED,
UserData varbinary(MAX) NOT NULL,
CreatedDate datetime2 NOT NULL
) WITH (MEMORY_OPTIMIZED = ON, DURABILITY = SCHEMA_AND_DATA);
数据库表题
1. 电商产品有颜色(红、蓝、黑)、尺寸(S、M、L)等变体信息,买家购买一个红色中码的商品对应的变体名为:红_M。那么给定一组变体信息,用程序生成所有变体组合数据。
变体示例:Color: Red,Green Size: S,M Style: A
变体组合结果:Red_S_A; Red_M_A; Green_S_A; Green_M_A
//测试用例
var list = new List<string[]>{
new string[]{"Red","Green"},
new string[]{"S","M"},
new string[]{"A"}
};
var result = Combine(list);
//期望result为:Red_S_A; Red_M_A; Green_S_A; Green_M_A
public List<string> Combine(List<string[]> list){ … }
- 答案:
public static List<string> Combine(List<string[]> list)
{
List<string> result = new List<string>();
int[] indices = new int[list.Count]; // 用于跟踪每个字符串数组中当前选取的元素的索引
while (true)
{
string combined = "";
for (int i = 0; i < list.Count; i++)
{
combined += "_" + list[i][indices[i]]; // 将当前索引对应的元素添加到组合中
}
result.Add(combined); // 将组合添加到结果列表中
// 更新索引
int j = list.Count - 1;
while (j >= 0 && indices[j] == list[j].Length - 1)
{
indices[j] = 0;
j--;
}
// 检查是否所有索引都已经达到最大值
if (j < 0)
{
break;
}
indices[j]++; // 增加索引
}
return result;
}
2. 内部系统的产品库中有一个产品拥有三个维度的变体,分别是:Color、Size 和 Style。现在要将其上传到平台 A,但平台 A 仅支持两个维度的变体,因此需要对变体进行降维。那么给定一组变体信息,用程序实现变体降维操作。
变体示例: Color: Red,Green Size: S,M Style: A,B
降维后:Color: Red,Green Size: S_A,S_B,M_A,M_B
var pair = new Dictionary<string, List<string>> {
{"Color",new List<string>{ "Red","Green" }},
{"Size",new List<string>{ "S","M" }},
{"Style",new List<string>{ "A","B" }},
};
var result = Reduce(pair);
public Dictionary<string, List<string>> Reduce(Dictionary<string, List<string>> pair){...}
- 答案:
class Program { static void Main() { // 定义变体维度 string[] colors = { "Red", "Green" }; string[] sizes = { "S", "M" }; string[] styles = { "A", "B" };
// 降维操作
Dictionary<string, string[]> reducedDimensions = ReduceDimensions(colors, sizes, styles);
// 打印降维后的变体
Console.WriteLine("Color: " + string.Join(",", reducedDimensions["Color"]));
Console.WriteLine("Size: " + string.Join(",", reducedDimensions["Size"]));
}
static Dictionary<string, string[]> ReduceDimensions(string[] colors, string[] sizes, string[] styles)
{
var reduced = new Dictionary<string, string[]>
{
{ "Color", colors },
{ "Size", sizes.SelectMany(size => styles.Select(style => size + "_" + style)).ToArray() }
};
return reduced;
}
}
3. 试用SQL查询语句表达下列对教学数据库中三个基本表 S、SC 、C 的查询:
S(sno, sname, sage, ssex):学号、姓名、年龄、性别SC(sno, cno, grade):学号、课程号、成绩C(cno, cname, teacher):课程号、课程名、教师名
3.1. 求年龄大于所有女同学年龄的男学生姓名和年龄
SELECT sname, sage FROM S AS X
WHERE x.ssex = '男' AND x.sage > ALL (
SELECT sage FROM S AS Y WHERE y.ssex = '女'
);
3.2. 求年龄大于女同学平均年龄的男学生姓名和年龄
SELECT sname, sage FROM S
WHERE ssex = '男' AND sage > (
SELECT AVG(sage) FROM S WHERE ssex = '女'
);
3.3. 在SC中检索成绩为空值的学生学号和课程号
SELECT sno, cno FROM SC WHERE grade IS NULL;
3.4. 检索姓名以WANG打头的所有学生的姓名和年龄
SELECT sname, sage FROM S WHERE sname LIKE 'WANG%';
3.5. 检索学号比WANG同学大,而年龄比他小的学生姓名
SELECT sname FROM s
WHERE sno > (SELECT sno FROM s WHERE sname = 'WANG')
AND sage < (SELECT sage FROM s WHERE sname = 'WANG');
3.6. 统计每门课程的学生选修人数(超过2人的课程才统计)
SELECT cno, COUNT(sno) AS 人数 FROM SC
GROUP BY cno HAVING COUNT(sno) > 2
ORDER BY 人数 DESC, cno ASC;
3.7. 求LIU老师所授课程的每门课程的学生平均成绩
SELECT cname, AVG(grade) FROM SC, C
WHERE SC.cno = C.cno AND teacher = 'liu'
GROUP BY c.cno, cname;
3.8. 求选修C4课程的学生的平均年龄
SELECT AVG(sage) FROM S, SC
WHERE S.sno = SC.sno AND cno = '4';
3.9. 统计有学生选修的课程门数
SELECT COUNT(DISTINCT cno) FROM SC;
3.10. 在基本表SC中修改4号课程的成绩
UPDATE SC SET grade = grade * 1.05 WHERE cno = '4' AND grade <= 75;
UPDATE SC SET grade = grade * 1.04 WHERE cno = '4' AND grade > 75;
3.11. 把低于总平均成绩的女同学成绩提高5%
UPDATE SC SET grade = grade * 1.05
WHERE grade < (SELECT AVG(grade) FROM SC)
AND sno IN (SELECT sno FROM S WHERE ssex = '女');
3.12. 把选修数据库原理课不及格的成绩改为空值
UPDATE SC SET grade = NULL
WHERE grade < 60 AND cno IN (
SELECT cno FROM C WHERE cname = '数据库原理'
);
3.13. 把WANG同学的学习选课和成绩全部删去
DELETE FROM SC WHERE sno IN (
SELECT sno FROM S WHERE sname = 'WANG'
);
3.14. 在基本表SC中删除尚无成绩的选课元组
DELETE FROM SC WHERE grade IS NULL;
3.15. 在基本表S中插入一个学生元组
INSERT INTO S(sno, sname, sage) VALUES('S9', 'WU', 18);