尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

SQL Server数据表创建:从基础语法到高级性能优化实战

SQL Server数据表创建:从基础语法到高级性能优化实战 1. 项目概述从零到一构建数据容器在数据库的世界里数据表是存储和操作数据的基石。无论你是刚接触SQL Server的新手还是需要快速回顾语法细节的开发者掌握创建数据表的完整语法都是一项核心技能。这不仅仅是记住几个关键字那么简单它关乎到数据结构的合理性、查询性能的优劣以及未来业务扩展的灵活性。一个设计良好的表能为整个应用系统打下坚实的基础而一个随意创建的表则可能成为后续维护和性能优化的噩梦。很多人以为CREATE TABLE语句就是简单的“列名加数据类型”但实际工作中你需要考虑的远不止这些如何设置主键确保唯一性怎样添加外键维护数据完整性索引该怎么建才能加速查询字段是否允许为空默认值怎么设这些细节共同构成了数据表的“完整语法”。接下来我将结合十多年的数据库开发与运维经验为你拆解SQL Server中创建数据表的每一个语法元素并分享那些官方文档里不会写的实操心得和避坑指南。2. 核心语法结构全解析创建数据表的CREATE TABLE语句其完整骨架远比想象中复杂。它不是一个简单的命令而是一个包含表结构定义、约束声明、索引创建甚至文件组指定的综合性声明。理解这个结构是写出健壮、高效DDL语句的第一步。2.1 基础语法框架最基础的CREATE TABLE语句包含以下部分CREATE TABLE [database_name].[schema_name].table_name ( column1 datatype [NULL | NOT NULL] [IDENTITY(seed, increment)] [CONSTRAINT ...], column2 datatype [NULL | NOT NULL] [DEFAULT default_value], ... [CONSTRAINT constraint_name] PRIMARY KEY (column_name), [CONSTRAINT constraint_name] FOREIGN KEY (column_name) REFERENCES other_table(other_column), [CONSTRAINT constraint_name] CHECK (logical_expression), [CONSTRAINT constraint_name] UNIQUE (column_name) ) [ON {filegroup | default}]; [TEXTIMAGE_ON {filegroup | default}];我们来逐一拆解CREATE TABLE这是语句的起始命令告诉SQL Server你要创建一个新表。[database_name].[schema_name].table_name这是表的三部分名称。database_name是可选的默认为当前数据库schema_name通常为dbo数据库所有者它像是一个命名空间用于逻辑上组织数据库对象。我强烈建议始终显式指定架构例如dbo.Employee这能避免因用户默认架构不同而导致的潜在问题。列定义括号内的部分是表的列定义这是表的核心。每一列都需要定义其名称、数据类型和可选的属性。表约束在列定义之后可以定义表级的约束如主键、外键、检查约束和唯一约束。这些约束可以确保数据的完整性和一致性。ON子句这个可选子句指定表存储在哪个文件组上。对于大型数据库或需要性能隔离的场景合理使用文件组至关重要。TEXTIMAGE_ON子句则专门用于存储text、ntext、image、xml、varchar(max)、nvarchar(max)、varbinary(max)等大型对象LOB数据到特定的文件组。注意在定义列时NULL和NOT NULL属性必须明确指定。在SQL Server中数据库的ANSI_NULL_DEFAULT设置会影响默认行为但依赖默认设置是极不推荐的。显式声明可以消除歧义让表结构意图清晰这在团队协作和后期维护中非常重要。2.2 深入理解架构与命名架构Schema是一个常常被初学者忽略但极其重要的概念。它不是简单的“所有者”而是一个容器对象用于对表、视图、存储过程等进行逻辑分组。使用架构的好处很多权限管理可以针对整个架构授权而不是单个表简化安全管理。对象组织例如你可以创建HR架构存放所有人力资源相关的表Sales架构存放销售相关的表。避免命名冲突不同架构下可以有同名的表。在创建表时如果省略架构SQL Server会使用执行用户的默认架构。如果用户没有默认架构则使用dbo。这种隐式行为是许多“对象名无效”错误的根源。我的经验是永远使用两部分名称Schema.TableName来引用或创建对象。在脚本开头使用USE [YourDatabaseName];和SET QUOTED_IDENTIFIER ON;等语句来设定明确的上下文也是一个好习惯。3. 列定义数据表的基石列定义是CREATE TABLE语句中最核心的部分它决定了表中可以存储什么样的数据。一个考虑周到的列定义能为数据质量保驾护航。3.1 数据类型的选择艺术选择合适的数据类型是优化存储空间和查询性能的关键。SQL Server提供了丰富的数据类型以下是一些常用类型及选型建议数据类型分类常用类型存储范围/特点适用场景与选型建议精确数字int-2^31 到 2^31-1主键ID、计数、金额单位分。最常用的整数类型。bigint-2^63 到 2^63-1超大数据量的主键或需要极大范围的计数。decimal(p, s)固定精度和小数位财务数据、需要精确计算的数值。p是总位数s是小数位。近似数字float(n)浮点数值科学计算、对精度要求不高的测量数据。注意有精度损失。字符串char(n)固定长度非Unicode存储长度完全固定的代码如国家代码char(2)。浪费存储但检索快。varchar(n)可变长度非Unicode最常用。存储长度可变的文本如姓名、地址。n最大可为8000。varchar(max)可变长度非Unicode存储超大文本如文章内容、日志。最大2GB。nchar(n)/nvarchar(n)Unicode版本需要存储多语言字符如中文、阿拉伯文时使用。一个字符占2字节。日期时间date仅存储日期生日、入职日期等只需要日期的场景。datetime2大范围高精度推荐替代datetime。日期范围更大0001-9999年精度更高100纳秒。datetimeoffset包含时区信息需要记录绝对时间如全球性系统的交易时间的场景。实操心得主键列优先使用int或bigint并设置为IDENTITY自增。整数类型的比较和索引效率远高于字符串。字符串长度为varchar列设置长度时不要盲目使用max或一个很大的值。应根据业务实际最大可能长度来定义这有助于优化存储和内存分配。例如用户名varchar(50)通常足够。nvarcharvsvarchar如果你的系统确定只使用英文字符用varchar可以节省一半存储空间。但只要存在存储中文等非拉丁字符的可能就应使用nvarchar避免未来出现乱码问题。日期类型在新项目中忘记datetime吧直接用datetime2。它更精确存储范围更大是微软推荐的新标准。3.2 列属性详解除了数据类型列的属性定义了其行为和规则。NULL与NOT NULL这是最重要的属性之一。它规定该列是否允许存储NULL值表示未知或缺失。主键列必须为NOT NULL。对于业务上必须有值的字段如EmployeeName、OrderDate也应设为NOT NULL。允许NULL的列会增加查询逻辑的复杂性例如WHERE column value不会匹配NULL的行。IDENTITY属性用于创建自增列通常作为代理主键。EmployeeID int IDENTITY(1,1) NOT NULL PRIMARY KEYIDENTITY(1,1)种子为1增量为1。即从1开始每次增加1。自增列的值由数据库自动管理通常不允许手动插入除非使用SET IDENTITY_INSERT table_name ON。注意IDENTITY属性不保证连续性。如果插入失败或事务回滚序列号会被消耗掉导致“断层”。DEFAULT约束为列指定默认值。当插入数据未指定该列的值时将自动使用默认值。CreateDate datetime2 NOT NULL DEFAULT (GETUTCDATE()), -- 默认当前UTC时间 IsActive bit NOT NULL DEFAULT (1) -- 默认值为1True Status varchar(10) NOT NULL DEFAULT (Pending) -- 默认状态为‘Pending’使用默认值可以简化插入操作并确保数据有一致的初始状态。对于GETDATE()或GETUTCDATE()这类函数要用括号括起来。计算列计算列的值不是存储的而是通过同一表中其他列的计算表达式得出的。TotalAmount AS (Quantity * UnitPrice), -- 虚拟计算列不存储 PersistedTotal AS (Quantity * UnitPrice) PERSISTED -- 持久化计算列物理存储虚拟计算列在查询时实时计算不占用存储空间。持久化计算列PERSISTED将计算结果物理存储占用空间但查询性能好并且可以在其上创建索引。计算列不能用于INSERT或UPDATE语句除非是PERSISTED且使用了WITH CHECK选项的特定情况。4. 表约束数据完整性的守护者约束是强制数据完整性的规则。它们定义在表级别确保数据符合预定的业务规则。4.1 主键约束主键唯一标识表中的每一行。一个表只能有一个主键。-- 方式1作为列属性内联定义 CREATE TABLE dbo.Products ( ProductID int IDENTITY(1,1) PRIMARY KEY, ProductName nvarchar(100) NOT NULL ); -- 方式2作为表约束推荐可命名 CREATE TABLE dbo.Orders ( OrderID int IDENTITY(1,1) NOT NULL, OrderDate datetime2 NOT NULL, CONSTRAINT PK_Orders_OrderID PRIMARY KEY CLUSTERED (OrderID) );命名约束强烈推荐方式2为约束显式命名如PK_Orders_OrderID。当需要修改或删除约束时有名字会方便得多。否则数据库会生成一个随机的、难以理解的名字。CLUSTERED/NONCLUSTERED主键默认创建聚集索引CLUSTERED。聚集索引决定了表中数据的物理存储顺序。选择哪个列作为聚集索引键至关重要它通常是范围查询最多的列或者是单调递增的列如IDENTITY列以避免页分裂。4.2 外键约束外键用于建立两个表之间的链接它指向另一个表的主键或唯一键以强制引用完整性。CREATE TABLE dbo.OrderDetails ( OrderDetailID int IDENTITY(1,1) PRIMARY KEY, OrderID int NOT NULL, ProductID int NOT NULL, Quantity int NOT NULL, CONSTRAINT FK_OrderDetails_Orders FOREIGN KEY (OrderID) REFERENCES dbo.Orders(OrderID) ON DELETE CASCADE ON UPDATE NO ACTION, CONSTRAINT FK_OrderDetails_Products FOREIGN KEY (ProductID) REFERENCES dbo.Products(ProductID) );REFERENCES指定被引用的主表及其主键列。ON DELETE和ON UPDATE指定当主表中的行被删除或更新时子表中的对应行该怎么办。NO ACTION默认如果子表有匹配行则阻止删除/更新操作。CASCADE级联删除/更新子表中的匹配行。使用需极度谨慎可能导致意外的大规模数据删除。SET NULL将子表中的外键列设置为NULL要求该列允许NULL。SET DEFAULT将子表中的外键列设置为其默认值。性能考虑外键列上一定要建立索引否则每次检查引用完整性或执行CASCADE操作时都可能引发全表扫描严重拖慢性能。4.3 唯一约束与检查约束唯一约束确保列或列组合中的值都是唯一的但允许NULL值SQL Server中唯一约束允许存在一个NULL值除非该列同时被定义为NOT NULL。CONSTRAINT UQ_Employees_Email UNIQUE NONCLUSTERED (Email)它和主键的区别在于一个表可以有多个唯一约束且唯一约束列允许NULL。常用于邮箱、身份证号等业务上唯一但非主键的字段。检查约束限制列中可接受的值用于实施简单的业务规则。CONSTRAINT CK_Employees_Age CHECK (Age 18 AND Age 65), CONSTRAINT CK_Orders_Amount CHECK (TotalAmount 0)检查约束的表达式必须返回布尔值。它可以引用同一行的多个列。这是保证数据质量的第一道、也是最直接的防线。4.4 默认约束虽然DEFAULT可以在列定义中指定但作为命名约束定义也是一种好方法特别是当默认值比较复杂或者需要后续修改时。CREATE TABLE dbo.Log ( LogID int IDENTITY PRIMARY KEY, LogTime datetime2 NOT NULL, LogMessage nvarchar(max), CONSTRAINT DF_Log_LogTime DEFAULT (GETUTCDATE()) FOR LogTime );5. 高级选项与性能考量当基础结构满足后我们需要关注一些高级选项它们直接影响着表的性能、可管理性和可扩展性。5.1 索引设计在创建表时规划虽然索引可以在表创建后添加但在CREATE TABLE语句中直接定义聚集索引和主键能让数据在初始插入时就按最优顺序物理存储。CREATE TABLE dbo.LargeTransaction ( TransactionID bigint IDENTITY(1,1) NOT NULL, AccountID int NOT NULL, TransactionDate datetime2 NOT NULL, Amount decimal(18,2) NOT NULL, -- 定义主键聚集索引 CONSTRAINT PK_LargeTransaction_TransactionID PRIMARY KEY CLUSTERED (TransactionID), -- 同时定义另一个非聚集索引 INDEX IX_LargeTransaction_AccountDate NONCLUSTERED (AccountID, TransactionDate) ) ON [PRIMARY];聚集索引选择如果查询大多按TransactionID顺序访问那么将其作为聚集索引键是合适的。如果大部分查询是按AccountID和TransactionDate进行范围查询那么将(AccountID, TransactionDate)设为聚集索引键可能性能更好但这会牺牲插入性能因为TransactionID不是递增的。包含性列索引对于SELECT *或查询多个列的语句可以考虑使用包含性列索引来避免键查找。CREATE INDEX IX_Orders_CustomerID ON dbo.Orders(CustomerID) INCLUDE (OrderDate, Status);5.2 分区表管理海量数据的利器对于数据量极大例如数亿行的表可以考虑使用分区表。分区将一个大表在物理上分割成多个更小的、更易管理的部分分区但在逻辑上仍然是一个表。-- 1. 创建分区函数按日期范围分区 CREATE PARTITION FUNCTION pf_TransactionDate (datetime2) AS RANGE RIGHT FOR VALUES (2023-01-01, 2024-01-01, 2025-01-01); -- 2. 创建分区方案将分区映射到文件组 CREATE PARTITION SCHEME ps_TransactionDate AS PARTITION pf_TransactionDate TO ([FG2022], [FG2023], [FG2024], [FG2025]); -- 预先创建好的文件组 -- 3. 在分区方案上创建表 CREATE TABLE dbo.TransactionHistory ( TransactionID bigint IDENTITY, TransactionDate datetime2 NOT NULL, ... CONSTRAINT PK_TransactionHistory PRIMARY KEY (TransactionID, TransactionDate) -- 分区列必须包含在主键中 ) ON ps_TransactionDate(TransactionDate);注意事项分区列如上例的TransactionDate必须是主键或唯一索引的一部分。分区能极大提升针对分区列的范围查询和数据维护如归档、删除旧数据的效率。但分区设计复杂需要提前规划文件组和磁盘布局不适合小型数据库。5.3 文件组与存储优化ON子句允许你将表或索引放置在特定的文件组上。文件组是数据库文件的逻辑容器。CREATE TABLE dbo.LargeLOBData ( ID int PRIMARY KEY, DocumentData varbinary(max) ) ON [USERDATA] -- 将行数据放在USERDATA文件组 TEXTIMAGE_ON [LOBDATA]; -- 将LOB数据单独放在LOBDATA文件组这样做的好处是性能隔离可以将频繁访问的表放在高速磁盘如SSD对应的文件组上将历史归档表放在低速磁盘上。备份灵活性可以单独备份或还原某个文件组。管理大型对象使用TEXTIMAGE_ON将LOB数据分离存储避免大字段拖慢行数据的扫描速度。6. 完整创建表示例与脚本模板下面是一个融合了上述所有要点的、接近生产环境的完整示例。它模拟了一个电商订单系统的核心表。USE [YourDatabaseName]; GO -- 检查表是否存在避免重复创建 IF OBJECT_ID(dbo.Orders, U) IS NOT NULL DROP TABLE dbo.Orders; IF OBJECT_ID(dbo.OrderDetails, U) IS NOT NULL DROP TABLE dbo.OrderDetails; IF OBJECT_ID(dbo.Products, U) IS NOT NULL DROP TABLE dbo.Products; IF OBJECT_ID(dbo.Customers, U) IS NOT NULL DROP TABLE dbo.Customers; GO -- 1. 创建客户表 CREATE TABLE dbo.Customers ( CustomerID int IDENTITY(1000, 1) NOT NULL, -- 从1000开始自增 CustomerCode AS (CUST RIGHT(00000 CAST(CustomerID AS varchar(10)), 5)) PERSISTED, -- 持久化计算列生成客户编码 FullName nvarchar(100) NOT NULL, Email nvarchar(255) NOT NULL, Phone varchar(20) NULL, RegistrationDate datetime2 NOT NULL CONSTRAINT DF_Customers_RegDate DEFAULT (GETUTCDATE()), IsActive bit NOT NULL CONSTRAINT DF_Customers_IsActive DEFAULT (1), LastLoginTime datetime2 NULL, -- 约束定义 CONSTRAINT PK_Customers_CustomerID PRIMARY KEY CLUSTERED (CustomerID), CONSTRAINT UQ_Customers_Email UNIQUE NONCLUSTERED (Email), CONSTRAINT CK_Customers_Email CHECK (Email LIKE %__%._%), -- 简单的邮箱格式检查 INDEX IX_Customers_Name NONCLUSTERED (FullName) ) ON [PRIMARY]; GO -- 2. 创建产品表 CREATE TABLE dbo.Products ( ProductID int IDENTITY(1,1) NOT NULL, SKU varchar(20) NOT NULL, -- 库存单位业务唯一标识 ProductName nvarchar(200) NOT NULL, CategoryID int NOT NULL, UnitPrice decimal(10,2) NOT NULL CONSTRAINT CK_Products_Price CHECK (UnitPrice 0), CostPrice decimal(10,2) NULL, StockQuantity int NOT NULL CONSTRAINT DF_Products_Stock DEFAULT (0), IsListed bit NOT NULL CONSTRAINT DF_Products_Listed DEFAULT (1), CreatedTime datetime2 NOT NULL CONSTRAINT DF_Products_Created DEFAULT (SYSDATETIME()), ModifiedTime datetime2 NOT NULL CONSTRAINT DF_Products_Modified DEFAULT (SYSDATETIME()), CONSTRAINT PK_Products_ProductID PRIMARY KEY CLUSTERED (ProductID), CONSTRAINT UQ_Products_SKU UNIQUE (SKU), INDEX IX_Products_Category NONCLUSTERED (CategoryID, IsListed) INCLUDE (UnitPrice, ProductName) -- 覆盖索引加速按分类查询 ) ON [PRIMARY]; GO -- 3. 创建订单表假设按年分区 -- 首先我们需要分区函数和方案此处简化实际需先创建文件组 CREATE PARTITION FUNCTION pf_OrderYear (int) AS RANGE RIGHT FOR VALUES (20230101, 20240101, 20250101); -- 按年分区键整数表示YYYYMMDD CREATE PARTITION SCHEME ps_OrderYear AS PARTITION pf_OrderYear ALL TO ([PRIMARY]); -- 简化实际应分配到不同文件组 CREATE TABLE dbo.Orders ( OrderID bigint IDENTITY(1,1) NOT NULL, OrderNumber AS (ORD CONVERT(varchar(8), OrderDate, 112) RIGHT(000000 CAST(OrderID AS varchar(10)), 6)) PERSISTED, -- 生成订单号如ORD20231015000001 CustomerID int NOT NULL, OrderDate datetime2 NOT NULL CONSTRAINT DF_Orders_OrderDate DEFAULT (GETUTCDATE()), OrderYear AS (YEAR(OrderDate)) PERSISTED, -- 持久化列用于分区 TotalAmount decimal(18,2) NOT NULL, Status varchar(20) NOT NULL CONSTRAINT DF_Orders_Status DEFAULT (Pending) CONSTRAINT CK_Orders_Status CHECK (Status IN (Pending, Paid, Shipped, Delivered, Cancelled)), ShippingAddress nvarchar(500) NOT NULL, Notes nvarchar(max) NULL, CONSTRAINT PK_Orders_OrderID PRIMARY KEY NONCLUSTERED (OrderID, OrderYear), -- 非聚集主键分区列需包含在内 CONSTRAINT FK_Orders_Customers FOREIGN KEY (CustomerID) REFERENCES dbo.Customers(CustomerID) ON UPDATE NO ACTION ON DELETE NO ACTION, INDEX IX_Orders_CustomerDate NONCLUSTERED (CustomerID, OrderDate) INCLUDE (Status, TotalAmount), INDEX IX_Orders_DateStatus NONCLUSTERED (OrderDate, Status) ) ON ps_OrderYear(OrderYear); -- 在分区方案上创建表 GO -- 4. 创建订单明细表 CREATE TABLE dbo.OrderDetails ( OrderDetailID bigint IDENTITY(1,1) NOT NULL, OrderID bigint NOT NULL, ProductID int NOT NULL, Quantity int NOT NULL CONSTRAINT CK_OrderDetails_Quantity CHECK (Quantity 0), UnitPrice decimal(10,2) NOT NULL, -- 下单时的单价可能与产品当前价不同 LineTotal AS (Quantity * UnitPrice) PERSISTED, -- 计算行总价 CONSTRAINT PK_OrderDetails_OrderDetailID PRIMARY KEY CLUSTERED (OrderDetailID), CONSTRAINT FK_OrderDetails_Orders FOREIGN KEY (OrderID) REFERENCES dbo.Orders(OrderID) ON DELETE CASCADE, -- 订单删除明细级联删除 CONSTRAINT FK_OrderDetails_Products FOREIGN KEY (ProductID) REFERENCES dbo.Products(ProductID), CONSTRAINT UQ_OrderDetails_OrderProduct UNIQUE (OrderID, ProductID), -- 防止同一订单重复添加同一产品 INDEX IX_OrderDetails_ProductID NONCLUSTERED (ProductID) ) ON [PRIMARY]; GO -- 5. 在OrderDetails的外键列上创建索引最佳实践 CREATE INDEX IX_OrderDetails_OrderID ON dbo.OrderDetails(OrderID); GO -- 6. 插入示例数据 INSERT INTO dbo.Customers (FullName, Email) VALUES (张三, zhangsanexample.com); INSERT INTO dbo.Products (SKU, ProductName, CategoryID, UnitPrice) VALUES (PROD001, 无线鼠标, 1, 99.00); DECLARE CustomerID int SCOPE_IDENTITY(); DECLARE ProductID int SCOPE_IDENTITY(); INSERT INTO dbo.Orders (CustomerID, TotalAmount, ShippingAddress) VALUES (CustomerID, 198.00, 北京市海淀区); DECLARE OrderID bigint SCOPE_IDENTITY(); INSERT INTO dbo.OrderDetails (OrderID, ProductID, Quantity, UnitPrice) VALUES (OrderID, ProductID, 2, 99.00); GO7. 常见问题与排查技巧实录即使语法完全正确在实际创建表的过程中你仍然可能会遇到各种问题。以下是一些常见错误及其解决方法很多都是我在踩坑后总结的经验。7.1 错误对象名无效 / 外键引用失败问题描述执行CREATE TABLE时提示“对象名 XXX 无效”或“外键 FK_XXX 引用无效”。原因与排查对象不存在你引用的表在外键约束中或架构不存在。确保被引用的表在创建当前表之前已经存在。脚本的执行顺序很重要。架构不匹配你可能在dbo架构下创建表却引用了Sales.Customer。检查REFERENCES子句中的对象名是否包含正确的架构。列名或数据类型不匹配外键列的数据类型和长度必须与被引用列完全一致。例如int不能引用bigintvarchar(50)不能引用nvarchar(100)。解决方案仔细检查并修正对象名称确保使用两部分名称Schema.Table。调整脚本执行顺序先创建被引用的表父表再创建引用它的表子表。使用数据库设计工具如SSMS的数据库关系图来辅助验证关系。7.2 错误IDENTITY_INSERT设置为OFF问题描述尝试向带有IDENTITY列的表中插入显式值时报错“仅当使用了列列表并且 IDENTITY_INSERT 为 ON 时才能为表 XXX 中的标识列指定显式值”。解决方案-- 1. 允许插入显式值 SET IDENTITY_INSERT dbo.YourTable ON; INSERT INTO dbo.YourTable (IDColumn, OtherColumn) VALUES (10, Value); SET IDENTITY_INSERT dbo.YourTable OFF; -- 记得关闭 -- 2. 如果只是想获取刚插入的标识值使用SCOPE_IDENTITY()、IDENTITY或OUTPUT子句。 INSERT INTO dbo.YourTable (OtherColumn) VALUES (Value); SELECT SCOPE_IDENTITY(); -- 获取当前作用域内最后生成的标识值7.3 性能问题创建表后查询缓慢问题描述表创建成功了但插入数据后简单的查询都非常慢。排查与解决缺少索引这是最常见的原因。使用SQL Server Management Studio (SSMS) 的“执行计划”功能查看查询是否进行了全表扫描Table Scan。对于WHERE、JOIN、ORDER BY子句中常用的列应考虑创建索引。聚集索引选择不当如果聚集索引键选择了一个频繁更新的列或随机值列会导致大量的页分裂和碎片。聚集索引应选择静态的、递增的列如自增ID或用于范围查询的列。锁与阻塞在创建表或创建索引时特别是在线创建大型索引可能会长时间锁定表阻塞其他查询。对于大表考虑在业务低峰期进行操作或使用ONLINE ON选项企业版功能来创建索引减少阻塞。统计信息过期对于新创建并立即灌入大量数据的表统计信息可能没有及时更新导致查询优化器做出错误决策。可以手动更新统计信息UPDATE STATISTICS dbo.YourTable WITH FULLSCAN;7.4 设计陷阱与最佳实践总结过度使用SELECT *在表定义阶段就要考虑避免后续所有查询都使用SELECT *。明确列出需要的列或使用包含性列索引来覆盖查询能显著减少I/O。滥用VARCHAR(MAX)/NVARCHAR(MAX)将这些类型用于所有字符串列会浪费存储并影响性能。只有当数据可能超过8000字节时才使用MAX。忽略数据归档策略对于日志表、历史交易表等只增不减的表如果没有归档策略表会无限增长。在设计之初就应考虑按时间分区便于将旧数据移动到更便宜的存储或直接归档删除。忘记为外键列建索引这可能是影响联表查询性能的最大单一因素。记住这个口诀有外键必有索引。命名规范建立并遵循一套命名规范。例如主键PK_TableName_ColumnName外键FK_ChildTable_ParentTable索引IX_TableName_ColumnName。这能让你的数据库脚本一目了然。创建数据表是数据库设计的起点一个考虑周全的表结构是后续所有数据操作和性能优化的基石。花时间在设计阶段深思熟虑远比在问题出现后去修改一个已有数百万数据的生产表要轻松和安全得多。每次下笔写CREATE TABLE之前不妨多问自己几个问题这个字段真的需要吗这个数据类型是最优的吗哪些查询会用到这张表索引该如何设计回答好这些问题你创建的就不仅仅是一张表而是一个高效、稳定、易于维护的数据基石。
返回列表