1.概述:
在我们的数据库设计中,数据库的主键是必不可少的,主键的设计对整个数据库的设计影响很大.我就对自动增量字段与Guid字段的性能作一下对比,欢迎大家讨论.
2.简介:
1.自增量字段
自增量字段每次都会按顺序递增,可以保证在一个表里的主键不重复。除非超出了自增字段类型的最大值并从头递增,但这几乎不可能。使用自增量字段来做主键是非常简单的,一般只需在建表时声明自增属性即可。
自增量的值都是需要在系统中维护一个全局的数据值,每次插入数据时即对此次值进行增量取值。当在当量产生唯一标识的并发环境中,每次的增量取值都必须最此全局值加锁解锁以保证增量的唯一性。这可能是一个并发的瓶颈,会牵扯一些性能问题。
在数据库迁移或者导入数据的时候自增量字段有可能会出现重复,这无疑是一场恶梦(本人已经深受其害).
如果要搞分布式数据库的话,这自增量字段就有问题了。因为,在分布式数据库中,不同数据库的同名的表可能需要进行同步复制。一个数据库表的自增量值,就很可能与另一数据库相同表的自增量值重复了。
2.uniqueidentifier(Guid)字段
在MS Sql 数据库中可以在建立表结构是指定字段类型为uniqueidentifier,并且其默认值可以使用NewID()来生成唯一的Guid(全局唯一标识 符).使用NewID生成的比较随机,如果是SQL 2005可以使用NewSequentialid()来顺序生成,在此为了兼顾使用SQL 2000使用了NewID().
Guid:指在一台机器上生成的数字,它保证对在同一时空中的所有机器都是唯一的,其算法是通过以太网卡地址、纳秒级时间、芯片ID码和许多可能的数字生成。其格式为:04755396-9A29-4B8C-A38D-00042C1B9028.
Guid的优点就是生成的id比较唯一,不管是导出数据还是做分步开发都不会出现问题.然而它生成的id比较长,占用的数据库空间也比较多,随着外存价格的下降,这个也无需考虑.另外Guid不便于记忆,在这方面不如自动增量字段,在作调试程序的时候不太方便。
3.测试:
1.测试环境
操作系统:windows server 2003 R2 Enterprise Edition Service Pack 2
数据库:MS SQL 2005
CPU:Intel(R) Pentium(R) 4 CPU 3.40GHz
内存:DDRⅡ 667 1G
硬盘:WD 80G
2.数据库脚本
测试代码
3.数据库的插入测试
测试1
数据库量为:100条
运行结果
测试2
数据库量为:10000条
运行结果
数据库量为:100000条
运行结果
测试4
数据库量为:500000条
运行结果
4.将数据读到DataSet中
测试1
读取数据量:100
运行结果
测试2
读取数据量:10000
运行结果
测试3
读取数据量:100000
运行结果
测试4
读取数据量:500000
运行结果
4.记录总数测试
测试结果
5.指定条件查询测试
查询数据库中第300000条记录,数量记录量为610300.
4.总结:
使用Guid作主键速度并不是很慢,它反而要比使用自动增长型的增量速度还要快.
5.参考:
http://www.cnblogs.com
http://www.cnblogs.com/leadzen/archive/2008/05/10/1191010.html
主键是能够惟一地定义一行数据的一列或多列。主键中的列值不能设置为null值。主键为数据库引擎提供了一种获取数据库表中某个特定行的方法。主键还用于保证引用的完整性。在处理非连接数据时,如果多个用户同时插入数据,则必须确保不会出现重复的主键。
智能键是一种基于商业数据表示的键。例如,SKU(Stock Keeping Unit,常用保存单元)就是智能键的一个例子,它定义为一个包含10个字符的字段(在数据库中定义为CHAR(10))。该SKU可能包含以下信息:前四个字符保存供应商代号,随后的三个字符保存产品类型代号,最后三个字符保存一个序列号。
常规键(natural primary key)由商业数据中现有的单个或多个列构成,用于惟一地标识记录。例如,某个现有的商业过程可能要使用社会保险号来标识医院的患者。
尽管智能键和常规键互不相同,但它们都由使用商业相关数据的列构成,并且用户一般都可以查看它们。本书以后将这些键统一称为智能键。
代理主键的键值是由系统生成的,这些值与行中的商业数据没有关系,因此又称为非智能键(dumb key)。本书以后将这些键统一称为代理键(surrogate key)。自动增值的列就是这种键的一个例子,在添加新的行时,该列的值将相应地设置为1,2,3…,依此类推。在Microsoft SQL Server中,自动增值的列称为Identity列。本书以后将这种键统一称为Identity键(identity key)。GUID(globally unique identifier,全局惟一标识符)是另一个由系统生成键值的例子,它通过使用取值算法来生成惟一的键值。本书以后将这种键统一称为GUID键。
专家推荐使用哪一种主键类型呢?不同的专家有着各自的见解。大家在做出选择之前,应深入地理解各种方法的主要优缺点。
图5.3显示了智能键和代理键的一种实现示例。该示例包含三个表:第一个表用于保存作者数据,第二个表用于保存书籍数据,第三个表是一个多对多的联接表(join table),因为一个作者能够编写多本书籍,一本书籍也能够由多个作者共同编写。
注意,代理主键的实现在TblAuthor和TblBook表中额外包含了一个Id列,因为代理主键的键值是由系统生成的,与行数据无关。用户应不能看见代理主键。下面,我们将分析智能键与代理主键之间在实现上的差异。
数据大小 数据大小本身并不十分重要,重要的是在数据库和客户之间传输数据时所占用的带宽。代理主键的实现在每个主表中都额外增加了一列。这将显著地增大数据量的大小。如果代理主键是一个GUID列,则增加的列将使每行增加16个字节。如果增加的是一个自动增值的列,则因该列引起的数据增量取决于所选用的数据类型(int型增加4个字节,long型增加8个字节)。由于联接表通常包含数量庞大的行记录,因此在分析数据库大小的总体差异时,一定要考虑到智能键与代理主键在数据增量大小方面的差异。此外,主键通过创建一个惟一索引来确保自身的惟一性,因此还要考虑到这方面的数据增量。表5.1列出了每种主键类型在数据增量方面的比较结果。
智能键 代理键
描述 |
智能键 |
Identity键(int型) |
GUID键 |
1000条作者记录 |
9个字节/SSN = 9,000个字节 |
4个字节/int = 4,000个字节 |
16个字节/GUID = 16,000个字节 |
3000条书籍记录 |
10个字节/ISBN = 30,000个字节 |
4个字节/int = 12,000个字节 |
16个字节/GUID = 48,000个字节 |
10,000条作者/书籍记录 |
19个字节/键= 190,000个字节 |
8个字节/键 = 80,000个字节 |
32个字节/键 = 320,000个字节 |
小计 |
229,000个字节 |
96,000个字节 |
384,000个字节 |
索引 |
229,000个字节 |
96,000个字节 + 9000个SSN + 30,000个ISBN = 135,000个字节 |
384,000个字节 + 9000个SSN + 30,000个ISBN = 423,000个字节 |
总数 |
458,000个字节 |
231,000个字节 |
807,000个字节 |
显然,Identity键是这一比较回合的赢家,但要记住的是,int型的最大值是231-1=2 147 483 647。对于大多数应用程序而言,该值已经足够了,但是有些应用程序需要使用long数据类型来保存更多的记录。注意,计算不同类型索引大小的方法是互不相同的。代理键的实现仍需要在SSN列和ISBN列上提供一个惟一索引,以确保这些列的惟一性。
键的可见性 代理键对用户是不可见的,但智能键是可见的,并且用户知道如何使用智能键。客户应用程序可以隐藏代理键,但数据库工具却不能隐藏它们。这就要求使用数据库工具的人员必须理解如何使用代理键。因此,智能键是这一比较回合的赢家。
键的易修改性 主键是难以进行修改的,因为如果对主键进行修改,则修改结果必然对子表造成影响。这时就应该使用代理键取代智能键。为什么呢?因为代理键并不显示给用户,所以不必修改它们;而智能键包含用户可以看到的商业数据,因此必须允许该数据能够进行修改。
int型代理键也需要进行修改,以确保惟一性(正如前文所述),但GUID型代理键从不需要进行修改。这正是我为什么喜欢使用GUID型代理键的主要原因。
联接个数 在某些情况中,可以使用智能键来减少联接的个数。例如,如果想运行一个显示每个作者所著书籍的报表,并且该报表只包含作者的SSN字段和书籍的ISBN字段,则在实现智能主键时,只需查询TblAuthorBook联接表即可。在实现代理主键时,必须联合查询TblAuthor表、TblAuthorBook表和TblBook表才能获得该信息。因此,智能键是这一比较回合的赢家,但实践中很少只查询这两列信息而不查询其他的信息,诸如作者的姓名和书名。
SQL复杂性 智能键的实现通常使用多个列来确保惟一性。SQL查询会因为使用这样的复合智能键而变得错综复杂。正如前文所述,尽管可以联合更多的代理键,但代理键更易于使用,因为它们不使用复合键(联接表除外)。对比前面两种代理键类型,Identity键在实现上比GUID键更易于编写查询,但是一旦掌握了GUID数据类型(稍后将会予以讨论)的使用特点,就会发现,GUID键相较于Identity键,并不是特别难用的。
在非连接时确保惟一性 在 非连接环境中,几乎不能保证智能键在用户输入数据时的惟一性。问题是在向数据库服务器添加新行时,有人可能输入匹配信息,从而导致冲突。有人可能会辩解 说,使用代理键就可以解决该问题,但不要忘记,大家仍可以在诸如社会保险号或车辆标识号等字段上创建惟一索引,以在添加重复信息时能抛出一个异常。
在使用int型代理键时,管理主键列编号的窍门是将DataSet非连接对象中的AutoIncrement属性设置为true,AutoIncrementStep(增量)设置为-1(负1),AutoIncrementSeed(起始值)设置为-1,这样,在添加新行时,编号将从-1开始,每新增一行,编号就减1。负值认为是非连接占位符,因此不会与服务器的Identity列设置产生冲突,因为服务器只会分配正值编号。下面的SQL命令首先插入一行数据,然后立即查询该插入行。该命令所返回的信息用于将占位符(负值键)更新为数据库所创建的值。
SQL INSERT命令
INSERT INTO [TBLAUTHOR] ([SSN], [LastName], [FirstName])
VALUES (@SSN, @LastName, @FirstName)
SELECT Id, SSN, LastName, FirstName FROM TblAuthor
WHERE (Id = SCOPE_IDENTITY())
SCOPE_IDENTITY函数返回刚才所插入的作者ID值。注意,不要使用@@IDENTITY函数,因为在激活一个插入触发器,以在包含Identity列的表中插入一行或多行数据时,该函数将返回一个错误值。
因为必须获取服务器中的 数据来更新非连接数据中的占位符,所以必须考虑更新主键值对性能的影响。如果使用服务器中创建的值更新非连接数据键,会发生什么情况呢?这时,所有的子数 据都必须进行更新,以反映键值的变化;为实现这一目的,可以根据它们之间的相互关系启用级联更新功能。这又会引出另一个性能问题,尤其对于较大的DataSet对象。
在使用GUID代理主键时,一旦设置了主键就不必再修改它。主要的问题是如何设置键值。下面的代码片段演示了如何初始化GUID。
Visual Basic
Private Sub Form1_Load(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles MyBase.Load
For Each dt As DataTable In salesSurrogateGuidKeyDs.Tables
If (not dt.Columns("Id") Is Nothing) Then
AddHandler dt.TableNewRow, addressof InitializeGuid
End If
Next
End Sub
Private Sub InitializeGuid(ByVal sender As Object, _
ByVal e As DataTableNewRowEventArgs)
If (TypeOf e.Row("Id") Is DBNull) Then
e.Row("Id") = Guid.NewGuid()
End If
End Sub
C#
public Form1() //constructor
{
InitializeComponent();
foreach (DataTable dt in sales_SurrogateGuidKeyDs.Tables)
{
if(dt.Columns["Id"] != null)
dt.TableNewRow += new DataTableNewRowEventHandler(InitializeGuid);
}
}
private void InitializeGuid(object sender, DataTableNewRowEventArgs e)
{
if(e.Row["Id"] is DBNull)
e.Row["Id"] = Guid.NewGuid();
}
因为TblAuthor表和TblBook表具有同名的主键(“Id”),所以前面的示例代码包含一个InitializeGuid方法,用于创建新的GUID,在触发这些表的TableNewRow事件时,将调用该方法。通常,像该示例代码那样,使所有的代理键具有相同的名称是一种不错的做法。不必使用该实现创建级联关系。所以,GUID型代理键是这一比较回合的赢家。
在数据库间移动数据 在实现Identity键时,需要额外做一些工作才能在数据库之间移动数据。假设表的键值范围从1到n,这些值也可以作为数据库中所有外键的值。如何取出该数据,并将其合并到另一个使用相同数据编号的数据库中呢?要想解决这一问题,则需要对所有的Identity列进行重新编号。
如果使用GUID键实现,则移动数据只是将数据从一个数据库复制到另一个数据库,因此,GUID键是这一比较回合的赢家。
最终的赢家是… 我刚刚开发完成了一项实现GUID键的大型项目,并且还开发了多个使用智能键和Identity键实现的项目。表5.2总结了各种键类型在一些影响性能、数据大小和易用性等因素中所占的百分比。我根据自己使用这些键类型的经验,将权重值的范围设定为0%到100%,其中100%表示权重值最大。此外,键类型的系数分为以下三级:第一级系数为1,第二级系数为0.5,第三级系数为0,通过将键类型系数乘以每个因素的权重值,就可以得到每种键类型的权重值。
权重值 |
|||
因素及其权重 |
智能键 |
Identity键 |
GUID键 |
数据大小 = 25% |
12.5% |
25% |
0% |
键的可见性 = 5% |
5% |
2.5% |
0% |
键的易修改性 = 20% |
0% |
10% |
20% |
联接个数 = 5% |
5% |
2.5% |
2.5% |
SQL复杂性 = 5% |
0% |
5% |
2.5% |
确保惟一性 = 25% |
0% |
12.5% |
25% |
数据移动 = 15% |
7.5% |
0% |
15% |
总计 = 100% |
30% |
57.5% |
65% |
根据表5.2中的权重值,GUID键的绝大部分因素权重值都是最大的。如果大家对某些因素的权重存有异议,可以尝试着修改权重以验证是否能得到不同的结果。注意,这些主键的实现都不能面分之百地满足所有因素。一般认为,GUID键实现是最适合非连接数据应用程序的。智能键实现是否有属于自己的用武之地呢?有的,它最适合用于数据仓库应用程序,因为数据仓库应用程序在设计上一般都会提供高性能只读访问,并且包含最少的联接数。因为数据是只读的,所以“修改键”因素所占的权重较小。表5.3通过对表5.2中的权重进行局部调整,提供了各种键类型基于另一种权重的百分比。
权重值 |
|||
因素及其权重 |
智能键 |
Identity键 |
GUID键 |
数据大小=25% |
12.5% |
25% |
0% |
键的可见性=10% |
10% |
0% |
0% |
键的易修改性=5% |
0% |
2.5% |
5% |
联接个数=25% |
25% |
0% |
0% |
SQL复杂性=10% |
10% |
5% |
5% |
确保惟一性=5% |
0% |
0% |
5% |
数据移动=20% |
10% |
0% |
20% |
总计=100% |
67.5% |
32.5% |
35% |
前面的表只是作为一种参考,大家在做项目的时候,一定要考虑到其他可能影响项目的任何因素。
许多开发人员在试图使用GUID键时,对GUID感到非常恐惧。GUID可能非常大,但它们并不难以使用。下面将介绍一些使用技巧。
在调试时,可以选中包含一个GUID键的代码,这时IntelliSense会显示该GUID键(如图5.4所示);接着,选中该GUID键的键值,并将其复制到剪贴板;然后将该值粘贴到一个查询窗口中,并将大括号替换为单引号,如下面的SQL语句所示。
使用GUID键的SQL查询命令
SELECT Id, SSN, LastName, FirstName
FROM TblAuthor
WHERE (Id = 'cbc8c64c-6ba6-4bec-baef-4c0e50e8b251')
强烈建议大家在所有非联接表中使用同名的主键列(诸如Id)。这样做有利于编写处理GUID键的存储过程。此外,使主键成为每个表中的第一列还可以帮助用户理解该字段的目的。
由于数据库的设计原因,大家在查找某个外键列中的一个GUID时,却不知道该GUID的数据在哪儿。例如,假设有一个如图5.5所示的独占性OR关系。这种类型的关系经常出现在面向对象的环境中——一个Book类可能包含多个子类,诸如Ebook类、PaperBack类和HardCover类。这些类的字段互不相同,因此要么创建一个包含大量列值为null的表,要么如图5.5所示,为每个子类单独创建一个表,必须在这两种方法中选择其一。
如果想确定某个特定的GUID包含在哪个表中,则可以使用下面的存储过程来定位将该GUID作为主键值的表。
SQL uspGetDataForId
CREATE PROCEDURE dbo.uspGetDataForId
(
@id uniqueidentifier
)
AS
SET NOCOUNT ON
--NOTE: This proc assumes that all user tables have 'Tbl' prefix
--Usage: in Query Analyser, type the following without the '--'
--exec uspGetDataForId '78257ec8-c8f9-4d35-a636-d58d8a67c3d4'
DECLARE @tbl varchar(2000)
DECLARE @sql varchar(2000)
IF OBJECT_ID('tempdb..#idTable') IS NOT NULL DROP TABLE #idTable
CREATE TABLE #idTable (
Id uniqueidentifier,
Count INT,
TableName varchar(2000)
)
DECLARE tables_cursor CURSOR
FOR SELECT TABLE_NAME FROM information_schema.Tables
WHERE substring (TABLE_NAME,1,3)='Tbl'
OPEN tables_cursor
FETCH NEXT FROM tables_cursor INTO @tbl
WHILE @@FETCH_STATUS = 0
BEGIN
IF EXISTS (SELECT * FROM information_schema.columns
WHERE table_name=@tbl AND Column_Name='Id')
BEGIN
SET @sql = 'INSERT INTO #idTable SELECT id as ''Id'', '
+ 'count(*) as ''Count'',''' + @tbl +''' as ''TableName'' FROM '
+ @tbl + ' WHERE ID=''' + CONVERT(varchar(2000),@id)
+ ''' group by Id'
EXEC(@sql)
END
FETCH NEXT FROM tables_cursor INTO @tbl
END
CLOSE tables_cursor
DEALLOCATE tables_cursor
SELECT Id, TableName FROM #idTable WHERE Count > 0
注意,该存储过程要求所有主键列的名称都为Id,用户表的前缀都为Tbl。该存储过程没有尝试在其他任何列中查找一个GUID,但如果需要查找所有使用某个GUID的位置,则要读取所有的列。