数据库表和字段的命名方案






3.89/5 (23投票s)
本文介绍了一种数据库表和字段的命名方案。
为什么会出现问题?
数据库开发人员历来使用一种略显神秘的系统来命名数据库表和字段。最初是由于数据库管理系统(DBMS)的限制,这些命名方案(或缺乏命名方案)已成为惯例和传统。然而,随着数据库应用程序变得越来越复杂,表越来越多,开发人员团队也越来越庞大,并且开发人员不断更迭,为数据库对象实施健壮且规范的命名方案变得越来越重要。当您采用对象关系映射(ORM)技术或自动代码生成时,定义良好的命名方案变得更加重要。
本文介绍了一种我已经使用了多年并成功应用于多个商业应用程序的方案。
也许数据库命名中最常见的做法是使用一种大小写(大写或小写),单词之间用下划线分隔,并且大量缩写。固定大小写的名称已成为标准,因为数据库系统在表和字段名称中传统上不区分大小写(或要求固定大小写)。这也是学术界的一种遗留,在学术界,单一大小写的使用不仅在数据库设计中很常见,而且在编程语言中也很常见(例如,C 语言传统上以小写教授,FORTRAN 以大写教授)。用下划线分隔单词是固定大小写名称的自然结果,在学术界也很常见。大量使用缩写是 dBase 和其他系统中对表和字段名称长度有严格限制的时代遗留下来的。许多在旧系统中受过教育的数据库设计人员仍然保留了这种缩写名称的使用,这是一种严重的习得性无助的心理学原理。
大多数数据库书籍仍然遵循这些原则。因此,即使是最近关于 SQL Server 2005 的书籍,也使用了诸如以下示例的表和字段:
表:customers
字段:
cust_id
first_name
last_name
addr1
在一个类似命名的 orders
表上进行 join
的 SQL 会是这样的:
SELECT customers.*, orders.* FROM customers INNER JOIN orders ON _
customers.cust_id = orders.cust_id
这种命名方案存在几个问题:
- 它不必要地难以被人类阅读,并导致凌乱的 SQL 语句。
join
结果中有两个名为cust_id
的列——一个来自customers
,一个来自orders
。虽然这不是一个不可逾越的问题,但最好通过适当的命名方案来避免。-
它不适合自动代码生成程序或对象关系映射(ORM)技术,如 Hibernate。
我开发数据库系统已经十二年多了。我的工作包括自定义应用程序、基于 Web 的数据库和“盒装”应用程序。我曾担任数据库的首席设计师,不得不承受自己早期的错误。我被迫使用其他人开发的数据库设计——有些好,有些坏。我还深入研究了竞争产品的数据库设计细节,以便将它们的数据导入到我公司的产品中。最后,我开发了一套广泛的自动代码生成工具,使我能够快速开发和修改健壮的数据库应用程序。
从这些经验中,我总结出了一套数据库命名系统,我已经在多个项目上使用了至少五年,并发现它非常令人满意。即使您不想采用此命名方案的每个功能,我也相信您会在本文中找到一些有用的命名技巧。
我的数据库命名方案有一些基本属性:
- 我使用混合大小写名称。
- 我**不**使用下划线。混合大小写使得下划线变得不必要,一旦你习惯了省略它们,它们看起来很丑,而且不输入它们更容易。
- 我给表名添加前缀以表示三种不同类型的表。
- 我给字段名添加一个三字母代码前缀以指示哪个表拥有该字段。
- 我使用 GUID 字符串作为主键。
我将在下面解释每个原因。
表命名
在大多数数据库中,有三种类型的表:
- 数据表 - 当然,数据库中的所有表都包含数据,但我使用此术语来指代实际存储我们最初构建数据库所要存储的数据的表,例如客户、订单或产品。例如,一个
customers
表将包含有关客户的信息,其中包含name
、address
和phone
等字段。Customers
是一个数据表 - 与其他表类型不同。 - 链接表 - 链接表只用于连接来自两个不同数据表的两个关键字段,以形成多对多关系。例如,您可以在
vendors
表和products
表之间建立多对多关系,因为每个供应商可以支持多个产品,并且每个产品可以通过多个供应商销售。这将需要第三个表来链接供应商和产品。 - 选择列表表 - 通常会有一些表包含数据表中字段的选择列表。例如,您的
vendors
表中可能有一个状态字段。供应商状态的值可以从另一个表中选择。我将这些类型的表称为“选择列表”表,因为它们允许用户从列表中进行选择。
在我的命名方案中,我喜欢给每个表名添加三个前缀之一,以指示表的类型。我使用以下前缀:
- 数据表 - 我使用前缀
tbl
。因此,记住我的混合大小写和无下划线规则,您可以有以下数据表: tblCustomer
tblOrder
tblOrderEntry
tblVendor
tblProduct
- 链接表 - 我使用前缀
link
。因此,要将供应商与产品链接起来,您将有一个表linkVendorProduct
。 - 选择列表表 - 我使用前缀
pltbl
。因此,对于供应商状态,您将有一个名为pltblVendorStatus
的表。如果您还有一个客户状态表,您可以有pltblCustomerStatus
。
优点
我发现这个表命名系统有几个优点。
- 显然,从表名可以很容易地判断表包含什么类型的数据。
- 我见过的每个数据库应用程序(例如 Microsoft Access 或 SQL Server)都按字母顺序列出您的表。使用此前缀方案会导致您的表在按字母顺序显示时按类型分组。
- 如果您开发任何类型的自动代码生成工具,可以很容易地从表名以编程方式确定表包含什么类型的数据。您只需检查前缀。
单数/复数名称
请注意,在我上面的数据表中,所有表名都是单数,即 tblCustomer
而不是 tblCustomers
。无论您喜欢单数还是复数名称,都应该始终保持一致使用其中之一。我更喜欢单数,因为它对我来说看起来更简洁。
其他表类型
在您的开发中,您可能会发现包含其他表类型(例如):
- 日志表
- 错误表
- 系统表
每个都可以有自己的前缀。
字段命名
传统数据库命名方案的一个大问题是,在两个最终会连接在一起的不同表中,常见有相同的字段名。虽然这个问题并非不可克服,但它很不方便,应该避免,原因有二:
- 当您将包含相同名称字段的表连接在一起时,您需要用一堆
AS
子句来使结果字段名唯一,从而使您的 SQL 语句变得混乱。这不仅会创建凌乱的 SQL,而且还会特别复杂化任何类型的自动代码生成工具或 ORM 技术的使用。 - 这使得解释某些错误消息变得更加困难。例如,假设您正在使用传统命名并收到错误“
Field cust_id not found.
”如果您有多个表包含cust_id
字段,您可能无法立即知道是哪个表生成了错误。
我设计的字段命名方案是,任何两个数据表都不会有相同的字段名(这对链接表和选择列表表来说不那么重要,因为它们很少互相连接)。对于每个表名,我都会想出一个对每个数据表都唯一的三字符前缀。例如,tblCustomer
的前缀可以是 cus
。tblOrder
的前缀可以是 ord
。tblProduct
的前缀可以是 prd
。每个字段名都以这个前缀开头。
tblCustomer
cusCustomerID
cusCustomerName
tblOrder
ordOrderID
ordCustomerID
ordOrderDate
现在,当我对这两个表按客户 ID 进行连接时,我不会有重复的字段名,因为客户表中的客户 ID 字段与订单表中的客户 ID 字段名称不同。
字段名中绝不能使用空格
命名字段时,您**绝不能**做的一件事就是在名称中包含空格——即使您的 DBMS(例如 Microsoft Access)支持它。在表名中包含空格会导致极其丑陋且难以阅读的 SQL 语句。这很讽刺,因为 DBMS 供应商允许在字段名中包含空格,以便表本身对新手程序员和数据库开发人员看起来更友好。当图形查询构建工具在后台生成 SQL 时,它工作得很好。但是一旦您的数据库应用程序变得更加复杂并且您对 SQL 更加熟练,您就会后悔这些“友好名称”。
常用字段的命名(Notes, Status, Name, Date 等)
在开发数据库时,您可能会注意到许多表会有一些具有共同用途的字段。这些通常是名为 notes
、status
、name
或某些变体的字段。例如,您的 customer
表可能有一个用于客户备注的 notes
字段,您的 order
表可能有一个用于订单备注的 notes
字段。使用我的前缀系统,您可能会倾向于这样命名它们:
cusNotes
ordNotes
您可以看到,在这种情况下,使用前缀来指示所有者表在 join
操作中区分订单备注和客户备注已经很方便了。
但是,我推荐这种方法:
cusCustomerNotes
ordOrderNotes
这种差异很微妙,但如果您的数据表映射到程序代码中的对象,则非常重要。在我的程序代码中,我创建包装数据表中数据的对象。但我不会将字段名中的前缀带入程序代码。这些前缀用于数据库命名。我使用自己的代码命名系统进行编码。在第一种情况下,我的 customer
对象将有一个 Notes
字段,我的 order
对象也将有一个 Notes
字段。虽然从编程角度来看,这没什么问题,但根据经验我发现它不方便在源代码中进行项目范围的搜索。假设我想查找所有客户备注的代码引用。如果我只搜索术语 Notes
,搜索结果将包括 order notes
和代码中任何其他类型的“notes
”的引用。但是,如果我使用更具描述性的命名方案,我就可以搜索 CustomerNotes
。我的搜索结果将仅限于 customer
对象的备注。
这听起来可能微不足道,但随着您的项目扩展到包含数百个代码文件,命名对象以方便搜索变得越来越重要,您会因此而感谢付出的努力。
链接表和选择列表表的字段名
请记住,对于多对多关系,您将数据表与链接表连接,因此您不希望链接表与数据表具有相同的字段名。这意味着您的表 linkVendorProduct
**不**应具有字段 venVendorID
和 prdProductID
,因为这些字段名已存在于数据表中。我喜欢简单地在链接表字段前加上 link
,这样我的 linkVendorProduct
表将具有以下字段:
linkVendorID
linkProductID
同样,我喜欢在选择列表表字段前加上 pl
。同样,您不希望拥有与数据表中存在的相同字段名。表 plCustomerStatus
将具有以下字段:
plCustomerStatusID
plCustomerStatusValue
关于匈牙利命名法
一些数据库设计人员使用一种匈牙利命名法——在字段名前缀一个值以指示字段类型,例如 nCustomerID
。虽然我在程序代码中对匈牙利命名法深信不疑(但那是另一篇文章),但它对数据库字段名来说是不好的,原因有二:
- 字段名应与类型无关。换句话说,您应该能够更改字段中的数据类型,而不会以任何方式影响您的 SQL 语句或程序代码。
- 在连接在一起的两个表中,您仍然可以拥有重复的字段名。
我们的 SQL 语句再探
现在让我们看看使用新的命名方案后,我们的 customer
-order join
会是什么样子。
SELECT tblCustomer.*, tblOrder.* FROM tblOrder INNER JOIN tblCustomer _
ON tblOrder.ordCustomerKey = tblCustomer.cusCutomerKey
现在,即使两个表都有 notes
字段,结果集也保证所有名称都是唯一的。
主键
传统上,数据库中的主键是整数。这有一些很好的理由:整数占用空间最小,排序速度快,并且大多数数据库系统允许键字段递增,从而使整数连续且唯一。Microsoft Access 默认为自动编号整数作为主键,因此通过该程序入门的数据库新手习惯于使用整数作为主键。
然而,我更喜欢一种替代方案——**全局唯一标识符**(GUID,发音为 goo-id),或者更具体地说,是一个 GUID 字符串。GUID 可以由大多数操作系统甚至某些数据库系统(如 SQL Server 2005,它支持 GUID 作为字段类型)生成。澄清一下,GUID 是 128 位的值,如果它们是在具有网卡的计算机上创建的,则保证在全世界范围内是唯一的。如果它们是在没有网卡的计算机上创建的,则它们不唯一的可能性很小,但由于它们包含时间戳和随机数,如果不是合理不可能,则重复的 GUID 实际上是不可能的。
使用 GUID 作为主键有两个优点:
1. 合并数据库
第一个优点不适用于大多数项目,但当它适用时,GUID 远优于整数。这涉及到合并数据库。
假设您销售一种使用关系数据库存储的商业联系人管理产品。您使用整数作为主键,并且整数会随每个新记录自动递增。每个使用您产品的人都将有一个 customers
表,其中包含从 1 到 n 的客户编号。如果出现您想要合并两个数据库的情况,这将非常困难。其中一个数据库必须重置所有主键。我处理过这个问题。虽然可以做到,但最好避免。
现在考虑同样的情况,但是每个主键都是 GUID 而不是整数。每个使用您的联系人管理器的人都将拥有完全唯一的主键值 ID。现在,合并数据只是将一个数据库中的记录附加到另一个数据库中的问题。不需要更改任何键值,并且表之间的所有关系都保持不变。
我开发了许多商业数据库应用程序。使用 GUIDs 使客户可以轻松地合并来自不同数据库的数据——这种情况确实发生——尤其是在从一个数据库版本升级到另一个数据库版本时。
对于一次性数据库项目,例如程序供应商的自定义库存管理应用程序,这种情况可能不常见。但如果将来发生公司合并怎么办?任何以 GUID 作为主键的数据库都更容易与其他系统集成,前提是其他系统也支持 GUID 作为主键(在同一个数据库中混合使用 GUID 和整数作为主键是不可取的)。
2. 在添加记录之前知道主键值
即使第一个情况不适用于您,这个肯定会适用。
这是一个常见场景:您使用整数作为主键,并且整数在添加记录时由 DBMS 分配。现在您想向 customers
表添加一个新客户,并向 orders
表添加一个新订单,其中包含一个指向客户 ID 的外键。涉及的步骤包括:
- 将客户数据写入数据库。
- 找到新的客户记录,并找出 DBMS 分配给它的整数键值。读取该值。
- 在
order
对象中设置客户 ID 值,并将订单写入数据库。
问题出在第2步。使用 GUID,我们可以消除这种读取操作,因为我们在将客户写入数据库之前将客户 ID 值(一个 GUID)分配给客户。现在过程是:
- 生成一个 GUID 并将其分配给新客户。将客户数据写入数据库。
- 将 GUID 作为
order
表中的客户 ID 外键,并将订单写入数据库。
我们已经消除了查找和读取步骤,以找出 DBMS 分配给客户的 ID。
带前缀的 GUID 字符串
在任何数据库产品的开发期间,当数据库表结构不断变化,代码与数据库并非总是同步时,使用 GUID 可以提供帮助。
考虑我们的链接表,它以多对多关系将供应商链接到产品。如果您使用整数作为主键,打开该表将只显示两列整数——一列是供应商 ID,一列是产品 ID——或者您会这样认为!
正如我所说,我喜欢使用 GUID 字符串而不是实际的 GUID,所以我的主键字段实际上是文本类型。区别在于 GUID 实际上是一个二进制值,而字符串实际上是二进制 GUID 的字符串表示。GUID 字符串看起来像这样:
005bf43a-01ca-454a-996c-2c73621ceeb6
回想一下,在我们的数据表命名约定中,我喜欢在字段名前缀一个三字符值来指示表,例如 tblCustomers
的“cus
”。我也喜欢在我的 GUID 字符串前缀相同的三个字符代码。因此,我的客户主键将如下所示:
CUS:005bf43a-01ca-454a-996c-2c73621ceeb6
(根据我自己的惯例,我喜欢使用大写字母作为前缀,并用分号将前缀与 GUID 本身分隔开)。
这为什么重要?因为现在无论何时我看到一个主键值,我都可以立即判断它来自哪个数据表。这对于整数来说是不可能的。现在,当我打开我的供应商-产品链接表时,我可以轻松检查以确保供应商列中的所有外键都以 VEN
开头,并且产品列中的所有外键都以 PRD
开头。这在开发和调试我所从事的各种数据库应用程序中被证明非常有用。(诚然,在表之间设置适当的参照完整性可以消除这个问题,但在表结构不断变化的开发期间,放宽参照完整性并不罕见)。
使用 GUID 字符串的优点是您可以为其标记其他诊断值。例如,如果它是在调试模式下创建的,您可以在字符串后附加一个 D
,如果记录是在生产模式下创建的,则附加一个 P
。这将使您能够轻松确保调试数据不会与生产数据混淆——或者至少知道哪个是哪个。或者,如果数据库记录是通过从另一个数据库或产品以前的版本导入创建的,您可以附加一个 I
。
浏览器地址栏中的 Web 数据库键值
如果您曾经为 Web 编写过数据库应用程序,那么您可能需要避免的一件事是使用连续整数作为主键值。这是因为大多数数据库驱动的网站在某些时候会在 Web 浏览器的地址栏中显示键字段。任何有进取心的黑客都可以简单地增加或减少键值,并可能看到他们不应该看到的数据。通过使用 GUID 作为主键,黑客更难通过这种方法渗透网站的内部工作。它仍然不是完全安全的,但它比整数方法有所改进。
GUID 永远不会用尽
GUIDs 作为主键比整数具有一个主要优势,那就是 GUIDs 的供应是无限的。当然,可用整数数量的限制不太可能在您的产品或供应商表中造成问题——很少有公司拥有超过 400 万种产品(也许 Amazon.com?)。然而,如果您使用数据库表来记录网页浏览量,那么整数数量的限制五年后很可能成为问题。
GUID 的缺点
使用 GUID 作为主键确实有一些缺点,因此它们可能不适用于所有应用程序。
GUID 长度 - 请注意,GUID 字符串的长度始终为 36 个字符。再加上前缀的三个字符和一个冒号,这意味着您的主键字段是一个长度为 40 的字符串。如果还需要为其他标签添加额外的空间,那么 GUID 的缺点就会显而易见——**性能!**
理想情况下,主键应该小而排序快,以提高性能。这就是整数如此吸引人的原因。然而,GUID 的优势可能会抵消性能损失。
可读性 - GUID 看起来很丑陋。虽然整数杂乱无章,不能告诉您太多关于对象的信息,但在开发过程中,告诉同事客户编号 3452 有问题,要比告诉他客户 CUS:005bf43a-01ca-454a-996c-2c73621ceeb6
有问题容易得多。在我的开发中,当试图找出应用程序为什么找不到客户 CUS:005bf43a-01ca-454a-996c-2c73621ceeb6
或者试图在数据库中找到相应的外国键值时,GUID 被证明有点不方便。虽然可以做到,但长时间查看列和行或 GUID 可能会让您头痛欲裂!
在代码中创建 GUID
如果您决定使用 GUID 字符串作为主键,您会想知道如何在代码中生成它们。
在 .NET 中创建 GUID
在 .NET 中创建 GUID
非常简单。这是一个 C# 示例。
System.Guid guid = System.Guid.NewGuid();
string s = guid.ToString();
使用 MFC 在 C++ 中创建 GUID
使用 MFC 在 C++ 中创建 GUID
要复杂一些。这是我支持 ANSI 和 Unicode 的代码:
CString GetGuidString()
{
GUID guid;
CoCreateGuid(&guid);
#ifdef _UNICODE
WORD * str;
UuidToString((UUID*)&guid, &str);
CString s((LPTSTR)str);
RpcStringFree(&str);
return s;
#else
BYTE * str;
UuidToString((UUID*)&guid, &str);
CString s((LPTSTR)str);
RpcStringFree(&str);
return s;
#endif
}
对于 C++,您需要链接到 RPCRT4.LIB,否则 Rpc
函数将出现链接器错误。
抱歉,VBer 们。这些是我唯一使用的两种语言。
查询和存储过程的命名
在您的 DBMS 中,为存储过程(在 Microsoft Access 中称为查询)使用前缀也很有帮助。我喜欢使用一个指示存储过程类型的前缀:
i
用于INSERT
命令s
用于SELECT
查询d
用于DELETE
命令u
用于UPDATE
命令
这样的命名方案有几个优点。首先,您可以快速通过名称判断存储过程的类型。其次,由于大多数 DBMS 按字母顺序列出您的存储过程,它们将按类型分组列出。
任何我实际没有使用的存储过程,我喜欢在其前缀加上一个 x
。当存储过程按字母顺序显示时,我没有使用的那些会显示在列表的底部。
为存储过程使用某种独特的命名方案,不仅在您的 DBMS 中工作时有帮助,而且在您查看程序代码时,还可以快速确定您正在“打开”的是存储过程而不是表。同样,这在程序代码中进行全局搜索时特别有帮助。假设在我的 .NET 应用程序中,我想找到所有执行 DELETE
存储过程的程序代码。因为我已经将所有名称都前缀为 d
,所以我可以搜索:
new OleDbCommand("d
ORM 技术和自动代码生成的考虑
通读数据库应用程序的程序代码,您会发现一件事——无论在哪里进行数据库访问,您都会看到几乎相同的代码逻辑一遍又一遍。数据库编程非常重复,而且可能非常乏味。如果您还没有考虑为您的数据库应用程序使用 ORM 或自动代码生成技术,我认为这非常值得一试。
ORM 技术允许您轻松地将程序代码中的对象映射到数据库表。ORM 包处理大部分数据库活动,包括读取和写入记录,并自动用数据库中的数据填充您的对象。我使用过 NHibernate——流行的 Hibernate 产品的 .NET 版本,结果喜忧参半。它简化了 .NET 数据库编程的许多细节(我发现 ADO.NET 非常冗长),但当它无法正常工作时,问题可能很难诊断。
自动代码生成更进一步。自动代码生成器是一种基于数据库设计实际编写程序代码(例如对象类)的软件程序。我开发自动代码生成工具多年,这些程序已经变得相当复杂。您可以在我的 CodeProject 文章 C++ 面向对象数据库生成器中看到我的早期工作版本。虽然它们的开发工作量不小,但一旦拥有它们,节省的时间将非常可观。它们还能生成更健壮的软件。一旦您的生成器能够生成无 bug 的代码,它将永远是无 bug 的。如果发现 bug,您可以轻松修复代码生成器并重新生成所有类。向对象添加功能也很容易。
代码生成工具的使用与精心设计的数据库命名约定密不可分。您的代码生成工具可以使用表和字段名称来确定要生成哪种类型的代码。例如,通过使用与数据表不同的前缀命名选择列表表,您的代码生成器可以知道哪些表是什么,并为不同的类对象编写代码。