资讯动态

SQL Server权限表设计:RBAC模型下Users、Roles、UserRoles三表实战

发布时间:2026/10/16 12:25:48 来源:尧图企业网站定制
做后台系统绕不开账号和权限。不管内容管理后台、电商运营平台还是会员系统底子都是同一套东西用户表、角色表再加一张用户与角色的关联表。这篇文章用 SQL Server 的 T-SQL 规范把这三张表从需求拆解到建表实战完整走一遍过程中会解释每一步为什么这样选适合刚接手数据库设计或者想把表结构写得更规范的开发者参考。我这些年看过不少项目的权限模块最常见的写法是用户表里直接放一个 RoleId看起来省了一张表等到业务要求“一个用户既是编辑又是审核员”就会很尴尬。改表加列、加 Json 字段、甚至按角色复制账号的我都见过。问题不是出在写法上而是建模阶段没把“用户、角色、权限”这三者的关系想清楚。所以先别急着写 CREATE TABLE先把模型讲透。1. 需求拆解与建模思路1.1 为什么是“用户 角色”而不是“用户 权限”权限模型里最常用的是 RBAC也就是基于角色的访问控制。它的核心思路是把“用户”和“权限”解耦中间加一个“角色”作为缓冲。用户不直接挂权限而是挂角色角色再挂权限。看起来多绕了一层实际收益非常大。打个比方公司不会给每个员工单独写一份“你可以进哪间办公室、能不能用那台打印机”的清单而是统一设置岗位。行政岗有行政岗的权限开发岗有开发岗的权限。员工离职了岗位还在新员工入职直接套岗位就行。系统里的角色就是“岗位”一套角色规则可以复用到任意多个用户身上。为什么这次只设计用户表和角色表不把权限表一起建了因为权限点菜单、按钮、接口通常要等业务页面稳定后才能梳理清楚。第一版先把用户、角色、用户与角色这三张关系表建好后续要加“角色权限关联表”时直接新建表、挂外键即可不需要动现有结构。这就是分层的好处。1.2 三张表怎么分工这套结构里每张表只干一件事Users 表记录“谁”。存登录名、显示名、密码哈希、状态这些账号属性。Roles 表记录“身份”。存角色编码、角色名、说明、状态。UserRoles 表记录“谁拥有哪个身份”。这是典型的多对多关系表。为什么不把角色直接写到 Users 表的 RoleId 列三个原因。第一角色数量不固定。一个用户完全可能是“内容编辑”加“内容审核”两个角色加一个 RoleId 根本不够加两个、三个列都挡不住下一次变化。第二关联表可以记录额外信息。比如这个角色是谁在什么时候授予的、什么时候过期这些在审计和权限管理里非常有用。第三查“某个角色下有哪些用户”这种反向查询靠关联表按 RoleId 过滤一下就出来了效率很高。有些小型系统确实用一个 RoleId 就能撑住但一旦需求升级到多角色就要拆表迁移。这个代价远大于一开始就建关联表的成本。1.3 字段设计到什么程度该收手很多人喜欢第一次就把用户表字段铺全生日、性别、部门、工号、兴趣标签……我都理解业务方提需求时恨不得全要。但从数据库设计角度第一版要守住边界。User 表当前必须的字段其实不多业务主键 UserId、登录名 UserName、显示名 DisplayName、密码哈希 PasswordHash、启用状态 IsEnabled、创建时间 CreatedAt。这些是账号体系运转的底座。可以预埋但不急着写的最后登录时间、头像地址、邮箱、手机号。邮箱和手机号可以做唯一标识但在需要短信、邮件服务对接之前它们只是普通联系方式。头像更是纯展示字段。第一版先把核心账号建好资料类字段留给后续的个人资料模块。还有一个建议非核心字段可以单独建扩展表通过 UserId 关联。这不是过度设计而是让主表保持精简。主表字段越多索引维护成本越高将来改字段的连锁影响也越大。1.4 表名、字段名、约束名统一约定先声明一个原则SQL Server 不区分大小写取决于排序规则但代码规范要统一。我习惯用 PascalCase 命名表名和字段名表名用复数dbo.Users、dbo.Roles、dbo.UserRoles。这里有个特殊的点USER 在 T-SQL 里是有特殊含义的关键字。直接写CREATE TABLE dbo.User会遇到一堆麻烦。复数命名不仅符合“表里存多个用户”的语义也顺手规避了关键字冲突。约束名也要有规则。主键用 PK_表名唯一键用 UK_表名_列名外键用 FK_子表_父表默认约束用 DF_表名_列名。好处是在报错、迁移、运维时能直接定位到具体约束。SSMS 自动生成的约束名是一串随机字符像 FK__UserRole__UserI__2329BC38没人看得懂是哪个字段的约束。schema 前缀 dbo 要显式写出来。虽然当前用户默认就在 dbo schema 下但脚本在不同库、不同登录下执行时显式写 dbo 能保证行为一致。这个习惯在维护长期项目时特别重要。2. T-SQL 规范下的结构选型2.1 数据类型怎么选为什么先把每个关键字段怎么选讲清楚。主键用 INT IDENTITY(1,1) 是大多数情况下的合理选择。INT 占 4 字节上限约 21 亿一般业务系统十年都用不完。如果预计数据量会超过 21 亿或者需要多库合并才考虑 BIGINT 或 UNIQUEIDENTIFIER。GUID 做主键有个问题聚簇索引的顺序性会很差频繁插入容易引起页分裂。实际里如果要用 GUID建议把它作为逻辑键物理主键仍然用自增列。字符串字段建议统一用 NVARCHAR。SQL Server 默认仓库排序规则通常支持中英文NVARCHAR 按 Unicode 字符存储中文、日文、特殊符号都稳。VARCHAR 更适合确定纯 ASCII 的场景比如内部编码、日志代码。要注意 NVARCHAR(64) 里的 64 是字符数不是字节数中文也按一个字符算所以存二十个汉字完全没问题。时间字段用 DATETIME2(3) 而不是老的 DATETIME。DATETIME 的精度只有 3.33 毫秒范围也从 1753 年才开始DATETIME2 精度可到 100 纳秒级别范围从公元 1 年到 9999 年。括号里的 3 表示毫秒精度对绝大多数业务足够了。存时间值时我建议存 UTC取 SYSUTCDATETIME()展示时再转本地时间。这样多台服务器、跨时区协作不会乱。布尔值用 BIT。BIT 只有 0 和 1语义清楚占用空间小。注意 BIT 本身就不需要 CHECK 约束因为取值范围已经被类型限定死了。如果后续要表达“启用、禁用、锁定”三种状态那就要改成 TINYINT同时加 CHECK 约束限定只能取 0、1、2。PasswordHash 用 NVARCHAR(256)。常见密码哈希算法比如 bcrypt、argon2id、PBKDF2输出长度从 60 到 128 字符左右。256 是给未来算法升级留的余量。这里强调一句永远不要存明文密码也不要用简单的 MD5、SHA1 做密码哈希那是拿用户账号安全开玩笑。2.2 主键、唯一、外键、默认这些约束怎么落约束是表结构里最值钱的部分它把“业务规则”直接写进了数据库应用层再怎么写代码都绕不过去。Users 和 Roles 用单列自增主键这个不多说。UserRoles 表用复合主键 (UserId, RoleId)一箭双雕主键自带唯一性天然防止同一个用户被重复分配同一个角色查询“用户有哪些角色”时这个索引也能直接用上。复合主键的列顺序有讲究UserId 放在第一列是因为绝大多数查询是从用户出发。如果后续“按角色查用户”高频再单独建 RoleId 的非聚集索引。唯一约束要落在业务上真正唯一的地方。UserName 必须唯一因为登录名重复会让登录流程没法处理。RoleCode 必须唯一因为程序里判断身份用的是角色编码而不是可以随便改的中文角色名。唯一约束在实际执行时也会生成唯一索引既能保证数据正确又能加速查询。外键方面UserRoles 的两个外键用 ON DELETE CASCADE 是合理的。删除用户关联记录自动清理删除角色关联记录也跟着删。否则用户已经删了用户角色表里还挂着一行指向不存在用户的记录后续查询全是坑。Users 表里的 CreatedBy 自引用外键不要级联用默认的 NO ACTION 更合适。员工删了它的创建人记录不应该引发任何自动删除操作。默认约束主要用于 INSERT 时不传值的情况。IsEnabled 默认 1CreatedAt 默认 SYSUTCDATETIME()。要特别注意默认约束只在 INSERT 不提供该列时生效它不会自动更新。像 UpdatedAt 这类“最后修改时间”字段要么应用层在 UPDATE 时主动赋值要么写一个 update 触发器。我更倾向应用层维护因为触发器是隐式逻辑排查问题时容易被忽略。2.3 容易被忽略的库级与脚本级规范T-SQL 脚本开头经常会看到SET ANSI_NULLS ON和SET QUOTED_IDENTIFIER ON这两行。这是 SQL Server 生成脚本时自动带的很多人直接忽略。但 QUOTED_IDENTIFIER 控制双引号是否被解释为标识符ANSI_NULLS 控制 NULL 参与比较的行为。如果关闭了这些开关某些查询结果会变得不可预测甚至导致索引视图无法创建。建议保留默认 ON。脚本里的GO是分批执行的标记。在 SSMS 中执行建表脚本时写 GO 能让错误定位更清楚。DDL 语句也可以包在事务里出错了直接回滚避免建到一半留下半成品表。判断表是否已经存在时用OBJECT_ID(Ndbo.Users, NU)比查系统视图更直观。这个细节在可重复执行的迁移脚本里很重要第一次执行是建表第二次执行要先判断并删掉旧表。排序规则是另一个容易被忽略的点。同一实例下不同库的排序规则可能不同比如一个库用中文排序规则另一个库用拉丁排序规则。两个库的表 JOIN 时可能报“无法解决排序规则冲突”。解决办法通常是统一库级排序规则或者在 JOIN 列上显式指定 COLLATE。我的建议是同一个业务系统尽量在库级别统一表级别不要随便覆盖。3. 建表实操与完整脚本下面进入正题。以下脚本按依赖顺序执行先角色表再用户表再关联表最后补索引。父表必须先创建外键才能引用。3.1 先建角色表SET NOCOUNT ON; IF OBJECT_ID(Ndbo.UserRoles, NU) IS NOT NULL DROP TABLE dbo.UserRoles; IF OBJECT_ID(Ndbo.Users, NU) IS NOT NULL DROP TABLE dbo.Users; IF OBJECT_ID(Ndbo.Roles, NU) IS NOT NULL DROP TABLE dbo.Roles; GO CREATE TABLE dbo.Roles ( RoleId INT NOT NULL IDENTITY(1,1), RoleCode NVARCHAR(50) NOT NULL, RoleName NVARCHAR(50) NOT NULL, Description NVARCHAR(256) NULL, IsEnabled BIT NOT NULL CONSTRAINT DF_Roles_IsEnabled DEFAULT (1), CreatedAt DATETIME2(3) NOT NULL CONSTRAINT DF_Roles_CreatedAt DEFAULT (SYSUTCDATETIME()), CONSTRAINT PK_Roles PRIMARY KEY CLUSTERED (RoleId), CONSTRAINT UK_Roles_RoleCode UNIQUE (RoleCode) ); GO先删除旧表再创建这个顺序是有讲究的。UserRoles 引用 Users 和 Roles所以清理时 UserRoles 必须第一个删Users 里有自引用外键以及被 UserRoles 引用所以要在 Roles 之后删。创建时反过来Roles 没有任何依赖最先建。RoleCode 是给程序判断用的比如管理员角色编码就固定为 admin。程序里判断身份用 RoleCode不轻易变RoleName 是给人看的改了不影响逻辑。3.2 再建用户表和自引用外键CREATE TABLE dbo.Users ( UserId INT NOT NULL IDENTITY(1,1), UserName NVARCHAR(64) NOT NULL, DisplayName NVARCHAR(64) NOT NULL, Email NVARCHAR(128) NULL, Mobile NVARCHAR(20) NULL, PasswordHash NVARCHAR(256) NOT NULL, IsEnabled BIT NOT NULL CONSTRAINT DF_Users_IsEnabled DEFAULT (1), CreatedAt DATETIME2(3) NOT NULL CONSTRAINT DF_Users_CreatedAt DEFAULT (SYSUTCDATETIME()), UpdatedAt DATETIME2(3) NULL, CreatedBy INT NULL, CONSTRAINT PK_Users PRIMARY KEY CLUSTERED (UserId), CONSTRAINT UK_Users_UserName UNIQUE (UserName) ); GO ALTER TABLE dbo.Users ADD CONSTRAINT FK_Users_CreatedBy FOREIGN KEY (CreatedBy) REFERENCES dbo.Users (UserId); GO用户名和显示名分开是有原因的。UserName 是登录账号一旦确定就不应该让用户随意改它可能被用于系统间对接。DisplayName 是界面上展示的名字可以随便改。中文系统里 DisplayName 用真实姓名UserName 用拼音或英文。Email 和 Mobile 允许为 NULL因为注册流程完全可能只提供用户名加密码。等后续需要“手机号找回密码”或“邮箱验证”时再考虑加唯一约束。注意这里有个 SQL Server 的特性普通 UNIQUE 约束允许多个 NULL 值。如果想要“邮箱有值时必须唯一”需要额外用过滤唯一索引去实现后面会专门讲。CreatedBy 自引用外键放在 ALTER TABLE 里单独加因为 CREATE TABLE 时 Users 本身还没建完无法自引用。这个字段允许为空表示部分初始化数据由系统直接写入没有具体创建人。自引用外键在初期可能用不上但它是审计功能的地基。3.3 最后建关联表CREATE TABLE dbo.UserRoles ( UserId INT NOT NULL, RoleId INT NOT NULL, AssignedAt DATETIME2(3) NOT NULL CONSTRAINT DF_UserRoles_AssignedAt DEFAULT (SYSUTCDATETIME()), CONSTRAINT PK_UserRoles PRIMARY KEY CLUSTERED (UserId, RoleId), CONSTRAINT FK_UserRoles_Users FOREIGN KEY (UserId) REFERENCES dbo.Users (UserId) ON DELETE CASCADE, CONSTRAINT FK_UserRoles_Roles FOREIGN KEY (RoleId) REFERENCES dbo.Roles (RoleId) ON DELETE CASCADE ); GO这表名字叫 UserRoles内容就是映射关系。复合主键 (UserId, RoleId) 让同一用户重复分配同一角色直接报错这个规则由数据库保证应用层写多少遍判断都不如这个约束稳固。AssignedAt 记录角色授予时间。将来做权限审计时能回答“张三的管理员角色是什么时候给的”这样的问题。如果业务需要“临时授权”可以考虑加 ExpireAt 字段但第一版没必要留给后续扩展。外键用 CASCADE 是在“用户删除后其角色关系自动清理”和“角色删除后用户关系自动清理”两个场景下最省心的方案。如果你用的是普通 NO ACTION删一个用户之前必须手动清理 UserRoles 表否则会报外键冲突。运维时这种多余的步骤越少越好。3.4 索引补全CREATE NONCLUSTERED INDEX IX_Users_CreatedAt ON dbo.Users (CreatedAt DESC) INCLUDE (UserName, DisplayName, IsEnabled); GO CREATE NONCLUSTERED INDEX IX_UserRoles_RoleId ON dbo.UserRoles (RoleId) INCLUDE (UserId); GO主键自带的聚簇索引已经覆盖了 UserId 和 UserRole 的 (UserId, RoleId) 组合查询。但每个索引都对应一类业务查询不是为了凑数量。IX_Users_CreatedAt 服务的是“最近注册用户列表”这类场景。CreatedAt DESC 排序配合 INCLUDE 把经常展示的列带进去查询时就不需要再回表拿数据。INCLUDE 列的加入要克制不是越多越好只放高频展示且长度可控的列。IX_UserRoles_RoleId 服务的是“某个角色下有哪些用户”的反向查询。复合主键把 UserId 放在前面按 RoleId 单独过滤时用不上所以这个非聚集索引正好补位。索引不是建完就万事大吉。数据量小的时候SQL Server 可能走全表扫描也不慢等数据量上来才发现缺索引ALTER TABLE 加索引时的锁开销比新建时要大得多。趁表还是空的把索引建好是一个成本极低、收益很大的操作。4. 测试数据与验证查询4.1 插入初始数据INSERT INTO dbo.Roles (RoleCode, RoleName, Description) VALUES (admin, 管理员, 拥有系统全部权限), (editor, 内容编辑, 负责内容发布与修改), (viewer, 访客, 只能查看公开数据); GO INSERT INTO dbo.Users (UserName, DisplayName, Email, PasswordHash) VALUES (zhangsan, 张三, zhangsanexample.com, $2y$10$xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx), (lisi, 李四, lisiexample.com, $2y$10$yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy); GO INSERT INTO dbo.UserRoles (UserId, RoleId) VALUES (1, 1), (2, 2), (2, 3); GO这条脚本演示三种角色、两个用户、三种分配关系。注意 INSERT 里不要指定 UserId、RoleId 这些自增列让 SQL Server 自己生成。PasswordHash 字段里放的是示例占位符实际项目里这必须是密码哈希函数生成的结果绝不能是明文。UserRoles 里的 (2, 2) 和 (2, 3) 演示了一个用户拥有多个角色。李四既是内容编辑又是访客这种场景在用 RoleId 单列设计的老表里根本表达不了在关联表里一行一条数据非常清爽。如果脚本执行报错优先检查是不是表还没建好或者外键引用的 UserId、RoleId 并不存在。4.2 JOIN 查询验证SELECT u.UserId, u.UserName, u.DisplayName, r.RoleCode, r.RoleName FROM dbo.Users AS u INNER JOIN dbo.UserRoles AS ur ON ur.UserId u.UserId INNER JOIN dbo.Roles AS r ON r.RoleId ur.RoleId ORDER BY u.UserId, r.RoleId;这个查询把三张表串起来了。INNER JOIN 只返回有角色的用户结果应该是张三带管理员角色李四带两个角色总共三行。如果有一些用户还没有分配角色又想一并查出来把第一个 INNER JOIN 改成 LEFT JOIN 即可角色字段会显示 NULL。这种查询在管理系统里几乎每天都会用到。建表的目的不是为了存数据是为了回答这类业务问题。4.3 常用管理查询做后台系统时下面几条 SQL 会反复出现给用户分配角色INSERT INTO dbo.UserRoles (UserId, RoleId) VALUES (1, 3);取消某个角色DELETE FROM dbo.UserRoles WHERE UserId 1 AND RoleId 3;查每个用户有几种角色SELECT u.UserId, u.UserName, COUNT(ur.RoleId) AS RoleCount FROM dbo.Users AS u LEFT JOIN dbo.UserRoles AS ur ON ur.UserId u.UserId GROUP BY u.UserId, u.UserName;这些操作都建立在三表结构上。建表完成后先把这些高频查询在空表上跑一遍确认语法没问题再上线接业务代码。5. 常见问题与排查速查5.1 外键依赖导致的对象顺序问题建表和删表最常见的报错就是“数据库对象名无效”或者外键找不到引用的表。原因几乎都是顺序不对。创建时必须先建父表Roles、Users再建子表UserRoles删除时顺序反过来必须先删子表再删父表。Users 表因为有自引用外键即使没有 UserRoles直接 DROP 也可能报错必须先删除或不用管它的自引用约束SQL Server 在 DROP 表时会自动删除相关的自引用外键吗实际不会自动删需要用 ALTER TABLE DROP CONSTRAINT 或先删依赖关系。所以在可重复执行脚本里明确IF OBJECT_ID(...) IS NOT NULL DROP TABLE的顺序非常关键。自引用外键的创建必须放在 CREATE TABLE 之后单独用 ALTER TABLE 添加。这一点很多新手第一次写会卡住。5.2 IDENTITY 跳号自增列跳号几乎是每个用 IDENTITY 的人都会遇到的“正常现象”。插入失败、事务回滚已经分配的自增值不会归还。比如连续插入三条第二条回滚了第三条拿到的是 3 而不是 2。这不是 bug是设计使然。自增列只保证唯一不保证连续。不要用DBCC CHECKIDENT (Ndbo.Users, RESEED, 0)去“修复”跳号。重置自增可能造出重复主键引发更大问题。业务如果需要任何一个连续编号应该单独设计业务编号生成规则而不是依赖主键自增。TRUNCATE TABLE 会重置自增但要求表不能被外键引用使用限制很多DELETE 不会重置。生产环境清空大表前必须搞清楚这些区别。5.3 排序规则和唯一性排序规则决定了 SQL Server 怎么比较字符串。常见的 Chinese_PRC_CI_AS 是“大小写不敏感”的也就是说 UserName 只差大小写时会被当成同一个值。比如 “ZhangSan” 和 “zhangsan” 在唯一约束下会直接冲突。如果业务确实要求登录名大小写敏感可以在列级别指定排序规则UserName NVARCHAR(64) COLLATE Chinese_PRC_CS_AS NOT NULL这个改动会影响字符串比较、索引排序以及查询写法改成要慎重。日常项目里我觉得反而不区分大小写更友好登录时统一转换为小写再匹配用户在注册时也不会被“ZhangSan”和“zhangsan”两个账号搞晕。5.4 BIT 字段与 NULL 的唯一性问题BIT 字段本身只占一个位空间很小但它的选择性非常差。如果一张千万级用户的表经常用WHERE IsEnabled 1给它单独建普通索引SQL Server 很可能也不走索引因为一个值就能筛出一大片数据。真正有效的做法是配合其他高选择性条件一起用或者使用过滤索引CREATE INDEX IX_Users_Enabled_UserName ON dbo.Users (UserName) WHERE IsEnabled 1;这个索引只包含启用用户的 UserName查询启用用户时体积小很多。另一个常见坑是UNIQUE 约束允许多个 NULL。想在 Email 列上做“有值必须唯一”普通唯一约束做不到需要过滤唯一索引CREATE UNIQUE INDEX UK_Users_Email_Key ON dbo.Users (Email) WHERE Email IS NOT NULL;这样多个用户都可以没有邮箱但只要填了邮箱就不能重复。5.5 问题排查速查表报错或现象常见原因解决思路违反了 PRIMARY KEY 约束重复插入 UserRoles 同一条关系插入前查重或忽略主键冲突INSERT 语句与 FOREIGN KEY 约束冲突插入了不存在的 UserId 或 RoleId检查数据是否先入库无法删除表因为它被外键引用删除顺序不对或外键是 NO ACTION先删子表记录再删父表对象名无效当前数据库不对或对象创建顺序不对USE 目标库先建父表无法解决排序规则冲突多库多列排序规则不一致库级统一或显式 COLLATEIDENTITY 编号不连续插入回滚消耗了自增值属正常行为不要强行重置6. 实操心得与扩展建议6.1 我建表前会做的一步先别急着打开编辑器写 CREATE TABLE我会先列三张清单。第一张是查询清单把系统将来会出现的高频查询先列出来登录查用户、查用户角色列表、按角色查用户、按创建时间分页。第二张是约束清单把业务上“必须唯一”“必须非空”“必须级联删除”的规则列出来。第三张才是字段清单。这个习惯帮我避过很多坑。比如复合主键选 (UserId, RoleId) 而不是 (RoleId, UserId)就是因为查询清单里“查用户角色”排在最前面。如果没有这份清单建表时很容易凭感觉选错索引列顺序等业务上线再发现就会非常被动。6.2 这套表结构怎么长成完整权限体系现在这套三张表的模型是权限系统的底座接下来扩展方向非常明确。需要细粒度权限时加一张 Permission 表权限点表再加一张 RolePermission 关联表。角色不再直接对应“是否能用某个按钮”而是对应一串权限点。用户查权限时先通过 UserRoles 拿到角色列表再通过 RolePermissions 拿到权限点集合。账号安全方面可以给 Users 表加 PasswordVersion、FailedLoginCount、LockedUntil。PasswordVersion 用来记录当前密码是第几代哈希算出来的密码算法升级时不会误判老用户的登录状态。FailedLoginCount 和 LockedUntil 配合登录锁定策略。多租户场景下核心表可以加 TenantId。但注意加租户字段会影响所有业务表包括查询条件、索引设计必须提前统一规划而不是某一张表单独加。软删除也是常见需求加 DeletedAt默认 NULL配合过滤索引查询活跃数据。这套核心表设计好之后这些扩展都只是“加表”“加字段”的常规操作不需要推翻重来。6.3 表结构脚本一定要纳入版本管理建表脚本不是一次性工具它是项目的资产必须放进代码仓库长期维护。建议按版本号组织迁移脚本比如一个数据库初始化目录下放 001_init_users_roles.sql、002_add_user_security_fields.sql。每个脚本要么不执行要么完整执行用迁移工具记录已应用版本。不要在 SSMS 里手工改生产表结构改完文档里没有记录三个月后没人知道这张表为什么会多出一列。索引变更要放到迁移脚本里写清楚。大表加索引时尽量使用 ONLINE ON 选项减少对线上写入的阻塞。执行前在准生产环境完整跑一遍脚本确认对象顺序、数据迁移、回滚方案都没有问题再上线。这套用户、角色、关联表的结构我在几个内容类后台里验证过一开始觉得多一张表麻烦后来反而成了习惯。数据库设计没有银弹建表规范的目的也不是炫技而是让三个月后的你、接手项目的同事一眼能看出这张表到底在表达什么。最后分享一个小技巧每次创建或修改表把对应的高频查询 SQL 一起附在说明文档里将来排查慢 SQL 时你会感谢当时的自己。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑