读书人

对于MS [主键默许是聚集索引] 的质疑

发布时间: 2013-07-11 15:38:46 作者: rapoo

对于MS [主键默认是聚集索引] 的质疑
1.ms sqlserver的默认特性:主键默认是聚集索引
2.我听到的一些与我的理解明显不同的言论:
..聚集索引所在的列或列的组合最好是唯一的
..最好使用自增列作为聚集索引列


上面这些我一直理解不能,而且持上述看法的不在少数,这与我的认知严重违背,有谁能有办法说服我,告诉我是我错了?


================================以下是我的看法================================

1.在大多数情况下我们的性能问题是读取而不是写入,客户反映我们的系统慢,通常说的是某个功能或报表的查询慢,而不是保存慢,即使对于一个写操作非常频繁的系统来说,读写的比例至少在10:1以上,普通的信息系统这个比例就更高了,所以"从性能的角度考虑"这句话,我认为是要重点考虑读取性能,而不是写入的性能。
基于上述原因,我认为我们在做设计的时候,如果能通过聚集显著的提升读取性能,那么我们就可以接受聚集带来的负面效果(写性能下降,以及表上其它非聚集索引的读性能下降)

2.聚集的优势在于读取批量且连续的记录,这是由聚集的物理形态决定的。比如读取一个月内的销售数据,如果按日期聚集,则这一个月内的数据所在的页面在物理上是连续的,可以极大避免最昂贵的磁头随机移动的操作,这是非聚集索引无法做到的。就好比你查字典,查找所有读音为 [ta] 的字效率很高,但是查找所有偏旁部首为 [亻]的字效率非常低。
因此基于上述分析:我认为聚集索引应该要选择一个离散度适中的列,以保证能够命中适当范围的记录,注意我的表述是[适当范围]:
如果命中范围太大则说明该列没有索引的价值(比如性别更,平均每次命中50%的记录)
如果命中范围太小则说明没有聚集的价值(比如主键,平均只能命中1条记录)

3.表上的聚集索引只能有一个,而且聚集还有负面影响,因此其宝贵程度不言而喻。而自长列通常在业务逻辑上毫无意义(用户不可能按自增长列来进行查询或分析等业务操作),这样的列用做主键没有问题,但用作聚集索引只能说是暴殄天物,原因与上面第2条同理:自增长列的离散度太低,导致选择性过高,每次只能命中一条记录,并不具备聚集的价值,这样的聚集还不如没有,因为我们承受了聚集带来的负面效果,却又没有体现出聚集的优势,因此我一向认为堆表的性能肯定要比那些设计得很糟糕的聚集表要高。
[解决办法]
聚集索引所在的列或列的组合最好是唯一的
--> 不一定,当表的主键约束不是表的聚集索引时,聚集索引的列或列组合可以不是唯一的.

最好使用自增列作为聚集索引列
--> 不一定,但需看具体的情况.
个人不建议用自增列作主键或聚集索引,因为:
1.自增列的数据(1,2,3,4..)没有意义,纯属为了不重复而存在,占用存储空间(每行4kb).
2.浪费了对于一个表唯一的一个聚集索引,查询时的访问顺序:非聚集索引->聚集索引->数据页.
3.当重复写入相同的记录时,无法检查数据的唯一性,造成数据重复.
[解决办法]
两位的精神这么好啊,都是半夜三更来提问回答问题的。
[解决办法]
如果已经有适合业务主键的话可以不用逻辑主键,但我大部分时候都逻辑主键。首先自增列的写入性能好,其次占用空间小。使用复杂的业务主键的时候建立额外的索引是非常痛苦的事情。
create table (Country char(10),City char(20),Offcie char(50),Staff char(20),Primary key(Country,City,Office,Staff),Name varchar(50).......)
如果使用业务主键的话经常会看到这样的表,不考虑元数据,这个表主键占用的空间是100byte,假设有100W的数据的时候,要创建额外的索引需要使用索引列使用的空间+100byte*100W=100MB的空间。
create table (ID int identity(1,1) primary key,Country char(10),City char(20),Offcie char(50),Staff char(20,Name varchar(50).......)
使用这样的结构的话,最初使用的空间是更多的,首先需要4byte*100W=4MB的聚集索引的空间,其次还需要一个业务主键替代的非重复的索引100byte+4byte=104MB,但这个优势慢慢会体现在建立索引上,因为建立一个索引只需要额外的4MB的空间了,一般这样的表都会建立非常多的索引,因为检索条件是非常灵活的。

另一个问题是建立外键的时候,如果有100byte的业务主键,有100W的数据量,要还创建一个使用这个的外键表数据上亿的时候内心会有什么东西奔腾而过的。


[解决办法]
学习,关注、支持
[解决办法]

引用:
我尤其不理解的是sql server为什么将主键默认为聚集索引,难道ms也认为离散度低的列作为聚集能带来更好的性能表现?但这样做是否与聚集的物理形态相违背啊,一个UNIQUE的索引,怎么能体现出聚集的优势呢?


虽然我谢绝教条主义,但我不明白你为什么将离散性和聚集性能挂钩。查询性能也不是只盯着聚集索引,就拿你举的例子[ta]和[亻],这是一个很形象的问题,你认为查找所有读音为 [ta] 的字效率很高,我认为查找所有偏旁部首为 [亻]的字更具效率,部首索引条目就有,多极几页,翻聚集索引的[ta],超10页没问题吧。每一个查询都走聚集,等于没索引设计,如将字典部首索引撕了。
[解决办法]
你上面提到多次“聚集还有负面影响”,能否说说负面影响具体指哪些? 除了你说的“写性能下降”
[解决办法]
引用:
a.是离散度高的列?(比如订单表中的业务日期,它的键值不具备唯一性,一个日期可能对应有数百张订单)
b.是离散度低的列?(比如主键,或是自增长列,这些它的每一个键值都是唯一的)
我认同a,但很多人认同b,主要问题是在这里。


LZ举得例子本身就是个大BUG,a和b都不是离散列。
你认同的a和别人认同的b是一个道理,跟是否是UNIQUE没关系

要实现业务日期高离散
1、用hash封装
2、要么让改装datetime,实现timestamp%60,按“秒”离散,或者更大的离散值

聚集索引到底因该被设计成单调递增还是离散式,这要根据业务来定论
就关系型数据库而言,大部分业务任然是轻量级的。流读业务多用单调递增,随机访问的业务多用离散。

sql server为什么将主键默认为聚集索引

首先要了解关系型数据库的三范式
第一范式就不提了
第二范式的定义:第二范式(2NF)要求数据库表中的每个实例或行必须可以被唯一地区分。为实现区分通常需要为表加上一个列,以存储各个实例的唯一标识。2NF要求实体的属性完全依赖于主关键字。所谓完全依赖是指不能存在仅依赖主关键字一部分的属性,如果存在,那么这个属性和主关键字的这一部分应该分离出来形成一个新的实体,新实体与原实体之间是一对多的关系。为实现区分通常需要为表加上一个列,以存储各个实例的唯一标识。简而言之,第二范式就是非主属性非部分依赖于主关键字。

第二范式中提到的唯一列和聚集索引合并设计的最大优势是:按唯一标识的主关键字,进行数据查、增、删、改时,性能最大化。这里指的查是指连接查询。

聚集索引往往被设计在一些有主外连接的数据字段上,也就是通常说业务连接体:比如订单流水号、Log日志流水号等。这些连接体加载着业务表于业务表之间的逻辑关系。逻辑关系就是业务关系,业务关系放到在三范式中就是主外键。聚集索引的设计方式和实体业务挂钩,和批量或随机访问的方式无关。所以主键被默认加上聚集索引并没有问题。
至于到底是设计成离散还是单调递增这个由业务决定,上面已经说过了,不重复。

LZ说:
通常说的是某个功能或报表的查询慢,而不是保存慢
因此我一向认为堆表的性能肯定要比那些设计得很糟糕的聚集表要高。

在轻量级数据级别下,这个假设没错,但当一张表上到几亿甚至几十亿,这个时候写的性能更重要。
为什么?道理很简单,当数据都写不进DB了,还谈什么读?
这时候只能做一件事情删除所有的二级索引!!只保留聚集索引
在这种情况下,所有的三范式、ACID定理都是浮云。
假设你用的是随机插入式堆表,那不好意思,只能等死。

还有一种设计模式,就是分布式 SQL SERVER + 数据代理层。在这种设计模式下SQL SERVER的读写比是倒过来
一次读取缓存,多次写入。嘿嘿,没见过吧。这种设计模式在网游行业非常普遍,SQL SERVER只作为数据持久化的载体,不带任何逻辑。玩家的数据每分钟可能会回写好几次,但读取只有一次,就是在玩家登录游戏的时候。假设你用的是随机插入式堆表,那不好意思,也只能等死。

所以,归根结底,还是要根据业务类型和数据量来决定数据库的设计模式,不论怎么设计都有其存在的价值,不能简单用对错来判定。

[解决办法]
引用:
还有一种设计模式,就是分布式 SQL SERVER + 数据代理层。在这种设计模式下SQL SERVER的读写比是倒过来
一次读取缓存,多次写入。


数据库本身不是也有lazywrite这一特性吗?提交的数据也不是时时刻刻写入磁盘中的数据文件中的。
[解决办法]
比较可以,不错


[解决办法]


出现了键查找 也就是 你那个bookmark lookup


其实出现这个问题的原因是因为索引建的不合适

因为我这个表有一个聚集索引和一个非聚集索引,索引只覆盖了我的OrderId和createdate这两个字段,

而我的查询返回的是OrderId,UserId,TotalMoney这三个字段,但这个时候的索引 聚集索引只覆盖了OrderId

这一个列,而非聚集索引只覆盖了CreateDate这一个列,这个时候就需要通过键查找去返回UserId,TotalMoney

这两列了。

但是我们如果这样做:

先删掉之前的非聚集索引

drop index CreataDate_idx on Orders
go


然后再创建一个新的索引,让它包含UserId,TotalMoney这两列
create nonclustered index CreataDate_idx on Orders(CreateDate asc)include(UserId,TotalMoney)
go



这个时候的执行计划
对于MS [主键默许是聚集索引] 的质疑

根本就没得你说的那个bookmark lookup了



bookmark lookup分为key lookup(表存在聚集索引的时候可能会出现),rid lookup(没得聚集索引的时候可能会出现) 至于rid lookup 你可以把聚集索引删了试试
------解决方案--------------------



[解决办法]
上面有好几位都提到了,用自增列做聚集索引,绝对是有道理的。
[解决办法]
首先第一点,自增键作为聚集索引是毫无意义的,因为关联查询不会使用自增键。
其次,什么键作为聚集索引,需要根据业务特征来判断。如果某表大多数时间都是按照时间区间作为唯一条件查询的,那么聚集索引设置在时间列上就很好呀!一般情况下,时间列和自增列一样也是单向增长的呀!!!
[解决办法]
上面恢复太多,没有仔细看,说法可能与其他人重复或者类似

我觉得搂主对聚集索引的理解稍有欠缺
1、在有聚集索引的数据表,非聚集索引除了记录索引键值外,需要记录聚集索引值,如果聚集索引不唯一,额外可能需要记录一个INT32的顺序号,注意这里是增加非聚集索引的叶子节点的每一行数据的,如果聚集索引值太长,非聚集索引太多,这里增加的索引存储不会很少,而且由于每行索引记录增大,造成每页可存放索引数下降,必然引起索引层数增加,从而从整体上影响效率;从这一点可以看出(个人认为),一般情况下聚集索引选择需要把聚集索引值的长度放在第一位,同时尽量是唯一的。
2、关于读写比例,确实大部分应用是读多写少,或者关注读性能多关注些性能少,但个人做过一些很注重写效率的系统,这时聚集索引对写性能的影响不能不考虑。如果考虑聚集索引对写性能的影响,就不能不考虑聚集索引的顺序,一般我们希望后插入的数据是排在聚集索引的末尾,这样就不会因为中间插入而发生的页拆分,页拆分由于I/O操作多是写操作最大的杀手之一。排在末尾的字段一般包括插入时间、自增列或者其他确定有序而且是随时间增大的ID。



3、写速度慢对整个系统效率的影响不能不考虑锁,一般情况下,我们编程时很少会在查询时直接允许脏读,这样写速度慢就会影响查询的速度。

4、不知道怎么得出的结论“如果命中范围太小则说明没有聚集的价值(比如主键,平均只能命中1条记录)”,聚集索引可以SEEK也可以SCAN,能找到命中范围小其他方面适合聚集的字段那就不必找其他了,这个也是基于第一条的。

5、关于又聚集的表(树表)和堆表总体性能的比较,我觉得不能片面考虑某一个查询,而是要根据非聚集索引的结构综合考虑所有可能查询的方式,一般情况下,只有在很特殊的情况(很难找出很短的唯一键、查询方式非常单一)才会出现堆表性能比树表好的情况

读书人网 >SQL Server

热点推荐