Skip to content

SQLServer 开发规范

开发规范概述

建立统一的 SQL Server 开发规范对于确保数据库性能、可维护性和安全性至关重要。规范的开发流程和编码标准可以减少错误、提高开发效率,并确保数据库系统的长期稳定运行。

本文将详细介绍 SQL Server 开发规范,包括数据库设计、SQL 编写、存储过程开发、索引设计和性能优化等方面的规范和最佳实践。

数据库设计规范

1. 数据库命名规范

数据库名称

  • 使用有意义的名称,反映业务功能
  • 避免使用特殊字符和空格
  • 使用 PascalCase 命名法(如:SalesDB、HRDB)
  • 长度不超过 64 个字符

架构名称

  • 使用默认架构(dbo)或根据业务功能创建特定架构
  • 架构名称使用 PascalCase(如:Sales、HR)
  • 避免使用系统保留架构名称

2. 表设计规范

表命名

  • 使用有意义的名称,反映表的用途
  • 使用 PascalCase 命名法(如:Customer、OrderDetail)
  • 避免使用复数形式(如:使用 Customer 而非 Customers)
  • 避免使用缩写,除非是广泛认可的缩写

列设计

  • 使用有意义的列名,反映数据内容
  • 使用 PascalCase 命名法(如:CustomerName、OrderDate)
  • 选择合适的数据类型,避免使用过大的数据类型
  • 为每个表设计主键
  • 合理使用 NULL 和 NOT NULL 约束
  • 避免使用 TEXT、NTEXT 和 IMAGE 等过时的数据类型,使用 VARCHAR(MAX)、NVARCHAR(MAX) 和 VARBINARY(MAX) 替代

示例

sql
-- 良好的表设计
CREATE TABLE dbo.Customer (
    CustomerID INT IDENTITY(1,1) PRIMARY KEY,
    CustomerName NVARCHAR(100) NOT NULL,
    Email NVARCHAR(100) NOT NULL UNIQUE,
    Phone VARCHAR(20) NULL,
    CreateDate DATETIME2(3) NOT NULL DEFAULT SYSDATETIME(),
    LastUpdateDate DATETIME2(3) NOT NULL DEFAULT SYSDATETIME()
);

-- 不良的表设计
CREATE TABLE dbo.cust (
    id INT, -- 无意义的列名
    name VARCHAR(500), -- 数据类型过大
    email VARCHAR(100), -- 无 NOT NULL 约束
    dt DATETIME -- 无意义的列名
);

3. 约束设计规范

主键约束

  • 每个表必须有主键
  • 优先使用整数类型(INT、BIGINT)作为主键
  • 考虑使用 IDENTITY 属性自动生成主键值
  • 避免使用复合主键,除非业务上确实需要

外键约束

  • 合理使用外键约束,维护数据完整性
  • 外键名称格式:FK_表名_关联表名
  • 考虑外键约束对性能的影响,特别是在高并发环境

唯一约束

  • 对需要唯一标识的列添加唯一约束
  • 唯一约束名称格式:UK_表名_列名

检查约束

  • 对列值范围添加检查约束
  • 检查约束名称格式:CK_表名_列名

示例

sql
-- 约束示例
CREATE TABLE dbo.OrderDetail (
    OrderDetailID INT IDENTITY(1,1) PRIMARY KEY,
    OrderID INT NOT NULL,
    ProductID INT NOT NULL,
    Quantity INT NOT NULL,
    UnitPrice DECIMAL(10,2) NOT NULL,
    -- 外键约束
    CONSTRAINT FK_OrderDetail_Order FOREIGN KEY (OrderID) REFERENCES dbo.Order(OrderID),
    CONSTRAINT FK_OrderDetail_Product FOREIGN KEY (ProductID) REFERENCES dbo.Product(ProductID),
    -- 检查约束
    CONSTRAINT CK_OrderDetail_Quantity CHECK (Quantity > 0),
    CONSTRAINT CK_OrderDetail_UnitPrice CHECK (UnitPrice >= 0)
);

SQL 编写规范

1. 基本规范

代码风格

  • 使用一致的缩进(建议使用 4 个空格)
  • 关键字使用大写(如:SELECT、FROM、WHERE)
  • 表名和列名使用与数据库中一致的大小写
  • 每行只写一个关键字或子句
  • 使用换行分隔不同的逻辑部分

示例

sql
-- 良好的 SQL 风格
SELECT 
    c.CustomerName,
    o.OrderID,
    o.OrderDate,
    SUM(od.Quantity * od.UnitPrice) AS TotalAmount
FROM 
    dbo.Customer c
JOIN 
    dbo.Order o ON c.CustomerID = o.CustomerID
JOIN 
    dbo.OrderDetail od ON o.OrderID = od.OrderID
WHERE 
    o.OrderDate BETWEEN '2023-01-01' AND '2023-12-31'
GROUP BY 
    c.CustomerName,
    o.OrderID,
    o.OrderDate
ORDER BY 
    TotalAmount DESC;

-- 不良的 SQL 风格
select c.customername,o.orderid,o.orderdate,sum(od.quantity*od.unitprice) as totalamount from dbo.customer c join dbo.order o on c.customerid=o.customerid join dbo.orderdetail od on o.orderid=od.orderid where o.orderdate between '2023-01-01' and '2023-12-31' group by c.customername,o.orderid,o.orderdate order by totalamount desc;

2. SELECT 语句规范

  • 明确指定需要查询的列,避免使用 SELECT *
  • 使用表别名简化查询
  • 合理使用 JOIN 语句,避免笛卡尔积
  • 对大表查询添加 WHERE 条件,减少返回数据量
  • 避免在 WHERE 子句中使用函数,以免无法使用索引
  • 合理使用 ORDER BY 和 GROUP BY,注意对性能的影响

示例

sql
-- 避免使用 SELECT *
SELECT CustomerID, CustomerName, Email FROM dbo.Customer;

-- 避免在 WHERE 子句中使用函数
-- 不良示例
SELECT * FROM dbo.Order WHERE YEAR(OrderDate) = 2023;

-- 良好示例
SELECT * FROM dbo.Order WHERE OrderDate BETWEEN '2023-01-01' AND '2023-12-31';

3. INSERT 语句规范

  • 明确指定插入的列名,避免依赖列顺序
  • 批量插入时使用 BULK INSERT 或表值参数,提高性能
  • 避免在循环中执行单条 INSERT 语句
  • 考虑使用事务确保数据完整性

示例

sql
-- 明确指定列名
INSERT INTO dbo.Customer (CustomerName, Email, Phone) 
VALUES ('John Doe', 'john.doe@example.com', '123-456-7890');

-- 批量插入示例(使用表值参数)
DECLARE @Customers TABLE (
    CustomerName NVARCHAR(100),
    Email NVARCHAR(100),
    Phone VARCHAR(20)
);

INSERT INTO @Customers VALUES 
('John Doe', 'john.doe@example.com', '123-456-7890'),
('Jane Smith', 'jane.smith@example.com', '987-654-3210'),
('Bob Johnson', 'bob.johnson@example.com', '555-123-4567');

INSERT INTO dbo.Customer (CustomerName, Email, Phone)
SELECT CustomerName, Email, Phone FROM @Customers;

4. UPDATE 和 DELETE 语句规范

  • 始终在 UPDATE 和 DELETE 语句中添加 WHERE 条件,避免误操作
  • 使用事务确保数据一致性
  • 考虑使用 TOP 子句限制更新或删除的行数
  • 避免在 UPDATE 语句中更新索引列,特别是聚集索引
  • 对于大数据量的更新或删除,考虑分批次处理

示例

sql
-- 始终添加 WHERE 条件
UPDATE dbo.Customer 
SET LastUpdateDate = SYSDATETIME() 
WHERE CustomerID = 1;

-- 避免无 WHERE 条件的 UPDATE
-- 危险示例
UPDATE dbo.Customer SET LastUpdateDate = SYSDATETIME();

-- 分批次更新示例
DECLARE @BatchSize INT = 1000;
DECLARE @RowCount INT = 1;

WHILE @RowCount > 0
BEGIN
    UPDATE TOP (@BatchSize) dbo.OldTable
    SET Status = 'Processed'
    WHERE Status = 'Pending';
    
    SET @RowCount = @@ROWCOUNT;
    
    -- 可选:添加延迟,减少系统负载
    -- WAITFOR DELAY '00:00:01';
END;

存储过程和函数规范

1. 命名规范

  • 存储过程名称格式:usp_功能_对象(如:usp_GetCustomer、usp_InsertOrder)
  • 函数名称格式:ufn_功能_对象(如:ufn_CalculateTotal、ufn_FormatPhone)
  • 避免使用 sp_ 前缀,因为 SQL Server 会先搜索系统存储过程

2. 编码规范

  • 每个存储过程和函数开头添加注释,说明功能、参数和返回值
  • 使用 SET NOCOUNT ON 减少网络流量
  • 合理使用参数,避免 SQL 注入
  • 避免在存储过程中使用动态 SQL,如必须使用,使用参数化查询
  • 处理错误和异常,使用 TRY...CATCH 块
  • 避免在存储过程中使用游标,尽量使用集合操作

示例

sql
-- 存储过程示例
CREATE PROCEDURE usp_GetCustomer
    @CustomerID INT,
    @IncludeOrders BIT = 0
AS
BEGIN
    SET NOCOUNT ON;
    
    BEGIN TRY
        -- 查询客户信息
        SELECT 
            c.CustomerID,
            c.CustomerName,
            c.Email,
            c.Phone,
            c.CreateDate,
            c.LastUpdateDate
        FROM 
            dbo.Customer c
        WHERE 
            c.CustomerID = @CustomerID;
        
        -- 如果需要,查询订单信息
        IF @IncludeOrders = 1
        BEGIN
            SELECT 
                o.OrderID,
                o.OrderDate,
                o.TotalAmount
            FROM 
                dbo.Order o
            WHERE 
                o.CustomerID = @CustomerID
            ORDER BY 
                o.OrderDate DESC;
        END
    END TRY
    BEGIN CATCH
        -- 记录错误信息
        INSERT INTO dbo.ErrorLog (ErrorMessage, ErrorNumber, ErrorProcedure, ErrorLine)
        VALUES (ERROR_MESSAGE(), ERROR_NUMBER(), ERROR_PROCEDURE(), ERROR_LINE());
        
        -- 重新抛出错误
        THROW;
    END CATCH;
END;

3. 函数规范

  • 区分标量函数、表值函数和多语句表值函数
  • 优先使用内联表值函数,性能优于多语句表值函数
  • 避免在函数中使用副作用操作(如修改表)
  • 合理使用函数,避免过度使用导致性能问题

示例

sql
-- 内联表值函数示例
CREATE FUNCTION ufn_GetRecentOrders
    (@Days INT)
RETURNS TABLE
AS
RETURN
(
    SELECT 
        o.OrderID,
        o.CustomerID,
        o.OrderDate,
        o.TotalAmount
    FROM 
        dbo.Order o
    WHERE 
        o.OrderDate >= DATEADD(DAY, -@Days, SYSDATETIME())
);

-- 标量函数示例
CREATE FUNCTION ufn_CalculateDiscount
    (@TotalAmount DECIMAL(10,2))
RETURNS DECIMAL(10,2)
AS
BEGIN
    DECLARE @Discount DECIMAL(10,2);
    
    IF @TotalAmount >= 1000
        SET @Discount = @TotalAmount * 0.1;
    ELSE IF @TotalAmount >= 500
        SET @Discount = @TotalAmount * 0.05;
    ELSE
        SET @Discount = 0;
    
    RETURN @Discount;
END;

索引设计规范

1. 索引命名规范

  • 聚集索引名称:PK_表名
  • 非聚集索引名称:IX_表名_列名1_列名2
  • 唯一索引名称:UX_表名_列名1_列名2

2. 索引设计原则

  • 为经常用于查询条件、连接条件和排序的列创建索引
  • 避免为频繁更新的列创建过多索引
  • 考虑索引覆盖,包含查询中需要的所有列
  • 合理设计复合索引,将最常用的列放在前面
  • 避免创建重复索引
  • 定期维护索引,重新组织或重建碎片化的索引

示例

sql
-- 良好的索引设计
CREATE NONCLUSTERED INDEX IX_Order_OrderDate_CustomerID
ON dbo.Order (OrderDate DESC, CustomerID)
INCLUDE (TotalAmount); -- 包含查询中需要的列,避免书签查找

-- 避免重复索引
-- 不良示例:重复索引
CREATE NONCLUSTERED INDEX IX_Customer_Email ON dbo.Customer (Email);
CREATE UNIQUE INDEX UX_Customer_Email ON dbo.Customer (Email); -- 与上面的索引重复

视图和同义词规范

1. 视图规范

  • 视图名称格式:v_功能_对象(如:v_CustomerOrders、v_ProductInventory)
  • 明确指定视图中的列名
  • 避免在视图中使用复杂的逻辑和函数
  • 考虑使用架构绑定视图,提高性能
  • 定期刷新索引视图的数据

示例

sql
-- 视图示例
CREATE VIEW v_CustomerOrders
AS
SELECT 
    c.CustomerID,
    c.CustomerName,
    o.OrderID,
    o.OrderDate,
    o.TotalAmount,
    od.ProductID,
    p.ProductName,
    od.Quantity,
    od.UnitPrice
FROM 
    dbo.Customer c
JOIN 
    dbo.Order o ON c.CustomerID = o.CustomerID
JOIN 
    dbo.OrderDetail od ON o.OrderID = od.OrderID
JOIN 
    dbo.Product p ON od.ProductID = p.ProductID;

2. 同义词规范

  • 同义词名称使用与原对象一致的命名规范
  • 合理使用同义词,简化跨数据库或跨服务器访问
  • 避免过度使用同义词,以免降低代码可读性

示例

sql
-- 同义词示例
CREATE SYNONYM dbo.RemoteCustomer
FOR RemoteServer.RemoteDB.dbo.Customer;

-- 使用同义词
SELECT * FROM dbo.RemoteCustomer;

事务和锁规范

1. 事务规范

  • 保持事务简短,减少锁定时间
  • 使用明确的事务边界(BEGIN TRANSACTION、COMMIT、ROLLBACK)
  • 避免在事务中执行不必要的操作(如等待用户输入)
  • 考虑使用 SNAPSHOT 隔离级别,减少锁定冲突
  • 合理设置事务隔离级别,平衡一致性和性能

示例

sql
-- 事务示例
BEGIN TRANSACTION;

BEGIN TRY
    -- 插入订单
    INSERT INTO dbo.Order (CustomerID, OrderDate, TotalAmount) 
    VALUES (@CustomerID, SYSDATETIME(), @TotalAmount);
    
    DECLARE @OrderID INT = SCOPE_IDENTITY();
    
    -- 插入订单详情
    INSERT INTO dbo.OrderDetail (OrderID, ProductID, Quantity, UnitPrice) 
    SELECT @OrderID, ProductID, Quantity, UnitPrice 
    FROM @OrderDetails;
    
    -- 更新产品库存
    UPDATE p
    SET p.StockQuantity = p.StockQuantity - od.Quantity
    FROM dbo.Product p
    JOIN @OrderDetails od ON p.ProductID = od.ProductID;
    
    -- 提交事务
    COMMIT TRANSACTION;
    
    -- 返回新订单ID
    SELECT @OrderID AS OrderID;
END TRY
BEGIN CATCH
    -- 回滚事务
    ROLLBACK TRANSACTION;
    
    -- 记录错误
    INSERT INTO dbo.ErrorLog (ErrorMessage, ErrorNumber) 
    VALUES (ERROR_MESSAGE(), ERROR_NUMBER());
    
    -- 重新抛出错误
    THROW;
END CATCH;

2. 锁规范

  • 避免长时间持有锁
  • 合理使用锁定提示(如 NOLOCK、ROWLOCK),但要注意数据一致性
  • 避免在高并发环境中使用表锁
  • 监控锁定和阻塞情况,及时解决问题

性能优化规范

1. 查询优化

  • 使用执行计划分析查询性能
  • 避免全表扫描,尽量使用索引查找
  • 优化 JOIN 操作,选择合适的连接类型
  • 避免在查询中使用 OR 条件,考虑使用 UNION 替代
  • 合理使用临时表和表变量,避免过度使用
  • 避免使用游标,尽量使用集合操作

2. 索引优化

  • 定期分析索引使用情况,删除未使用的索引
  • 重建或重新组织碎片化的索引
  • 考虑使用列存储索引,提高数据仓库查询性能
  • 合理设置填充因子,减少页分裂

3. 配置优化

  • 根据工作负载调整 SQL Server 配置参数
  • 优化 TempDB 配置,避免争用
  • 配置适当的内存设置
  • 优化存储配置,提高 IO 性能

开发流程规范

1. 开发环境

  • 使用独立的开发、测试和生产环境
  • 确保开发环境与生产环境配置相似
  • 使用版本控制系统管理数据库脚本

2. 变更管理

  • 所有数据库变更必须经过评审
  • 使用变更脚本管理数据库变更
  • 测试变更脚本在测试环境的效果
  • 制定回滚计划,确保变更可以回滚
  • 记录所有数据库变更,包括时间、人员和内容

3. 测试规范

  • 为每个数据库对象编写测试用例
  • 测试边界情况和异常情况
  • 执行性能测试,确保符合性能要求
  • 测试高并发情况下的系统稳定性

常见问题

Q: 如何避免 SQL 注入攻击?

A

  • 使用参数化查询或存储过程
  • 验证和过滤用户输入
  • 避免使用动态 SQL,如必须使用,使用 sp_executesql 和参数
  • 限制数据库用户的权限,遵循最小权限原则

Q: 如何提高查询性能?

A

  • 为查询条件列创建索引
  • 避免使用 SELECT *,只查询需要的列
  • 优化 JOIN 操作,选择合适的连接类型
  • 避免在 WHERE 子句中使用函数
  • 考虑使用索引覆盖或包含列

Q: 如何处理大数据量的更新或删除?

A

  • 使用分批次处理,每次处理少量数据
  • 避免长时间持有锁
  • 选择业务低峰期执行
  • 使用事务确保数据一致性
  • 考虑使用临时表或表变量

Q: 如何优化存储过程性能?

A

  • 避免使用游标,尽量使用集合操作
  • 使用 SET NOCOUNT ON
  • 合理使用参数,避免参数嗅探
  • 避免在存储过程中使用动态 SQL
  • 优化存储过程中的查询,使用合适的索引

总结

建立和遵循 SQL Server 开发规范对于确保数据库系统的性能、可维护性和安全性至关重要。规范的开发流程和编码标准可以减少错误、提高开发效率,并确保数据库系统的长期稳定运行。

本文介绍了 SQL Server 开发规范的各个方面,包括数据库设计、SQL 编写、存储过程开发、索引设计和性能优化等。DBA 和开发人员应该共同遵循这些规范,确保数据库系统的高质量和高性能。

同时,规范应该根据业务需求和技术发展不断更新和完善,以适应不断变化的数据库环境和技术栈。