在 SQL Server 中,存储过程(Stored Procedure) 是一组为了完成特定功能而预先编写好、并存储在数据库中的 SQL 语句集合
你可以把它想象成编程语言里的“函数”或“方法”,把复杂的业务逻辑封装在一个存储过程里,以后只需要调用它的名字就能执行,而不需要每次都重新写一遍长长的 SQL 代码
✨ 创建存储过程
CREATE PROCEDURE 存储过程名称
@参数名1 数据类型,
@参数名2 数据类型 OUTPUT -- OUTPUT 表示输出参数
AS
BEGIN
-- 这里写具体的 SQL 逻辑
SELECT * FROM Employees WHERE Department = @参数名1;
END
EXEC 存储过程名称 @参数名1 = '销售部';
🚩 常用场景与代码示例
-- 创建一个查询所有员工信息的存储过程 CREATE PROCEDURE GetEmployeeList AS BEGIN SELECT Name, Department, Salary FROM Employees; END -- 执行 EXEC GetEmployeeList;
-- 根据部门名称查询员工 CREATE PROCEDURE GetEmployeesByDept @DeptName NVARCHAR(50) -- 输入参数 AS BEGIN SELECT Name, Salary FROM Employees WHERE Department = @DeptName; END -- 执行并传入参数 EXEC GetEmployeesByDept @DeptName = '销售部';
-- 查询指定部门的员工人数,并将人数作为结果输出 CREATE PROCEDURE GetDeptCount @DeptName NVARCHAR(50), -- 输入参数 @EmpCount INT OUTPUT -- 输出参数 AS BEGIN SELECT @EmpCount = COUNT(*) FROM Employees WHERE Department = @DeptName; END -- 执行并接收输出结果 DECLARE @TotalCount INT; EXEC GetDeptCount @DeptName = '销售部', @EmpCount = @TotalCount OUTPUT; SELECT @TotalCount AS 部门总人数;
👀 查看存储过程
SELECT definition FROM sys.sql_modules WHERE object_id = OBJECT_ID('存储过程名称');
EXEC sp_helptext '存储过程名称'; SELECT OBJECT_DEFINITION(OBJECT_ID('存储过程名称')); ✏️ 修改存储过程
ALTER PROCEDURE 存储过程名称
@参数名 数据类型
AS
BEGIN
-- 这里写修改后的新 SQL 逻辑
SELECT * FROM 表名 WHERE 条件 = @参数名;
END
🏷️ 重命名存储过程
EXEC sp_rename '旧存储过程名称', '新存储过程名称';
🗑️ 删除存储过程
在 SQL Server 中,触发器(Trigger)是一种特殊的存储过程,它和普通的存储过程最大的区别在于:普通存储过程需要你用 EXEC 显式调用,而触发器是自动执行的 2)
🚩 常用场景与代码示例
场景:记录数据变更日志(AFTER UPDATE 触发器):假设我们想在员工薪资发生变动时,自动把变动记录保存到一张日志表里
-- 创建一个 AFTER UPDATE 触发器 CREATE TRIGGER tr_LogSalaryChange ON Employees AFTER UPDATE AS BEGIN -- 只有当 Salary 列被更新时才执行 IF UPDATE(Salary) BEGIN INSERT INTO SalaryChangeLog (EmployeeID, OldSalary, NewSalary, ChangeDate) SELECT d.EmployeeID, d.Salary AS OldSalary, i.Salary AS NewSalary, GETDATE() FROM deleted d INNER JOIN inserted i ON d.EmployeeID = i.EmployeeID WHERE d.Salary <> i.Salary; -- 确保薪资真的发生了变化 END END
场景:防止核心表被误删(DDL 触发器)
-- 创建一个数据库级别的 DDL 触发器,拦截所有的 DROP TABLE 操作 CREATE TRIGGER safety ON DATABASE FOR DROP_TABLE AS BEGIN PRINT '禁止删除表!请先联系数据库管理员。'; ROLLBACK; -- 回滚操作,取消删除 END
✨ 创建触发器
创建触发器使用 CREATE TRIGGER 语句,你需要指定它绑定的表、触发时机(AFTER 或 INSTEAD OF)以及触发的具体事件(INSERT、UPDATE、DELETE)
CREATE TRIGGER tr_Employee_Audit
ON Employees
AFTER INSERT, UPDATE
AS
BEGIN
-- 这里写触发后执行的逻辑,比如插入日志表
PRINT '员工表数据发生了变动!';
END
👀 查看触发器
EXEC sp_helptext 'tr_Employee_Audit';
查看某张表上所有的触发器信息: EXEC sp_helptrigger 'Employees';
通过系统视图查看更详细的状态(如是否被禁用)
SELECT name, is_disabled FROM sys.triggers WHERE parent_id = OBJECT_ID('Employees');
✏️ 修改触发器 (ALTER)
修改触发器的逻辑时,强烈建议使用 ALTER TRIGGER,它和创建时的语法几乎一模一样,只是把 CREATE 换成了 ALTER
ALTER TRIGGER tr_Employee_Audit ON Employees AFTER INSERT -- 修改为只在插入时触发 AS BEGIN PRINT '员工表发生了新增操作!'; END
🏷️ 重命名触发器 (RENAME)
SQL Server 官方非常不建议使用 sp_rename 来重命名触发器,因为这只会改“外壳”,不会修改系统视图(sys.sql_modules)里的底层定义,容易引发后续维护的混乱
最规范、最安全的做法是:先删除,再用新名字重新创建
-- 1. 删除旧触发器 DROP TRIGGER tr_Employee_Audit; GO -- 2. 用新名字重新创建 CREATE TRIGGER tr_Employee_NewAudit ON Employees AFTER INSERT AS ...
禁用触发器 (DISABLE)
DISABLE TRIGGER tr_Employee_Audit ON Employees;
启用触发器 (ENABLE)
ENABLE TRIGGER tr_Employee_Audit ON Employees;
🗑️ 删除触发器 (DROP)
-- 标准删除 DROP TRIGGER tr_Employee_Audit; -- 安全删除(SQL Server 2016 及以上版本推荐,防止触发器不存在时报错) DROP TRIGGER IF EXISTS tr_Employee_Audit;
在 SQL Server 中,游标(Cursor) 是一种数据库对象,它允许你逐行地处理查询结果集中的数据,你可以把它想象成编程里的“指针”或者一个“数据游标”
普通的 SQL 查询(比如 SELECT)是面向集合的,一次性返回所有符合条件的行;而游标则是面向行的,它让你能够像遍历数组一样,一行一行地读取、修改或执行复杂的业务逻辑
📝 游标的基本使用步骤
使用游标通常遵循固定的“五步走”流程
DECLARE MyCursor CURSOR FOR SELECT Name, Salary FROM Employees WHERE Department = '销售部';
OPEN MyCursor; -- 声明变量来接收数据 DECLARE @EmpName NVARCHAR(50), @EmpSalary DECIMAL(10,2); -- 读取第一行 FETCH NEXT FROM MyCursor INTO @EmpName, @EmpSalary;
WHILE @@FETCH_STATUS = 0 BEGIN -- 在这里写对每一行数据的具体处理逻辑 PRINT '员工姓名:' + @EmpName + ',薪资:' + CAST(@EmpSalary AS VARCHAR); -- 继续读取下一行 FETCH NEXT FROM MyCursor INTO @EmpName, @EmpSalary; END
CLOSE MyCursor; DEALLOCATE MyCursor;
在 SQL Server 中,最常用的查看游标的系统过程是 sp_cursor_list
-- 声明一个变量来接收游标列表 DECLARE @Report CURSOR; -- 调用系统过程,把当前所有的游标信息赋值给这个变量 EXEC sp_cursor_list @cursor_return = @Report OUTPUT, @cursor_scope = 2; -- 查看这个“监控大屏”上的信息 FETCH NEXT FROM @Report; WHILE (@@FETCH_STATUS = 0) BEGIN FETCH NEXT FROM @Report; END -- 看完记得关闭监控 CLOSE @Report; DEALLOCATE @Report;
在 SQL Server 中,要报告服务器游标的属性,最标准的方法是使用系统存储过程 sp_describe_cursor
🚩 假设我们有一个员工表,我们先创建一个游标,然后调用系统过程来查看它的属性
-- 1. 声明并打开一个全局游标 DECLARE MyCursor CURSOR GLOBAL FOR SELECT Name, Salary FROM Employees WHERE Department = '销售部'; OPEN MyCursor; GO -- 2. 声明一个游标变量,用来接收“体检报告” DECLARE @CursorReport CURSOR; -- 3. 调用系统过程 sp_describe_cursor 生成报告 -- 参数说明: -- @cursor_return: 接收报告的变量 -- @cursor_source: 指定游标来源('global' 表示全局游标) -- @cursor_identity: 指定游标的名字 EXEC master.dbo.sp_describe_cursor @cursor_return = @CursorReport OUTPUT, @cursor_source = N'global', @cursor_identity = N'MyCursor'; -- 4. 读取并展示这份“体检报告” FETCH NEXT FROM @CursorReport; WHILE (@@FETCH_STATUS = 0) BEGIN FETCH NEXT FROM @CursorReport; END -- 5. 清理工作:关闭并释放报告游标 CLOSE @CursorReport; DEALLOCATE @CursorReport; GO -- 6. 清理工作:关闭并释放我们最初创建的业务游标 CLOSE MyCursor; DEALLOCATE MyCursor;
在 SQL Server 中,索引(Index) 是一种用于快速查找和访问数据库中数据的特殊数据结构,索引的核心分类:行存储索引和列存储索引
💻 创建索引的 T-SQL 语法
-- 在员工表的 Department 列上创建一个非聚集索引 CREATE NONCLUSTERED INDEX IX_Employees_Department ON Employees (Department);
-- 通常在创建表时指定主键,系统会自动创建聚集索引 CREATE TABLE Employees ( EmployeeID INT PRIMARY KEY, -- 默认创建聚集索引 Name NVARCHAR(50) );
DROP INDEX IX_Employees_Department ON Employees; 🚩 语法示例
CREATE NONCLUSTERED INDEX IX_Employees_Department ON Employees (Department) WITH ( PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = ON, -- 利用 tempdb 加速排序 DROP_EXISTING = OFF, -- 新建索引,不是重建 ONLINE = ON, -- 允许在线操作,不锁表 ALLOW_ROW_LOCKS = ON, -- 允许行锁 ALLOW_PAGE_LOCKS = ON, -- 允许页锁 DATA_COMPRESSION = PAGE -- 开启页压缩,节省空间 ) ON [PRIMARY]; -- 指定存储的文件组
📊 SET STATISTICS IO ON(看真实的 I/O 消耗)
这个开关的作用是:在查询执行后,返回该语句对磁盘和内存的真实读取次数。它是判断 SQL 语句性能最客观的指标之一
SET STATISTICS IO ON; GO -- 你的查询语句 SELECT * FROM Employees WHERE Department = '销售部'; GO SET STATISTICS IO OFF;
输出结果解读:
🗺️ SET SHOWPLAN… ON(看执行计划)
这个开关的作用是:让 SQL Server 只分析并返回执行计划,但不真正执行查询,它能告诉你数据库引擎打算“如何”去执行这条 SQL
SET SHOWPLAN_ALL ON; -- 或者使用 SET SHOWPLAN_TEXT ON GO -- 你的查询语句 SELECT * FROM Employees WHERE Department = '销售部'; GO SET SHOWPLAN_ALL OFF;
输出结果解读:
| 维护目标 | 作用 | 旧版 DBCC 命令 (已弃用) | 现代标准做法 (推荐) |
|---|---|---|---|
| 查看碎片 | 显示指定表或索引的碎片率和统计信息,用于判断是否需要维护 | DBCC SHOWCONTIG | sys.dm_db_index_physical_stats |
| 重建索引 | 删除旧索引并重新创建,最大程度消除碎片,优化页面密度(脱机操作) | DBCC DBREINDEX | ALTER INDEX … REBUILD |
| 重组索引 | 对索引叶级页面进行重新排序,使物理顺序与逻辑顺序一致(联机操作) | DBCC INDEXDEFRAG | ALTER INDEX … REORGANIZE |
全文索引(Full-Text Index)是一种专门用于加速文本数据搜索的索引类型。它和普通的 B-Tree 索引完全不同
⚙️ 功能安装
🛠️ 创建与使用要点
-- 1. 先创建全文目录 CREATE FULLTEXT CATALOG ftCatalog AS DEFAULT; -- CREATE FULLTEXT CATALOG 创建全文目录 -- ftCatalog 全文目录名称 -- AS DEFAULT 把它设为当前数据库的默认全文目录 -- 2. 创建全文索引(表必须有唯一非空列作为键) CREATE FULLTEXT INDEX ON dbo.Articles(Content, Title) -- 给 dbo.Articles 表的 Content 和 Title 列创建全文索引 KEY INDEX PK_Articles -- 使用唯一键索引 PK_Articles 作为行标识(行标识就是通过全文索引筛选出来的一张详细对应表) ON ftCatalog -- 索引数据放在 ftCatalog 全文目录中 WITH CHANGE_TRACKING AUTO; --数据变化时自动同步到全文索引 // 删除全文索引 DROP FULLTEXT INDEX ON 表名;
事务(Transaction)是数据库中不可分割的逻辑工作单元,它包含的一系列操作要么全部成功执行,要么全部不执行。
最经典的例子就是银行转账:A 账户扣款 100 元,B 账户增加 100 元,这两个操作必须作为一个整体成功或失败,不能出现"扣了钱但没到账"的情况
-- 每条语句自动成为一个事务 INSERT INTO users VALUES ('张三', 25); UPDATE users SET age = 26 WHERE name = '张三';
BEGIN TRANSACTION; UPDATE account SET balance = balance - 100 WHERE id = 1; UPDATE account SET balance = balance + 100 WHERE id = 2; COMMIT TRANSACTION;
SET IMPLICIT_TRANSACTIONS ON; -- 以下操作自动开始新事务 INSERT INTO users VALUES ('李四', 30); COMMIT TRANSACTION; -- 显式提交 -- 下一个操作又会自动开始新事务 DELETE FROM users WHERE name = '李四'; ROLLBACK TRANSACTION; -- 显式回滚
BEGIN TRANSACTION — 开始事务: 标记事务的起点,将 @@TRANCOUNT 加 1
BEGIN TRANSACTION; -- 或者简写 BEGIN TRAN;
COMMIT TRANSACTION — 提交事务
COMMIT TRANSACTION; -- 或者简写 COMMIT TRAN;
ROLLBACK TRANSACTION — 回滚事务: 撤销事务中所有已执行的操作,恢复到事务开始前的状态
-- 回滚整个事务 ROLLBACK TRANSACTION; -- 回滚到指定保存点 ROLLBACK TRANSACTION SavePoint1;
SAVE TRANSACTION — 设置保存点: 在事务内部设置一个标记点,可以有选择地回滚到该点,而不是回滚整个事务
BEGIN TRANSACTION; INSERT INTO demo VALUES ('AA', 'A term'); SAVE TRANSACTION SavePoint1; -- 设置保存点 INSERT INTO demo VALUES ('BB', 'B term'); ROLLBACK TRANSACTION SavePoint1; -- 只回滚到保存点,AA 的插入保留 COMMIT TRANSACTION;
SET XACT_ABORT — 错误处理控制: 控制发生运行时错误时是否自动回滚整个事务
-- OFF(默认):只回滚出错的语句,事务继续 SET XACT_ABORT OFF; BEGIN TRAN; INSERT INTO t2 VALUES (1); -- 成功 INSERT INTO t2 VALUES (2); -- 外键错误,只回滚本条 INSERT INTO t2 VALUES (3); -- 成功执行 COMMIT TRAN; -- 1 和 3 被保存 -- ON:任何错误都回滚整个事务 SET XACT_ABORT ON; BEGIN TRAN; INSERT INTO t2 VALUES (4); -- 成功 INSERT INTO t2 VALUES (5); -- 外键错误,整个事务回滚 INSERT INTO t2 VALUES (6); -- 不会执行 COMMIT TRAN; -- 所有修改都被撤销
在生产环境中建议始终设置 SET XACT_ABORT ON,确保错误时整个事务被回滚,避免数据不一致
嵌套事务:QL Server 支持嵌套事务,但需要注意:内部事务的 ROLLBACK 会回滚整个外部事务,而内部事务的 COMMIT 只是将 @@TRANCOUNT 减 1,并不会真正提交
BEGIN TRAN t1; INSERT INTO demo2 VALUES ('lis', 1); BEGIN TRAN t2; INSERT INTO demo VALUES ('BB', 'B term'); COMMIT TRAN t2; -- 只是 @@TRANCOUNT 减 1,并未真正提交 INSERT INTO demo2 VALUES ('lis', 2); COMMIT TRAN t1; -- 这里才真正提交所有操作
分布式事务: 当事务需要跨多个 SQL Server 实例执行时,需要使用分布式事务,由 MS DTC(Microsoft 分布式事务协调器) 管理,采用两阶段提交协议
BEGIN DISTRIBUTED TRANSACTION; UPDATE authors SET au_lname = 'McDonald' WHERE au_id = '409-56-7008'; EXECUTE link_Server.pubs.dbo.change_lname '409-56-7008', 'McDonald'; COMMIT TRAN;
需要安装 MS DTC 服务,且链接服务器的 RPC 选项必须设为 True
🚩 带错误处理的转账事务
SET XACT_ABORT ON; BEGIN TRY BEGIN TRANSACTION; -- A 账户扣款 UPDATE account SET balance = balance - 100 WHERE id = 1; -- B 账户加款 UPDATE account SET balance = balance + 100 WHERE id = 2; -- 记录转账日志 INSERT INTO transfer_log (from_id, to_id, amount, transfer_time) VALUES (1, 2, 100, GETDATE()); COMMIT TRANSACTION; PRINT '转账成功'; END TRY BEGIN CATCH IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION; PRINT '转账失败:' + ERROR_MESSAGE(); END CATCH;
锁是 SQL Server 数据库引擎用来控制多个事务并发访问同一资源的机制。当多个用户同时操作数据库时,锁确保数据的一致性和完整性,防止出现脏读、丢失修改等问题
SQL Server 使用多种锁模式来控制并发访问,每种模式适用于不同的操作场景
共享锁(S 锁)
排他锁(X 锁)
更新锁(U 锁)
意向锁(Intent Locks)
意向锁是表级锁,用于建立锁的层次结构,表明事务打算在更低粒度(页或行)上加锁
| 意向锁类型 | 含义 |
| IS(意向共享) | 事务打算在下层资源上加 S 锁(读) |
| IX(意向排他) | 事务打算在下层资源上加 X 锁(写) |
| SIX(共享意向排他) | 事务在表上加 S 锁,同时打算在下层资源上加 X 锁 |
架构锁(Schema Locks)
| 锁类型 | 用途 |
| Sch-M(架构修改锁) | 执行 DDL 操作(如 `ALTER TABLE`)时使用,阻止所有访问 |
| Sch-S(架构稳定性锁) | 编译查询时使用,阻止 DDL 但不影响 S/U/X 锁 |
-- 查看当前所有锁 SELECT request_session_id AS 会话ID, resource_type AS 资源类型, resource_description AS 资源描述, request_mode AS 锁模式, request_status AS 请求状态 FROM sys.dm_tran_locks;