数据库系统原理 · SQL 数据定义、更新及数据库编程 · 自学总结
本章核心SQL 不只是查询还包括定义数据结构、修改数据、以及用程序化的方式操作数据库。一、SQL 数据定义语言DDL1.1 是什么DDLData Definition Language 定义和修改数据库对象的语言负责建骨架。下辖知识点语句作用CREATE创建数据库对象数据库、表、视图、索引、约束等ALTER修改已有对象的结构DROP删除对象TRUNCATE清空表数据保留结构数据类型定义列的存储类型INT、VARCHAR、DATE、DECIMAL 等完整性约束PRIMARY KEY、FOREIGN KEY、UNIQUE、NOT NULL、CHECK、DEFAULT索引INDEX加速查询的数据结构域Domain自定义数据类型部分 DBMS 支持模式Schema数据库对象的命名空间/容器1.2 为什么要有 DDL没有 DDL数据库就是一团混沌没有 DDL 的问题DDL 解决后不知道数据存在哪、什么格式用 CREATE TABLE 明确定义结构程序代码和数据结构耦合改表结构不用改程序数据独立性数据没有约束想填什么填什么用 CHECK、NOT NULL 等保证质量查询慢得离谱用 CREATE INDEX 建立索引加速多人协作各自为政用 Schema 做命名空间隔离核心价值结构化管理—— 数据不是乱堆的而是有明确定义的格式。约束前置—— 在数据入库前就拦住脏数据。性能基础—— 索引、分区等性能优化手段都靠 DDL 实现。1.3 怎么用创建表CREATE TABLECREATE TABLE 学生 ( 学号 CHAR(10) PRIMARY KEY, -- 主码约束 姓名 VARCHAR(20) NOT NULL, -- 非空约束 性别 CHAR(2) CHECK (性别 IN (男,女)), -- CHECK约束 年龄 INT DEFAULT 18, -- 默认值 系号 CHAR(10), 身份证号 CHAR(18) UNIQUE, -- 唯一约束 -- 外码约束表级定义 CONSTRAINT FK_系号 FOREIGN KEY (系号) REFERENCES 系(系号) ON DELETE SET NULL -- 系被删学生系号变NULL ON UPDATE CASCADE -- 系号改了学生跟着改 );修改表ALTER TABLE-- 加列 ALTER TABLE 学生 ADD 邮箱 VARCHAR(50); -- 删列 ALTER TABLE 学生 DROP COLUMN 邮箱; -- 改列类型 ALTER TABLE 学生 ALTER COLUMN 年龄 SMALLINT; -- 加约束 ALTER TABLE 学生 ADD CONSTRAINT CHK_年龄 CHECK (年龄 BETWEEN 15 AND 50); -- 删约束 ALTER TABLE 学生 DROP CONSTRAINT CHK_年龄;删除表DROP TABLEDROP TABLE 学生; -- 表结构和数据全删 DROP TABLE 学生 CASCADE; -- 级联删除关联对象如外键引用的视图索引CREATE INDEX-- 单列索引 CREATE INDEX IX_姓名 ON 学生(姓名); -- 复合索引 CREATE INDEX IX_系名_年龄 ON 学生(系号, 年龄); -- 唯一索引 CREATE UNIQUE INDEX IX_身份证 ON 学生(身份证号); -- 删除索引 DROP INDEX IX_姓名;索引的作用加速 WHERE、ORDER BY、JOIN 的查询速度代价是占用空间、减慢 INSERT/UPDATE/DELETE。模式Schema-- 创建模式命名空间 CREATE SCHEMA 教学管理; -- 在指定模式下建表 CREATE TABLE 教学管理.学生 (...); -- 授权 GRANT CREATE SCHEMA TO 用户名;二、SQL 数据更新语言DML2.1 是什么DMLData Manipulation Language 操作表中数据的语言负责填内容。下辖知识点语句作用INSERT插入新数据UPDATE修改已有数据DELETE删除数据MERGE / UPSERT插入或更新存在则更新不存在则插入批量插入一次插入多行子查询插入从查询结果插入2.2 为什么要有 DML数据不是静态的需要持续维护场景DML 动作新生入学INSERT 插入学生记录学生转系UPDATE 修改系号学生退学DELETE 删除记录成绩录入INSERT / UPDATE 成绩数据迁移INSERT 子查询从旧表导入核心价值数据生命周期管理—— 数据会增删改DML 是日常运维的主力。集合操作—— SQL 的 DML 操作的是集合多行不是逐行处理效率高。与查询结合—— INSERT/UPDATE/DELETE 可以嵌套子查询实现复杂的数据维护。2.3 怎么用插入INSERT-- 插入单行 INSERT INTO 学生 (学号, 姓名, 性别, 年龄, 系号) VALUES (2024001, 张三, 男, 20, CS); -- 插入多行 INSERT INTO 学生 (学号, 姓名, 性别, 年龄, 系号) VALUES (2024002, 李四, 女, 19, CS), (2024003, 王五, 男, 21, EE); -- 从查询结果插入批量导入 INSERT INTO 优秀学生 (学号, 姓名, 平均分) SELECT 学号, 姓名, AVG(成绩) FROM 学生 JOIN 选课 ON 学生.学号 选课.学号 GROUP BY 学号, 姓名 HAVING AVG(成绩) 85;更新UPDATE-- 单表更新 UPDATE 学生 SET 年龄 年龄 1 WHERE 系号 CS; -- 带子查询的更新 UPDATE 学生 SET 系号 AI WHERE 学号 IN ( SELECT 学号 FROM 选课 WHERE 课程号 AI101 GROUP BY 学号 HAVING COUNT(*) 3 ); -- 多表关联更新SQL Server语法 UPDATE 学生 SET 学生.系号 新系表.新系号 FROM 学生 JOIN 新系表 ON 学生.系号 新系表.旧系号;删除DELETE-- 条件删除 DELETE FROM 学生 WHERE 系号 CS; -- 带子查询的删除 DELETE FROM 学生 WHERE 学号 NOT IN (SELECT DISTINCT 学号 FROM 选课); -- 删除没选任何课的学生 -- 清空表可回滚 DELETE FROM 学生;DELETE vs TRUNCATE 区别特性DELETETRUNCATE语句类型DMLDDL是否记录日志逐行记录日志量大记录页级操作日志量小能否回滚✅ 可以✅ 可以事务内能否触发触发器✅ 触发 DELETE 触发器❌ 不触发效率慢快重置自增ID❌ 不重置✅ 重置三、视图View3.1 是什么视图 从一个或多个基本表或其他视图导出的虚拟表不存储实际数据只保存查询定义。下辖知识点知识点是什么简单视图基于单表、无函数、无 GROUP BY 的视图复杂视图多表连接、聚合函数、分组等可更新视图可以通过视图 INSERT/UPDATE/DELETE 基本表不可更新视图涉及聚合、DISTINCT、GROUP BY 等的视图无法直接更新WITH CHECK OPTION限制通过视图修改的数据必须满足视图的 WHERE 条件物化视图实际存储查询结果的视图有冗余但查询快级联删除/更新视图定义中的级联操作3.2 为什么要有视图直接操作基本表的困境问题视图解决表结构复杂用户只需要看几列视图只暴露需要的列隐藏敏感/无关字段同样的复杂查询要写无数次视图把查询封装起来用时像查表一样简单不同用户应该看到不同数据视图加权限控制实现行级/列级安全重构表结构后应用程序要改视图作为抽象层底下表结构变了视图不变报表查询要实时计算聚合物化视图预存结果查询直接读核心价值简化查询—— 复杂查询包成视图用的时候SELECT * FROM 视图名。安全隔离—— 用户只能看到视图允许看到的列和行。逻辑独立性—— 表结构调整时通过视图兼容旧接口。性能优化物化视图—— 预计算 预存储报表秒出。3.3 怎么用创建视图CREATE VIEW-- 简单视图只暴露部分列 CREATE VIEW 学生基本信息 AS SELECT 学号, 姓名, 性别, 系号 FROM 学生; -- 复杂视图多表连接 聚合 CREATE VIEW 系成绩统计 AS SELECT 学生.系号, COUNT(DISTINCT 学生.学号) AS 学生人数, AVG(选课.成绩) AS 平均成绩, MAX(选课.成绩) AS 最高分 FROM 学生 LEFT JOIN 选课 ON 学生.学号 选课.学号 GROUP BY 学生.系号; -- 安全视图只显示成绩及格的学生 CREATE VIEW 及格学生 AS SELECT 学号, 姓名, 系号 FROM 学生 WHERE 学号 IN (SELECT 学号 FROM 选课 WHERE 成绩 60) WITH CHECK OPTION; -- 通过此视图插入的学生必须满足条件使用视图-- 像查表一样查视图 SELECT * FROM 系成绩统计 WHERE 平均成绩 80; -- 更新视图仅限可更新视图 UPDATE 学生基本信息 SET 系号 AI WHERE 学号 2024001; -- 实际修改的是底层学生表删除视图DROP VIEW 系成绩统计; -- 只删视图定义不影响基本表物化视图Materialized View-- Oracle / PostgreSQL 语法 CREATE MATERIALIZED VIEW 月销售统计 AS SELECT 月份, SUM(金额) AS 总额 FROM 订单 GROUP BY 月份; -- 手动刷新 REFRESH MATERIALIZED VIEW 月销售统计; -- 或定时自动刷新视图 vs 物化视图特性普通视图物化视图是否存数据❌ 不存每次实时查✅ 存了查询结果查询速度慢要执行底层查询快直接读结果数据实时性实时取决于刷新策略占用空间几乎不占用占用存储四、T-SQL 简介4.1 是什么T-SQLTransact-SQL SQL Server 的 SQL 方言扩展是标准 SQL 加上微软扩展的过程化编程能力。下辖知识点知识点是什么变量声明DECLARE 变量名 类型赋值SET 变量 值或SELECT 变量 列 FROM ...流程控制IF...ELSE、WHILE、CASE、GOTO、RETURN批处理GO分隔批处理注释-- 单行和/* 多行 */系统函数GETDATE()、LEN()、CONVERT()等TRY...CATCH异常处理事务控制BEGIN TRAN、COMMIT、ROLLBACK、SAVEPOINT4.2 为什么要有 T-SQL标准 SQL 是声明式的只描述要什么不能描述怎么做的流程标准 SQL 的局限T-SQL 解决没法写变量暂存中间结果用变量存储没法按条件分支执行用IF...ELSE没法循环处理用WHILE出错只能返回错误码用TRY...CATCH优雅处理多条语句无法打包执行用BEGIN...END和GO无法调用复杂业务逻辑用存储过程/函数封装核心价值过程化编程—— 在数据库内部完成复杂逻辑不用来回传数据到应用层。减少网络往返—— 逻辑在数据库里执行省去应用层和数据库的通信开销。统一维护—— 业务逻辑写在数据库里一处改到处生效。4.3 怎么用变量与赋值DECLARE 系号 CHAR(10); DECLARE 人数 INT; -- 直接赋值 SET 系号 CS; -- 从查询结果赋值 SELECT 人数 COUNT(*) FROM 学生 WHERE 系号 系号; PRINT 该系有 CAST(人数 AS VARCHAR) 人;流程控制-- IF...ELSE DECLARE 平均分 DECIMAL(5,2); SELECT 平均分 AVG(成绩) FROM 选课 WHERE 课程号 DB; IF 平均分 80 PRINT 成绩优秀; ELSE IF 平均分 60 PRINT 成绩合格; ELSE PRINT 需要加强; -- WHILE 循环 DECLARE i INT 1; WHILE i 10 BEGIN INSERT INTO 测试表 (序号) VALUES (i); SET i i 1; END; -- CASE 表达式 SELECT 姓名, CASE WHEN 成绩 90 THEN 优秀 WHEN 成绩 80 THEN 良好 WHEN 成绩 60 THEN 及格 ELSE 不及格 END AS 等级 FROM 学生 JOIN 选课 ON ...;事务控制BEGIN TRANSACTION; BEGIN TRY UPDATE 账户 SET 余额 余额 - 1000 WHERE 账户号 A; UPDATE 账户 SET 余额 余额 1000 WHERE 账户号 B; COMMIT; -- 成功提交 PRINT 转账成功; END TRY BEGIN CATCH ROLLBACK; -- 出错回滚 PRINT 转账失败 ERROR_MESSAGE(); END CATCH;常用系统函数SELECT GETDATE() AS 当前时间, -- 2025-01-15 10:30:00 LEN(数据库) AS 字符串长度, -- 3 CAST(123 AS VARCHAR) AS 转字符串, -- 123 CONVERT(VARCHAR, GETDATE(), 120) AS 格式化日期 -- 120 ODBC 规范五、游标Cursor5.1 是什么游标 一种逐行处理查询结果集的机制把集合型的 SQL 结果转换成一行一行处理的方式。下辖知识点知识点是什么声明游标DECLARE 游标名 CURSOR FOR SELECT...打开游标OPEN取数据FETCH NEXT INTO 变量循环遍历WHILE FETCH_STATUS 0关闭游标CLOSE释放游标DEALLOCATE游标类型只进游标、动态游标、键集驱动游标、静态游标游标属性FETCH_STATUS、CURSOR_ROWS5.2 为什么要有游标SQL 是集合操作的但有些场景必须逐行处理场景为什么需要游标对每行数据做不同处理比如根据成绩分段发送不同的通知调用逐行处理的存储过程每行都要触发一个复杂操作结果集太大内存放不下用游标一次读一行流式处理与不支持集合操作的外部系统交互逐行输出给旧系统行与行之间有依赖关系下一行的计算依赖上一行的结果核心价值逐行控制能力—— 弥补 SQL 集合操作的不足。内存友好—— 不用一次性加载整个结果集。但注意游标是最后手段能用集合操作解决的优先用集合操作因为游标性能差。5.3 怎么用游标的基本使用流程-- 1. 声明游标 DECLARE cur_student CURSOR FOR SELECT 学号, 姓名, 年龄 FROM 学生 WHERE 系号 CS; -- 2. 打开游标 OPEN cur_student; -- 3. 取第一行 FETCH NEXT FROM cur_student INTO 学号, 姓名, 年龄; -- 4. 循环处理 WHILE FETCH_STATUS 0 -- 0 表示成功取到数据 BEGIN -- 对当前行做处理 PRINT 姓名 的年龄是 CAST(年龄 AS VARCHAR); -- 取下一行 FETCH NEXT FROM cur_student INTO 学号, 姓名, 年龄; END; -- 5. 关闭并释放 CLOSE cur_student; DEALLOCATE cur_student;一个完整的业务例子-- 给每个学生发送通知假设有发邮件的存储过程 DECLARE 学号 CHAR(10), 姓名 VARCHAR(20), 成绩 INT; DECLARE cur_grade CURSOR FOR SELECT 学生.学号, 学生.姓名, 选课.成绩 FROM 学生 JOIN 选课 ON 学生.学号 选课.学号 WHERE 选课.课程号 DB; OPEN cur_grade; FETCH NEXT FROM cur_grade INTO 学号, 姓名, 成绩; WHILE FETCH_STATUS 0 BEGIN IF 成绩 90 EXEC 发送通知 学号, 恭喜 姓名 你的数据库成绩优秀; ELSE IF 成绩 60 EXEC 发送通知 学号, 姓名 你的数据库课程需要补考。; FETCH NEXT FROM cur_grade INTO 学号, 姓名, 成绩; END; CLOSE cur_grade; DEALLOCATE cur_grade;集合操作的替代方案更推荐-- 用 CASE 一次性处理性能更好 SELECT 学号, 姓名, CASE WHEN 成绩 90 THEN 优秀 WHEN 成绩 60 THEN 需补考 ELSE 正常 END AS 评价 FROM 学生 JOIN 选课 ON ...;六、存储过程Stored Procedure6.1 是什么存储过程 预编译并存储在数据库中的一组 SQL 语句可以接收参数、执行逻辑、返回结果。下辖知识点知识点是什么创建存储过程CREATE PROCEDURE参数输入参数IN、输出参数OUT、输入输出参数INOUT返回值RETURN返回整数状态码结果集SELECT返回结果集修改/删除ALTER PROCEDURE、DROP PROCEDURE执行EXEC / EXECUTE递归调用存储过程调用自身加密WITH ENCRYPTION保护源码重新编译WITH RECOMPILE每次执行重新生成执行计划6.2 为什么要有存储过程把 SQL 语句写在应用程序里的困境问题存储过程解决同样的 SQL 在多处重复写封装一次到处调用SQL 语句通过网络传来传去只传过程名和参数减少网络流量数据库结构改了所有应用代码要改改存储过程即可应用层无感知复杂逻辑要在应用层和数据库间往返逻辑在数据库内部完成减少往返SQL 注入攻击风险参数化存储过程天然防注入权限控制粒度粗用户只需有执行存储过程的权限无需直接访问表核心价值代码复用—— 一处写到处用。性能提升—— 预编译、减少网络传输。安全增强—— 权限隔离、防 SQL 注入。维护方便—— 改存储过程 改全局逻辑。6.3 怎么用创建存储过程-- 简单存储过程查询某系学生 CREATE PROCEDURE GetStudentsByDept 系号 VARCHAR(10) AS BEGIN SELECT 学号, 姓名, 年龄 FROM 学生 WHERE 系号 系号; END; -- 带输出参数的存储过程统计某系人数 CREATE PROCEDURE CountStudentsByDept 系号 VARCHAR(10), 人数 INT OUTPUT AS BEGIN SELECT 人数 COUNT(*) FROM 学生 WHERE 系号 系号; RETURN 0; -- 返回状态码 0 表示成功 END; -- 复杂存储过程转账 CREATE PROCEDURE TransferMoney 转出账户 CHAR(10), 转入账户 CHAR(10), 金额 DECIMAL(18,2) AS BEGIN BEGIN TRANSACTION; BEGIN TRY -- 检查余额 DECLARE 余额 DECIMAL(18,2); SELECT 余额 余额 FROM 账户 WHERE 账户号 转出账户; IF 余额 金额 BEGIN ROLLBACK; RETURN 1; -- 余额不足 END; -- 执行转账 UPDATE 账户 SET 余额 余额 - 金额 WHERE 账户号 转出账户; UPDATE 账户 SET 余额 余额 金额 WHERE 账户号 转入账户; COMMIT; RETURN 0; -- 成功 END TRY BEGIN CATCH ROLLBACK; RETURN -1; -- 系统错误 END CATCH; END;执行存储过程-- 执行简单存储过程 EXEC GetStudentsByDept CS; -- 执行带输出参数的存储过程 DECLARE 人数 INT; EXEC CountStudentsByDept CS, 人数 OUTPUT; PRINT 该系人数 CAST(人数 AS VARCHAR); -- 执行转账存储过程 DECLARE 结果 INT; EXEC 结果 TransferMoney A001, B002, 1000.00; IF 结果 0 PRINT 转账成功; ELSE IF 结果 1 PRINT 余额不足; ELSE PRINT 系统错误;删除存储过程DROP PROCEDURE GetStudentsByDept;七、触发器Trigger7.1 是什么触发器 一种特殊的存储过程不由用户显式调用而是在特定事件INSERT/UPDATE/DELETE发生时自动触发执行。下辖知识点知识点是什么DML 触发器由 INSERT/UPDATE/DELETE 触发DDL 触发器由 CREATE/ALTER/DROP 等触发AFTER 触发器在操作完成后触发SQL Server 默认INSTEAD OF 触发器替代原始操作执行常用于视图更新BEFORE 触发器在操作执行前触发MySQL/Oracle 支持INSERTED 表存放刚插入/更新后的新数据内存中的虚拟表DELETED 表存放刚删除/更新前的旧数据内存中的虚拟表行级触发器每行触发一次语句级触发器每条语句触发一次递归/嵌套触发器触发器触发另一个触发器ENABLE/DISABLE启用/禁用触发器7.2 为什么要有触发器有些操作必须在数据变更时自动发生不能靠人工场景触发器作用插入订单时自动扣减库存INSERT 触发器自动更新库存表删除员工时自动删除其家属记录DELETE 触发器级联删除修改账户余额时自动记录审计日志UPDATE 触发器写审计表防止非法时间段的修改BEFORE 触发器检查并拒绝复杂约束无法用 CHECK 表达用触发器写任意逻辑判断数据同步主表修改时触发器同步更新从表/缓存核心价值自动化—— 事件驱动不用手动调用。数据一致性—— 关联表之间的级联操作自动完成。审计追踪—— 谁在什么时候改了什么自动记录。复杂约束—— CHECK 不够用时触发器可以写任意逻辑。但注意触发器是双刃剑过多或过于复杂会降低性能、增加维护难度。7.3 怎么用创建触发器-- 触发器1插入学生时自动初始化选课统计表 CREATE TRIGGER trg_InsertStudent ON 学生 AFTER INSERT AS BEGIN INSERT INTO 学生统计 (学号, 选课门数, 总学分) SELECT 学号, 0, 0 FROM INSERTED; END; -- 触发器2删除学生时级联删除其选课记录 CREATE TRIGGER trg_DeleteStudent ON 学生 INSTEAD OF DELETE AS BEGIN -- 先删选课记录 DELETE FROM 选课 WHERE 学号 IN (SELECT 学号 FROM DELETED); -- 再删学生 DELETE FROM 学生 WHERE 学号 IN (SELECT 学号 FROM DELETED); END; -- 触发器3修改成绩时记录审计日志 CREATE TRIGGER trg_AuditGrade ON 选课 AFTER UPDATE AS BEGIN IF UPDATE(成绩) BEGIN INSERT INTO 成绩变更日志 (学号, 课程号, 旧成绩, 新成绩, 修改时间, 操作人) SELECT DELETED.学号, DELETED.课程号, DELETED.成绩, INSERTED.成绩, GETDATE(), SUSER_SNAME() FROM DELETED JOIN INSERTED ON DELETED.学号 INSERTED.学号 AND DELETED.课程号 INSERTED.课程号; END; END;INSERTED 和 DELETED 虚拟表操作INSERTEDDELETEDINSERT包含刚插入的新行空DELETE空包含刚删除的旧行UPDATE包含更新后的新行包含更新前的旧行禁用/启用触发器-- 禁用触发器批量导入数据时临时禁用 DISABLE TRIGGER trg_AuditGrade ON 选课; -- 批量操作... -- 重新启用 ENABLE TRIGGER trg_AuditGrade ON 选课;触发器 vs 约束 vs 存储过程 对比特性触发器约束存储过程执行时机自动事件驱动自动数据变更时检查手动调用主要用途级联操作、审计、复杂规则保证数据完整性封装业务逻辑灵活性最高任意逻辑低固定规则高任意逻辑性能影响较大小可控是否透明对应用透明对应用透明应用需显式调用八、知识脉络图SQL 数据定义、更新及数据库编程 │ ├── DDL建骨架 │ ├── 是什么CREATE/ALTER/DROP/TRUNCATE │ ├── 为什么定义结构、加约束、建索引、保证质量 │ └── 怎么用建表、改表、删表、加索引、建模式 │ ├── DML填内容 │ ├── 是什么INSERT/UPDATE/DELETE/MERGE │ ├── 为什么数据增删改、批量导入、集合操作 │ └── 怎么用单行/多行插入、带子查询更新、条件删除 │ ├── 视图虚拟表 │ ├── 是什么从基本表导出的虚拟表 │ ├── 为什么简化查询、安全隔离、逻辑独立、性能优化 │ └── 怎么用CREATE VIEW、可更新视图、WITH CHECK OPTION、物化视图 │ ├── T-SQL过程化编程 │ ├── 是什么SQL Server 的过程化扩展 │ ├── 为什么变量、分支、循环、异常处理、事务控制 │ └── 怎么用DECLARE/SET、IF...ELSE/WHILE/CASE、TRY...CATCH │ ├── 游标逐行处理 │ ├── 是什么逐行遍历结果集的机制 │ ├── 为什么集合操作无法处理的逐行场景 │ └── 怎么用DECLARE/OPEN/FETCH/CLOSE/DEALLOCATE │ ├── 存储过程预编译代码块 │ ├── 是什么预编译并存储的 SQL 过程 │ ├── 为什么复用、性能、安全、减少网络传输 │ └── 怎么用CREATE PROCEDURE、参数、返回值、EXEC │ └── 触发器事件驱动自动执行 ├── 是什么INSERT/UPDATE/DELETE 时自动触发的特殊过程 ├── 为什么自动化、级联操作、审计、复杂约束 └── 怎么用AFTER/INSTEAD OF、INSERTED/DELETED、ENABLE/DISABLE九、一句话记忆概念一句话DDL建骨架CREATE 建、ALTER 改、DROP 删DML填内容INSERT 插、UPDATE 改、DELETE 删视图虚拟表不存数据只存查询定义可更新视图简单的视图可以直接改底层表物化视图把视图结果存下来查起来飞快T-SQLSQL 变量 分支 循环 异常处理游标一行一行啃结果集性能差但有时候必须用存储过程把一堆 SQL 打包存数据库里随叫随到触发器表一有动静就自动执行的暗器INSERTED 表触发器里看新数据的窗口DELETED 表触发器里看旧数据的窗口AFTER 触发器改完后再执行INSTEAD OF 触发器把原来的操作替换掉触发器双刃剑用多了性能差调试难谨慎用总结基于《数据库系统概论》第3章扩展及数据库编程知识体系整理