简介:这份 SQL Server 学习笔记面向数据库初学者、数据库管理从业者以及准备系统梳理 SQL Server 基础知识的人群,围绕关系数据库管理系统的主要特点与核心知识点展开,可用于课堂复习、岗位入门和日常查阅。压缩包内含 1 个 doc 文档,大小约 499KB,体积轻便,适合直接下载后按章节阅读与标注。目前已有 440 人学习或下载,具备一定的参考与交流基础。内容覆盖创建、删除、修改数据库对象,C/S 体系与编程接口,表、列、索引、触发器、存储过程的限制参数,以及关系模型中的实体、属性、码、域和元组等概念;同时整理数据库授权、系统数据库作用、数据文件与日志文件存储、建库建表语句、主键/外键/默认值/check/unique 约束和视图创建等要点,便于读者建立从概念到语句实践的完整知识框架,并快速查阅常见对象与约束的用法。
1. 知识地图:从一条语句到整个库的四层骨架
复习 SQL Server 学习笔记时,最容易忽略的往往不是语句本身——写起来顺手——也不是那些数字,比如一张表最多能建几个索引。真正容易模糊的,是模型层与完整性层:候选码和主码差在哪、第二范式为什么会被一个联合主键搞崩、视图为什么有的能更新有的不能、簇索引为什么一个表只能有一个。这份笔记按四条线整理:体系与对象容量(架构、接口、上限参数、系统库与文件)、模型与范式(实体、码、域、1NF 到 BCNF)、实现与约束(建库建表、五类约束、视图索引存储过程游标、授权链)、易错点与自查。前两条线决定概念题能不能答对,后两条线决定动手时会不会返工重来。适合已经写过一些语句、但知识还散着、一查就乱的人拿来对照。
2. 体系与对象:容量上限、系统库与文件组成的参数表
2.1 C/S 体系与编程接口:先搞清楚谁在连谁
SQL Server 的结构第一层是 C/S 体系:客户端发起请求,服务器端解析、执行、把结果集送回去。这决定了后面很多参数为什么会存在——用户连接有上限、权限按登录来控制、锁与打开对象被单独统计、可同时打开的数据库数量也有上限。它不是「一个软件装完就完事」,而是一套两端的协作关系。
编程接口这一层,ODBC、VB、VC、Access 都能接入,说明它是开放的:前端换语言、换工具,不影响后端数据怎么组织。再往上是能力层,Web 支持与基于 Web 的分析、数据仓库功能、XML 扩展标记语言支持、分布式分区视图。分布式分区视图是把多台服务器上的表拼成一张逻辑表给前端看,做水平拆分时用得上,但代价是查询要走多台机器,跨机事务要额外考虑。
这一段建议按「接入方式 → 分析能力 → 分布式能力」三层来记,不要零散背名词。复习时问自己三个问题就够了:客户端以什么身份连进来、连进来能做什么分析、数据分散在多台机器上时靠什么拼成整体。
2.2 容量上限:这些数字需要一次查清,不用反复背
笔记里最容易抄错、也最容易考到的就是这张表。先把原样整理出来:
| 对象 | 上限 | 容易记错的地方 |
|---|---|---|
| 数据库大小 | 1MB — 1TB | 上限不是无限,规划容量时按这个量级估 |
| 每个数据库的表 | 20 亿 | 与「锁定及打开对象 20 亿」是同一个数量级,别串 |
| 每张表的列 | 1024 列 | 与存储过程的 1024 个参数撞数,靠「列 vs 参数」区分 |
| 每行的字符 | 8060 字符 | 是行宽限制,不是列数,也不是单个列的长度 |
| 索引 | 每表 1 个簇索引、249 个非簇索引(16 列) | 249 只数非簇索引,簇索引永远只有 1 个 |
| 触发器 | 每表 3 个(INSERT、UPDATE、DELETE) | 是按三种操作各一个,不是任意三个 |
| 存储过程 | 最多 1024 个参数、32 级嵌套 | 嵌套级数是 32,不是 1024 |
| 用户连接 | 32767 | 这是连接数,不是表数 |
| 可同时打开的数据库 | 32767(笔记里也出现过 327687 的写法,属于抄录串位,统一按表格) | 与用户连接同数,容易互相污染 |
| 锁定及打开对象 | 20 亿 | 与表数量同量级 |
这张表不建议死背,要记住的是「抓手」:1024 出现两次(列、参数),32767 出现两次(连接、同时打开的库),20 亿出现两次(表、锁定对象),249 只属于非簇索引,8060 只属于行宽。这样即使考场上记不清具体数字,也能靠配对关系推回来。
提示:笔记里同一个参数出现两个数字时,不要两个都记,选一个写进自己的参数表,另一个标注「已废弃写法」,否则复习时会一直自我冲突。
2.3 系统数据库与文件组成:库不是一坨,是几个文件加几个角色
六个系统库各有分工,混在一起背会乱:
| 系统库 | 作用 | 记忆点 |
|---|---|---|
| master | 保存主数据:登录账号、其他数据库信息、服务器全部配置信息 | 坏了整套都起不来,最不能乱动 |
| model | 模板库,新建数据库时按它的样子复制 | 改它等于改以后所有新库的默认长相 |
| tempdb | 存放临时表 | 连接一断,里面的东西就没了 |
| msdb | 默认 12MB,保存调度作业、警报、接收警报的操作员信息 | 与「自动化任务」相关 |
| pubs | 样例数据库 | 练手用 |
| northwind | 样例数据库 | 练手用,业务模型比 pubs 完整 |
数据在磁盘上被映射成几类操作系统文件:主要数据文件.mdf,包含数据库的启动信息并存储数据;次要数据文件.ndf,用来承接主文件放不下的数据,需要把数据摊到多个磁盘时用它延伸;日志文件.ldf,是事务机制和可恢复性的实现基础,记录用于恢复数据库的日志信息,一个事务的日志记录不可分割。
文件组是为了方便管理文件,把若干文件构成一个分配单元。做规划时的顺序是:先定数据文件和日志文件能不能放在不同的物理盘上(日志顺序写、数据随机读,混在一起性能会互相拖),再考虑要不要多个次要文件分摊 IO,最后才谈文件组。这个顺序反过来做,往往建完就得重建。
3. 关系模型与范式:实体、码、域到三大范式
3.1 概念模型里的六个基本词
这部分看着简单,但它是后面所有约束、主键、外键讨论的地基,词一旦混用,后面的题就全歪。
实体是客观存在、可以相互区分的事物。属性是实体具有的某一特性,比如性别、年龄。码是能唯一标识实体的属性集,学号就是学生实体的码——给一个学号20111221004,只能对应一个学生,这才叫唯一标识。域是属性的取值范围,性别取「男、女」,学号取 11 位整数,这就是域在起作用。
再往上一层是抽象:实体型是用实体名加属性集合来描述同类实体,写成「顾客(编号、姓名、性别、年龄)」这种形式;实体集是同型实体的集合,比如全体顾客、全体学生。联系有两层含义,一层是实体内部的联系(属性之间的关系),一层是实体之间的联系(顾客和订单之间)。这两个层次别混在一起记,前者在建模阶段消化掉,后者才会变成外键。
四种常见数据模型:层次模型、网状模型、关系模型、面向对象模型。关系模型是现在的主流,原因很实际——它把数据组织成表,用户不用关心物理存储怎么排,查询只描述「要什么」不描述「怎么找」。层次和网状模型要求你按指针路径访问,改一个结构牵动一大片,这是它们退场的主要原因。
3.2 关系模型里的一词两用:学术叫法和工程叫法对照
同一个东西在教材里叫一个名字,在工具里叫另一个名字,这是概念题丢分的高发区:
| 学术叫法 | 工程叫法 | 说明 |
|---|---|---|
| 关系 | 表 | 一张表就是一个关系 |
| 元组 | 行 | 表里的一行记录 |
| 属性 | 列 / 字段 | 表里的一列 |
| 分量 | 单元格的值 | 元组中的一个属性值 |
| 主码 | 主键 | 能唯一确定一个元组的属性组 |
| 域 | 取值范围 | 属性的取值集合 |
| 关系模式 | 表结构 | 关系名、列名、属性所属域名、属性向域的映射、数据间的依赖关系 |
「关系模式」这一行值得单独说:它描述的不是某一时刻表里有什么数据,而是这张表长什么样、列之间有什么依赖。所以你写CREATE TABLE的时候,写的其实是关系模式;你INSERT进去的每一条,才是关系在某个时刻的实例。这个区分在讨论范式的时候必须清楚,因为范式约束的是关系模式,不是某一批数据。
3.3 一张表凭什么算「关系」:五个条件
不是随便一张二维表都能叫关系,要同时满足:
- 每一列的数据类型都相同;
- 没有完全相同的两行;
- 列与列之间字段名不能重复;
- 没有组合列,即一列里不能再有子列,也不能把一列拆成两列以上;
- 行列的顺序不影响语义(教材里常说的「行无序、列无序」)。
第 4 条是最容易被忽略的,因为它和实际建表习惯冲突。业务上经常有人想在一列里塞「生活用品,电子产品」这种逗号分隔的多值,看着方便,实际已经把表踢出了关系模型——查询时得靠字符串切割,索引也用不上,排序更无从谈起。这就是下一节第一范式要解决的问题。
3.4 从 1NF 到 BCNF:每一层都在解决上一层留下的一个具体毛病
第一范式解决组合列。笔记里的例子很典型:一张商品表,列写成goods_type_1, goods_type_2, goods_type_3,想查「所属类别为生活用品的商品编号」,结果为空——因为生活用品可能落在第一列,也可能落在第三列,条件写哪一列都不完整。这就是组合列的直接代价。
改法有两种:把非原子属性拆成多列(适合列数固定且很少),或者拆成多行(适合列数不定)。更彻底的是分解表,把类别单独抽成一张表,商品表只留类别编号。判断要不要继续拆的标准是具体业务:如果业务上永远最多三个类别,拆成三列也能用;一旦会出现第四个,就必须拆表。
第二范式解决部分依赖。笔记里那张buy_info是关键案例:
CREATE TABLE buy_info ( 购买了商品的用户编号 VARCHAR(10), 用户购买商品编号 VARCHAR(10), 用户购买商品名称 VARCHAR(10), PRIMARY KEY (购买了商品的用户编号, 用户购买商品编号) );这段代码的毛病不在语法,在语义:用户购买商品名称只依赖「用户购买商品编号」这一个属性,却被绑在由两列组成的联合主键上,形成部分依赖。后果有两个,都很具体:一是联合主键的每一列都不允许为空,可现实中某个字段没填就可能插不进去,这叫插入异常;二是同一个商品名会在多条记录里重复出现,改名的时候要更新多处,漏一处就数据不一致,这叫更新异常。第二范式的定义就是:在满足 1NF 的前提下,每个非主属性都完全依赖于码,不存在只依赖码的一部分的非主属性。
第三范式解决传递依赖。如果 a 决定 b,b 决定 c,而 c 不属于 b、b 也推不出 a,那 c 就是通过 b 传递依赖于 a,这张表就不满足 3NF。典型场景是学生表里塞了「宿舍楼 → 楼长」这种链:学号定宿舍楼,宿舍楼定楼长,楼长跟学号没有直接关系,却被迫跟着每一行学生记录重复存储。
BCNF是修正的第三范式。3NF 只约束非主属性对码的依赖,BCNF 把这个要求收紧到所有属性,包括主属性——即每一个决定因素本身都必须是候选码。考试里区分 3NF 和 BCNF 的经典套路就是:表里有两个候选码,它们之间有重叠属性,某个主属性被另一个主属性决定,这时它满足 3NF 但不满足 BCNF。
这一节的复习方法不是背定义,而是拿一张具体的表,逐条问三个问题:有没有一列里塞了多个值(1NF)、有没有哪个非主属性只依赖联合主键的一部分(2NF)、有没有哪个非主属性被另一个非主属性决定(3NF)。三个问题都答「没有」,范式这一块基本就过关了。
4. 建库建表与可编程对象:DDL 主干、五类约束与授权链
4.1 创建数据库的三段式:ON 和 LOG ON 各管一段
建库语句的结构很固定,难点不在语法,在每个参数怎么填:
CREATE DATABASE shop -- 数据库逻辑名,允许用中文 ON PRIMARY -- 主文件组,存放数据文件 ( NAME = 'shop_data', -- 逻辑文件名,后续备份恢复靠它识别 FILENAME = 'D:\data\shop_data.mdf', -- 物理路径,必须提前建好目录 SIZE = 10MB, -- 初始大小 MAXSIZE = 500MB, -- 增长上限,不写就可能把磁盘吃满 FILEGROWTH = 5MB -- 每次增长量,按 MB 或百分比 ) LOG ON -- 事务日志文件,与数据文件分开 ( NAME = 'shop_log', FILENAME = 'D:\data\shop_log.ldf', SIZE = 5MB, MAXSIZE = 200MB, FILEGROWTH = 1MB );逐段看:ON后面定义数据文件(文件名、存储路径、初始大小、可增长到的最大值、增长增量),LOG ON后面定义事务日志文件,参数一一对应。SIZE给太小会频繁自动增长,每次增长都伴随文件初始化,表现为偶发卡顿;MAXSIZE不写,日志文件会一路涨到磁盘满;FILEGROWTH给百分比在小库上没问题,库大了每次增长就是几百 MB,容易抖。日常建库时,可选项(方括号里的内容)一般不指定,用默认值;关键字大小写不敏感,create和CREATE等价;数据库逻辑名可以用中文,但物理文件名建议还是英文,避免跨平台拷文件时编码出问题。
4.2 建表与数据类型:char 的截断是静默发生的
常用数据类型整理成表更清楚:
| 类型 | 写法 | 关键点 |
|---|---|---|
| 定长字符 | char(n),n 取 1—8000 | 实际存储长度固定为 n,超出 n 的部分被截掉,且不报错 |
| 变长字符 | varchar(n) | 存储大小为实际输入的字节数,不是 n |
| 整型 | int、bigint、smallint、tinyint | 按取值范围从大到小选,别一律用 int |
| 精确数值 | numeric(p, s) | p 是总位数,s 是小数位 |
| 货币 | money | 不需要单引号,但要带合适的货币符号 |
| 日期时间 | datetime | 表示一天内的时间点 |
| 表类型 | table | 存一组数据并作为函数结果集返回,只支持主键、唯一性、空值三种约束 |
char那条要单独画重点:超长部分是静默截断的,不抛异常。也就是说你插一个 12 位的字符串进char(10),存进去只有 10 位,而且当时的语句是成功的。这个坑在导入历史数据时特别常见,往往是事后对账才发现少了两个字符。变长场景一律用varchar,只有在长度绝对固定(如统一 6 位编码)时才用char,因为定长在频繁更新时不会产生行迁移。
插入语句的约束是硬性的:
INSERT INTO goods (goods_id, goods_name, goods_type) VALUES ('G00001', '课桌', '生活用品');括号里的列必须和值一一对应,个数和顺序都要匹配,否则整条插入失败。建表时如果某列允许为空,插入时这一项可以直接写NULL占位;如果建表时设了默认值约束,省略该列也能成功,系统会自动填默认值。这两种情况的区别要分清:写NULL是明确告诉系统「这里就是空」,省略列是「让它自己按默认值来」,语义不一样。
4.3 五类约束与完整性四条线:约束是手段,完整性是目的
先看约束,五类各有各管的范围:
| 约束 | 写法 | 管什么 | 违反时的表现 |
|---|---|---|---|
| 主键约束 | PRIMARY KEY | 唯一标识每行,值不能为空 | 插入重复或空值直接拒绝 |
| 外键约束 | FOREIGN KEY ... REFERENCES | 两张表之间的对应关系 | 被引用表里没有对应值时拒绝 |
| 默认值约束 | DEFAULT | 用户没指定时自动填的值 | 不违反,自动补齐 |
| 用户自定义约束 | CHECK | 自定义的取值规范 | 不满足规范时拒绝执行 |
| 唯一性约束 | UNIQUE | 该列的值不可重复 | 插入已存在的值被拒绝 |
再看完整性,四条线是「目的」,约束只是实现目的的手段,答题时不要混为一谈:
- 域完整性:输入到某一列的数据是否符合这一列的要求,即列的输入有效性,是对列做的规定,靠
DEFAULT和CHECK实现; - 实体完整性:要求每行有一个唯一标识符,能和其他行区分,表里没有重复行,是对行做的规定,靠主键实现;
- 用户自定义完整性:业务规则层面的约束,靠
CHECK、规则、触发器实现; - 参考完整性:维护表与表之间主键和外键的对应关系,在插入或删除记录时保证已定义的关系不被破坏,靠外键实现。
强制完整性的方式有三种,笔记里点得很清楚:约束、默认、规则。默认的用法是两步,先建默认对象再绑定到列上:
CREATE DEFAULT goods_type_default AS '生活用品'; -- 先定义默认值对象 EXEC sp_bindefault 'goods_type_default', 'goods.goods_type_name'; -- 再绑定到具体列注意sp_bindefault的第二个参数是「表名.列名」的字符串形式,写错表名不会报错,只会在插入时才发现没生效——这类静默失败在排查时最费时间,绑定完最好立刻插一条测试数据验证。
修改表的操作归纳成五类,这是实操题的高频点:
-- 1. 加列 ALTER TABLE goods ADD goods_remark VARCHAR(100) NULL; -- 2. 加约束(约束名建议自己起,别用系统随机名) ALTER TABLE goods ADD CONSTRAINT ck_goods_number CHECK (goods_number >= 0); -- 3. 删约束,再删列——顺序不能反 ALTER TABLE goods DROP CONSTRAINT ck_goods_number; ALTER TABLE goods DROP COLUMN goods_remark; -- 4. 开关约束 ALTER TABLE goods NOCHECK CONSTRAINT ALL; -- 关闭全部 ALTER TABLE goods CHECK CONSTRAINT ALL; -- 打开全部 -- 5. 改列类型或可空性 ALTER TABLE goods ALTER COLUMN goods_number INT NULL;第 3 类操作有个必须先做的前置动作:要删的列如果身上挂着索引、CHECK、外键、唯一性、主键、默认值或规则绑定,必须先删掉这些依赖,否则DROP COLUMN会直接失败。笔记里那句「要删除商品数量,需要先删除商品的约束条件」,说的就是这个顺序。
4.4 视图:定义限制比语法更值得记
建视图的基本形式:
CREATE VIEW goods_1_view_5 AS SELECT goods_name, goods_type_name FROM goods_1 WHERE goods_type_name = '电子产品' WITH CHECK OPTION; -- 通过本视图做的修改必须满足 WHERE 条件可选项一共四个,含义差别很大:WITH ENCRYPTION要求在存储视图语句文本时加密;WITH SCHEMABINDING把视图绑定到架构上,底层表的结构就不能随便改了;WITH VIEW_METADATA让浏览模式的元数据返回相对基表的视图名,而不是基表名;WITH CHECK OPTION规定对该视图做插入、修改时必须满足视图定义里的查询条件。
定义视图的查询表达式有三条硬限制:定义者必须对派生此视图的基本表有SELECT权限;查询语句里不能出现INTO、ORDER BY、COMPUTE、COMPUTE BY;不能引用临时表或表变量(临时表存在临时库里,连接一断就消失,视图没法依赖它)。
更新视图数据要同时满足几个条件:FROM子句至少引用一个表;选择列表里没有派生列;没有聚合函数;不含TOP、GROUP BY、UNION、DISTINCT。这四条记不住也没关系,反着想更快:只要是「算出来的」或「去重后的」结果,就没法反推回唯一一行,自然不能更新。多表连接视图的更新更严格,因为一行视图数据可能对应多张表的多行,系统无法判断该改哪张。
删除同理,DELETE里的视图必须是可更新且正确引用一个基表的视图。删一张表的数据和删多表关联视图的数据,成功率完全不在一个量级。
4.5 索引:簇索引只有一个,这句话值得抄三遍
索引是加速检索的一种由关键字组成的树状结构,建立目的是提高访问速度,可以用能唯一标识数据子集的列来建,也可以建成与原表分开的独立数据库对象。
两类索引的区别在于物理顺序:簇索引的顺序与表中数据的物理存储顺序一致,所以一个表只能有一个;非簇索引按完全不同的结构对数据排序,表里的数据可以按另一种顺序存储,它的运行原理是先通过索引找到数据存放的位置,再去那个位置取数据——也就是多一次定位。簇索引是「数据就是索引」,非簇索引是「索引指向数据」。
建索引的四个典型场景,几乎是原题:经常被搜索的列、需要排序的列、主键或外键所在列、经常出现在WHERE条件里的列。
-- 唯一簇索引:值不允许重复,且决定物理顺序 CREATE UNIQUE CLUSTERED INDEX goods_index ON goods_1(goods_id ASC); -- 复合索引:多列组合,列的先后顺序有讲究 CREATE INDEX goods_index2 ON goods_1(goods_id, goods_type_id);参数含义:UNIQUE表示唯一性索引,不允许存在索引值相同的两行;CLUSTERED表示簇索引;ASC/DESC指定排序方向。复合索引的列序不是随便排的,把选择性高、查询里最常用的列放前面,才能让后续列的排序也顺带用上索引;反过来,如果查询条件只用到了第二列,这个复合索引基本白建。
索引不是越多越好,每建一个索引,插入、更新、删除都要多维护一棵树。真实项目里,一张高频写入的表挂七八个索引,写性能掉一半是常事。所以「该建索引的地方」那四条是筛选标准,不是「全都建一遍」的清单。
4.6 存储过程与游标:把多条语句打包,把多行数据逐条处理
存储过程把查询、插入、删除、更新等一系列语句封在一起,调用时一起执行。能被触发器触发的操作是UPDATE、DELETE、INSERT三种。
CREATE PROCEDURE modify_process @bid_user VARCHAR(10), -- 出价用户 @bid_date DATETIME, -- 出价时间 @bid_number INT, -- 本次购买数量 @bid_goods_id VARCHAR(10), -- 商品编号 @goods_price MONEY -- 成交单价 AS UPDATE goods SET goods_number = goods_number - @bid_number WHERE goods_id = @bid_goods_id; INSERT INTO user_history VALUES (@bid_user, @goods_price * @bid_number, @bid_date);这段的价值在于「两条语句必须一起成功」:扣库存和记流水如果分开执行,中间挂掉就会出现库存扣了但流水没记的脏数据。封装进存储过程后,失败可以整体回滚。参数前缀@后面跟类型,调用时按位置或按名传值,参数个数最多 1024 个,嵌套层级最多 32 级。
有个细节值得留意:存储过程里的 SQL 语句在建的时候是封装好的,所以系统不会对它引用的表做存在性验证——表名写错了照样能建成功,要到执行时才报错。写完立刻EXEC一遍,是省事也实在的习惯。
游标用来处理「一行一行来」的场景,标准五步:
DECLARE @goods_name VARCHAR(50), @goods_number VARCHAR(20); DECLARE shop_info CURSOR FOR SELECT goods_name, goods_number FROM goods WHERE goods_name LIKE 'S%' ORDER BY goods_name, goods_number; OPEN shop_info; -- 1. 打开 FETCH NEXT FROM shop_info INTO @goods_name, @goods_number; -- 2. 取第一行 WHILE @@FETCH_STATUS = 0 -- 3. 循环取到取不出为止 BEGIN PRINT @goods_name + ' ' + @goods_number; FETCH NEXT FROM shop_info INTO @goods_name, @goods_number; END CLOSE shop_info; -- 4. 关闭 DEALLOCATE shop_info; -- 5. 释放@@FETCH_STATUS是全局变量,0 表示取到了,非 0 表示已到末尾或出错。这个循环的写法必须先FETCH一次再进WHILE,否则第一行会被跳过——这是游标最经典的一处坑。另外游标本质是逐行处理,数据量上万就会明显变慢,能用集合操作(UPDATE ... FROM、INSERT ... SELECT)就别用游标。
补充一个集合思路处理模糊查询的例子,比LIKE '%关键字%'更可控:
SELECT title FROM mydb WHERE title LIKE '关键字%' UNION SELECT title FROM mydb WHERE title NOT LIKE '关键字%' AND CHARINDEX('关键字', title) >= 1;前半段把前缀匹配的排前面,后半段用CHARINDEX补上出现在中间的情况,UNION去重。这样排序优先级可控,比单纯的%...%更容易判断索引有没有被用上。
4.7 登录、用户、角色:授权是一条链,不是一步
数据库授权是通过新建用户来实现的,目的是对这个用户指定其基于本登录所拥有的数据库操作权限。注意这句话里的层次:登录是服务器级的,用户是数据库级的,角色是权限的集合。三者是一条链,跳过任何一环都会出问题。
EXEC sp_addlogin 'app_user', 'pwd_here', 'shop'; -- 1. 建登录:名称、密码、默认库 EXEC sp_adduser 'app_user', 'app_user', 'db_owner'; -- 2. 建用户:登录名、用户名、所属角色 EXEC sp_addrole 'report_role', 'dbo'; -- 3. 建角色:角色名、拥有者 GRANT SELECT, INSERT ON goods TO app_user; -- 4. 授权 REVOKE INSERT ON goods FROM app_user; -- 5. 回收sp_addlogin的第三个参数是登录后默认连接的数据库,第四个是默认语言,都能省略。sp_adduser的用户自动成为指定角色的成员,角色填db_owner就是库内全权,生产环境一般不给。GRANT给权限,REVOKE回收权限,这两个是配套的,只授不回收,权限会越滚越大,最后没人说得清谁有什么。
5. 易错点:五个最容易被跳过的地方
5.1 概念层的三个漏点
易漏点一:候选码、主码、外码当成同义词用。为什么漏:教材里三个词挨着出现,定义又都是「唯一标识」,读快了就糊成一团。 怎么补:记住一条链——候选码是「所有能唯一标识一行的属性组」,可能有好几个;主码是从候选码里选定一个作为主码,所以主码一定是候选码,候选码不一定是主码;包含在任何一个候选码里的属性叫主属性,一个都没沾上的叫非主属性;外码是联系两张表的那个属性,它的值必须能在另一张表的主码里找到。做范式题第一步永远是先把候选码全列出来,漏一个候选码,后面 2NF、3NF 的判断全错。
易漏点二:联合主键里放了可为空的列。为什么漏:建表时只盯着「唯一性」,忘了主键隐含「非空」。 怎么补:主键的每一列都不允许为空,联合主键也一样。只要其中一列可能为空,这张表在设计上就已经埋了插入异常——业务上这条记录缺失该字段时根本插不进去。判断方法很直接:把主键的每一列单独问一句「这列业务上会不会没值」,答案是「会」,就该拆表或者换主键。
易漏点三:视图定义里顺手写了 ORDER BY。为什么漏:写查询写习惯了,SELECT后面跟ORDER BY是肌肉记忆。 怎么补:视图定义里不能出现INTO、ORDER BY、COMPUTE、COMPUTE BY,也不能引用临时表或表变量。排序需求放到查询视图的那条语句上去写,不要塞进视图定义。同理,TOP、GROUP BY、UNION、DISTINCT出现在视图里,这个视图基本就告别更新能力了。
5.2 语句层的两个翻车点
易漏点四:以为索引可以随便建,忘了簇索引只有一个。为什么漏:参数表里「249 个非簇索引」这个大数字太醒目,容易把「1 个簇索引」当成笔误。 怎么补:一张表只能有一个簇索引,因为簇索引决定的是数据的物理存储顺序,一个表不可能同时按两种顺序摆。已经有一个簇索引再建第二个,直接报错。同理,在已经有大量重复值的列上建UNIQUE索引,也会被拒绝——建之前先GROUP BY ... HAVING COUNT(*) > 1查一遍重复值,比建失败再回头找原因省时间。
易漏点五:删列直接删,忘了先拆约束。为什么漏:以为DROP COLUMN就是砍掉一列,没意识到这一列可能被索引、CHECK、外键、默认值、规则绑定引用着。 怎么补:删列前先查依赖,按「删约束 → 删索引 → 删列」的顺序走。被复制的列、用在索引中的列、用在CHECK/ 外键 /UNIQUE/ 主键里的列,都不能直接删。另外DELETE和DROP千万别混:DELETE FROM 表删的是行,表结构还在;DROP TABLE 表删的是整张表,连结构带数据一起没。这两者搞混,是能上生产事故级别的失误。
6. 复习清单:一页纸自查与查漏补缺
到了这一章,笔记该收了。复习时我不再从头翻,而是拿一张自查表过一遍,答不上来的那一格就是当天的复习任务:
| 自查项 | 能答出来就算过 | 答不上就回看 |
|---|---|---|
| 一张表最多几个簇索引、几个非簇索引 | 1 个、249 个 | 2.2 参数表 |
| 一行最多多少字符,是列数还是行宽 | 8060,行宽 | 2.2 参数表 |
| master 和 model 各管什么 | master 存主数据与配置,model 是新库模板 | 2.3 系统库 |
| 关系、元组、属性在工程里叫什么 | 表、行、列 | 3.2 对照表 |
| 一张表算「关系」的五个条件 | 同类型、无重复行、列名不重、无组合列、行列无序 | 3.3 |
| 2NF 解决的是哪种依赖 | 非主属性对码的部分依赖 | 3.4 |
| 3NF 和 BCNF 差在哪 | BCNF 把约束收紧到主属性 | 3.4 |
| 五类约束分别对应哪条完整性 | 主键→实体,外键→参考,CHECK/DEFAULT→域 | 4.3 |
| 视图不可更新的四个典型特征 | 派生列、聚合、TOP/GROUP BY/UNION/DISTINCT | 4.4 |
| 删列前必须先做什么 | 先删约束和索引 | 5.2 |
| 登录、用户、角色分别在哪一层 | 服务器、数据库、权限集合 | 4.7 |
| 游标五步的顺序 | 声明、打开、取、关、释放 | 4.6 |
表格之外,还有三个查漏补缺的具体做法,比反复看笔记有效得多。
第一个是反向出题。把参数表盖住,只留左侧的对象名,自己默写上限;把范式定义盖住,只给一张具体的表,自己判断它到第几范式。能出题说明真懂了,只能读说明只是眼熟。
第二个是拿错误代码验证。笔记里那些「会失败」的例子,亲手跑一遍比看十遍定义管用:往char(10)里插 12 位字符串看截断、往有簇索引的表上再建一个簇索引看报错、在视图里写ORDER BY看提示、删一个带CHECK约束的列看报错信息。报错信息本身就是最好的复习材料,它会告诉你系统在检查什么。
第三个是按错误分类建错题本。我自己的分类是三栏:概念混淆(候选码/主码、登录/用户)、语法限制(视图不能带ORDER BY、DROP COLUMN的前置条件)、数值记串(249 和 32767 混着用)。每栏只记「错在哪 + 正确说法 + 一句话原因」,不抄整道题。复习时只看第一栏,能想起后两栏就算过。
最后说个我自己踩过的坑:早年复习这类笔记,我总喜欢从第一页按顺序读到最后一页,读完感觉全会,一合上笔记问「249 是非簇索引还是索引总数」就卡住。后来改成先过自查表、再定点回看对应章节,同样一小时,效果差出一大截。从那以后我每次复习都强制先做一遍自查表,答不上来的才翻书,不再从头顺着读。希望这套整理方式能帮到你——这份 SQL Server 学习笔记的完整版,按章节顺序排好了,需要的时候拿去对照着过一遍就行。
本文还有配套的精品资源,点击获取