SQLSERVER 的主键索引真的是物理有序吗?
一:背景
1. 讲故事
最近在看 SQL SERVER 2008 查询性能优化,书中说当一个表创建了聚集索引,那么表中的行会按照主键索引的顺序物理排列,这里有一个关键词叫:物理排列,如果不了解底层原理,真的会被忽悠过去,其实仔细想一想不可能实现严格的 物理排列 ,那对性能是非常大的损害,本篇我们就从底层出发聊一聊到底是怎么回事。
二:原理探究
1. 我认为的物理排列
如果用 C# 代码来演示严格的物理排列,大概是这样的。
static void Main(string[] args)
{
List<int> list = new List<int>() {1,2,4,5 };
list.Insert(2, 3);
Console.WriteLine(string.Join(",", list));
}

从代码看我用 Insert 将 3 插入到了 list 集合中形成了物理有序,但不要忘了 Insert 的复杂度是 O(N),而且还要将 3 后面的数据整体挪动,可以参考源码中的 Array.Copy 方法。
public void Insert(int index, T item)
{
if (_size == _items.Length)
{
EnsureCapacity(_size + 1);
}
if (index < _size)
{
Array.Copy(_items, index, _items, index + 1, _size - index);
}
_items[index] = item;
_size++;
_version++;
}
现在你可以想一想,如果我们每次在 Insert 的时候 SQLSERVER 都要将数据页上的数据往后挪,那这个性能有多差?
2. 观察聚集索引下的数据排序
为了方便讲述,先创建一个测试表,插入 4 条记录,再创建一个聚集索引,sql 代码如下:
IF OBJECT_ID('t') IS NOT NULL DROP TABLE t;
CREATE TABLE t (a CHAR(5), b INT)
INSERT INTO t(a,b) VALUES('aaaaa',1);
INSERT INTO t(a,b) VALUES('ddddd',4);
INSERT INTO t(a,b) VALUES('ccccc',3);
INSERT INTO t(a,b) VALUES('eeeee',5);
CREATE CLUSTERED INDEX idx_a ON t(a);

从图中看数据果然是有序的,严格的按照 a , c, d , e 排序,接下来用 dbcc 观察下在底层数据页上这几条记录是不是物理有序的? 查询 SQL 如下:
DBCC TRACEON(3604)
DBCC IND(MyTestDB,t,-1)
DBCC PAGE(MyTestDB,1,472,2)
Page数据页的输出结果如下:
PAGE: (1:472)
PAGE HEADER:
Page @0x000002C6E75D0000
m_pageId = (1:472) m_headerVersion = 1 m_type = 1
m_typeFlagBits = 0x0 m_level = 0 m_flagBits = 0x4
m_objId (AllocUnitId.idObj) = 269 m_indexId (AllocUnitId.idInd) = 256
Metadata: AllocUnitId = 72057594055557120
Metadata: PartitionId = 72057594048348160 Metadata: IndexId = 1
Metadata: ObjectId = 850102069 m_prevPage = (0:0) m_nextPage = (0:0)
pminlen = 13 m_slotCnt = 4 m_freeCnt = 8024
m_freeData = 160 m_reservedCnt = 0 m_lsn = (49:1616:23)
m_xactReserved = 0 m_xdesId = (0:0) m_ghostRecCnt = 0
m_tornBits = 0 DB Frag ID = 1
Allocation Status
GAM (1:2) = ALLOCATED SGAM (1:3) = NOT ALLOCATED PFS (1:1) = 0x40 ALLOCATED 0_PCT_FULL
DIFF (1:6) = CHANGED ML (1:7) = NOT MIN_LOGGED
DATA:
Memory Dump @0x000000DF137F8000
000000DF137F8000: 01010000 04000001 00000000 00000d00 00000000 ....................
000000DF137F8014: 00000400 0d010000 581fa000 d8010000 01000000 ........X...........
000000DF137F8028: 31000000 50060000 17000000 00000000 00000000 1...P...............
000000DF137F803C: 00000000 01000000 00000000 00000000 00000000 ....................
000000DF137F8050: 00000000 00000000 00000000 00000000 10000d00 ....................
000000DF137F8064: 61616161 61010000 00030000 10000d00 63636363 aaaaa...........cccc
000000DF137F8078: 63030000 00030000 10000d00 64646464 64040000 c...........ddddd...
000000DF137F808C: 00030000 10000d00 65656565 65050000 00030000 ........eeeee.......
000000DF137F80A0: 00002121 21212121 21212121 21212121 21212121 ..!!!!!!!!!!!!!!!!!!
...
从 Memory Dump 区节的内存地址看,这四条记录果然是有序的,
3. 真的按照物理有序吗
接下来就是关键了,到底是不是物理有序,我们再插入一条 bbbbb 记录,看下会不会将 ccccc 所在的内存地址上的内容整体往后挪?测试的 sql 语句如下:
INSERT INTO t(a,b) VALUES('bbbbb',2);
SELECT * FROM t;

从图片看,貌似真的给塞进去了,那到底是不是这样呢? 带着好奇心再次观察下底层的索引数据页。
PAGE: (1:472)
PAGE HEADER:
Page @0x000002C6D76C4000
m_pageId = (1:472) m_headerVersion = 1 m_type = 1
m_typeFlagBits = 0x0 m_level = 0 m_flagBits = 0x0
m_objId (AllocUnitId.idObj) = 269 m_indexId (AllocUnitId.idInd) = 256
Metadata: AllocUnitId = 72057594055557120
Metadata: PartitionId = 72057594048348160 Metadata: IndexId = 1
Metadata: ObjectId = 850102069 m_prevPage = (0:0) m_nextPage = (0:0)
pminlen = 13 m_slotCnt = 5 m_freeCnt = 8006
m_freeData = 176 m_reservedCnt = 0 m_lsn = (49:1640:2)
m_xactReserved = 0 m_xdesId = (0:0) m_ghostRecCnt = 0
m_tornBits = 487522741 DB Frag ID = 1
Allocation Status
GAM (1:2) = ALLOCATED SGAM (1:3) = NOT ALLOCATED PFS (1:1) = 0x40 ALLOCATED 0_PCT_FULL
DIFF (1:6) = CHANGED ML (1:7) = NOT MIN_LOGGED
DATA:
Memory Dump @0x000000DF0FDF8000
000000DF0FDF8000: 01010000 00000001 00000000 00000d00 00000000 ....................
000000DF0FDF8014: 00000500 0d010000 461fb000 d8010000 01000000 ........F...........
000000DF0FDF8028: 31000000 68060000 02000000 00000000 00000000 1...h...............
000000DF0FDF803C: b5010f1d 01000000 00000000 00000000 00000000 ....................
000000DF0FDF8050: 00000000 00000000 00000000 00000000 10000d00 ....................
000000DF0FDF8064: 61616161 61010000 00030000 10000d00 63636363 aaaaa...........cccc
000000DF0FDF8078: 63030000 00030000 10000d00 64646464 64040000 c...........ddddd...
000000DF0FDF808C: 00030000 10000d00 65656565 65050000 00030000 ........eeeee.......
000000DF0FDF80A0: 10000d00 62626262 62020000 00030000 00002121 ....bbbbb.........!!
000000DF0FDF80B4: 21212121 21212121 21212121 21212121 21212121 !!!!!!!!!!!!!!!!!!!!
...
000000DF0FDF9FF4: 21219000 80007000 a0006000 !!....p...`.
OFFSET TABLE:
Row - Offset
4 (0x4) - 144 (0x90)
3 (0x3) - 128 (0x80)
2 (0x2) - 112 (0x70)
1 (0x1) - 160 (0xa0)
0 (0x0) - 96 (0x60)
从 Memory Dump 节的内存地址看,bbbbb 并没有插入到 aaaaa 和 cccccc 之间,而是写入到页面尾部的空闲空间中,接下来就有一个问题了,为什么 sql 输出中是有序的呢?怎么做到的? 如果你了解 Page 的 Slot 布局,你会发现 Slot1 指向的就是 bbbbb 这条记录的首地址,画一张图就是这样。

从图中我们就明白了最终的原理,当 Insert 时,SQLSERVER 并没有对表记录重排,而只是将指向的 Slot 槽位进行了重排,将物理无序做成了一种逻辑有序。
三:总结
其实大家只要往高性能上想,肯定不会实现物理有序的,太伤性能了,在 物理无序 上抽象出一层 逻辑有序 不失为一种好办法。
SQLSERVER 的主键索引真的是物理有序吗?的更多相关文章
- 1226关于count(*)不走主键索引反而走二级索引
转自 http://www.2cto.com/database/201508/433975.html mysqlcount(*)会选哪个索引? 2015-08-19 0个评论 来源:D ...
- 修改mysql表结构,添加一个主键索引自增字段,修改原来的主字段为普通字段
原来有一个字段id,为自增,主键,索引.现在要新增一个字段s_id为自增,主键,索引.同时把原来的主字段改成普通字段,默认值为0. Alter table e_diamond_jhds change ...
- Mysql索引介绍及常见索引(主键索引、唯一索引、普通索引、全文索引、组合索引)的区别
Mysql索引概念:说说Mysql索引,看到一个很少比如:索引就好比一本书的目录,它会让你更快的找到内容,显然目录(索引)并不是越多越好,假如这本书1000页,有500也是目录,它当然效率低,目录是要 ...
- Data Base sqlServer 组合主键
sqlServer 组合主键 创建表时: create table Person ( Name1 ) not null ,Name2 ) not null primary key(Name1,Na ...
- MYSQL的全表扫描,主键索引(聚集索引、第一索引),非主键索引(非聚集索引、第二索引),覆盖索引四种不同查询的分析
文章出处:http://inter12.iteye.com/blog/1430144 MYSQL的全表扫描,主键索引(聚集索引.第一索引),非主键索引(非聚集索引.第二索引),覆盖索引四种不同查询的分 ...
- Mysql主键索引、唯一索引、普通索引、全文索引、组合索引的区别
原文:Mysql主键索引.唯一索引.普通索引.全文索引.组合索引的区别 Mysql索引概念: 说说Mysql索引,看到一个很少比如:索引就好比一本书的目录,它会让你更快的找到内容,显然目录(索引)并不 ...
- 删除指定表的所有索引,包括主键索引,唯一索引和普通索引 ,适用于sql server 2005,
原文:删除指定表的所有索引,包括主键索引,唯一索引和普通索引 ,适用于sql server 2005, --删除指定表中所有索引 --用法:declare @tableName varchar(100 ...
- mysql索引之主键索引
MySQL目前主要有以下几种索引类型:1.普通索引2.唯一索引3.主键索引4.组合索引5.全文索引 二.语句 CREATE TABLE table_name[col_name data type] [ ...
- 唯一索引 && 主键索引
唯一索引唯一索引不允许两行具有相同的索引值. 如果现有数据中存在重复的键值,则大多数数据库都不允许将新创建的唯一索引与表一起保存. 当新数据将使表中的键值重复时,数据库也拒绝接受此数据.例如,如果在 ...
- PostgreSQL主键索引膨胀的重建方法
普通的索引膨胀处理比较简单,主键的索引膨胀也不复杂,只是在新旧索引交替时有一些小处理.本试验在primary key上通过CONCURRENTLY建立第二索引来解决索引膨胀问题,适用9.3.9.4,其 ...
随机推荐
- PHP cURL抓取网上图片
cURL的底层是由一个命令行工具实现的,用于获取远程文件或传输文件,更多的情况是用来模拟get/post表单提交.也可以用户文件上传,爬取文件,支持FTP/FTPS,HTTP/HTTPS等协议,通俗来 ...
- Go | 讲解GOROOT、GOPATH、GOBIN
前言 Go(又称 Golang)是 Google 开发的一种静态强类型.编译型.并发型,并具有垃圾回收功能的编程语言.Go 被誉为是未来的服务器端编程语言. Go是一门全新的静态类型开发语言,具有自动 ...
- CVE-2021-44832 log4j_2.17.0 RCE复现与吐槽
先说一句,这傻x洞能给cve就离谱,大半夜给人喊起来浪费时间看了一个小时. 先说利用条件: 需要加载"特定"的配置文件信息,或者说实际利用中需要能够修改配置文件(你都能替换配置文件 ...
- FlinkSQL之Windowing TVF
Windowing TVF 在Flink1.13版本之后出现的替代之前的Group window的产物,官网描述其 is more powerful and effective //TVF 中的tu ...
- Linux--多线程(三)
生产者消费者模型 概念: 生产者消费者模式就是通过一个容器来解决生产者和消费者的强耦合问题.生产者和消费者彼此之间不直接通讯,而通过一个来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给 ...
- 【k8s连载系列】k8s介绍
k8s是Kubernetes的缩写,Google 于 2014 年开源了 Kubernetes 项目. 一.k8s的历史演变 k8s的演变过程:首先从传统的服务-->虚拟机部署-->容器部 ...
- 关于.Net和Java的看法-一个小实习生经历
目录 背景 带着疑惑 生活中的迷茫 开始实训 实习 再看java 总结 背景 笔者是一个专科院校的一名普通学生,目前就职于某三线城市的WEB方面.Net开发实习生,在找实习期间和就业期间的一些看法,发 ...
- 46.drf过滤、搜索、排序
DRF的过滤类 drf过滤器在filters模块中,主要有四个类 BaseFilterBackend:过滤基类,留好占位方法待后续继承 SearchFilter:继承BaseFilterBackend ...
- 表单的子元素可不在form标签内
表单是网页用于向服务器发送数据的元素.其用法类似下面: <form method="POST" action="/login"> <input ...
- SCI简介和写作顺序
一.SCI论文组成部分简介 一篇完整的 sci 论文主要包括以下几个主要的组成部分,从前往后依次分别是 Title 就是说这个文章的标题其次是 Abstract 也就是这个文章的摘要.接下来是 Int ...