SQLSERVER 的主键索引真的是物理有序吗?

news2024/9/22 6:41:03

一:背景

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 槽位进行了重排,将物理无序做成了一种逻辑有序。

三:总结

其实大家只要往高性能上想,肯定不会实现物理有序的,太伤性能了,在 物理无序 上抽象出一层 逻辑有序 不失为一种好办法。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/141318.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

5G NR标准: 第18章 射频特性

第18章 射频特性 如第 3 章所述&#xff0c;NR 的射频特性与 5G 的可用频谱以及在这些频谱分配中运行所需的频谱灵活性密切相关。 虽然频谱灵活性一直是前几代移动系统的基石&#xff0c;但对于 NR&#xff0c;这一点变得更加突出。 它由几个组成部分组成&#xff0c;包括在不…

C++11 线程异步

文章目录1. 线程异步的概念2. future2.1 共享状态2.2 常用成员函数3. promise3.1 常用成员函数3.2 promise的基本使用4. package_task4.1 常用成员函数4.2 package_task的基本使用5. async5.1 async的基本使用6. promise、package_task、async的对比与总结1. 线程异步的概念 问…

干货 | 读懂 Appium 日志,让测试效率翻倍!

Appium 服务器运行时会产生很多日志&#xff0c;但是很多人并不了解其中的意义&#xff0c;也无法掌握有用的信息。本文将详细解读如何读懂 Appium 日志&#xff0c;并让你的测试效率翻倍。开启服务日志第一行显示了 Appium 版本和运行地址。$ appium[Appium] Welcome to Appiu…

曾经对程序员最好的公司,倒下了

硅谷有一家公司&#xff0c;它发明了同时代最好的CPU&#xff0c;最好的操作系统&#xff0c;最好的编程语言&#xff0c;但是由于傲慢和目光短浅&#xff0c;在短短二十多年间就走到了尽头。它就是Sun Microsystems&#xff0c;硅谷最让人惋惜的公司。1Sun的出现是个巧合。80年…

运动蓝牙耳机哪个品牌好、2023年最出色运动蓝牙耳机推荐

上班通勤、娱乐和运动时间都需要一款耳机的陪伴。相信音乐是许多健身爱好者锻炼时的必备&#xff0c;在枯燥的运动中用音乐分散注意力&#xff0c;不仅可以提高运动的积极性&#xff0c;还能让身体产生一种非常奇妙的愉悦感。不过&#xff0c;想要在运动中获得更好的聆听体验&a…

【图像处理OpenCV(C++版)】——3.2 几何变换之投影变换

前言&#xff1a; &#x1f60a;&#x1f60a;&#x1f60a;欢迎来到本博客&#x1f60a;&#x1f60a;&#x1f60a; &#x1f31f;&#x1f31f;&#x1f31f; 本专栏主要结合OpenCV和C来实现一些基本的图像处理算法并详细解释各参数含义&#xff0c;适用于平时学习、工作快…

前端高频vue面试题总结

created和mounted的区别 created:在模板渲染成html前调用&#xff0c;即通常初始化某些属性值&#xff0c;然后再渲染成视图。mounted:在模板渲染成html后调用&#xff0c;通常是初始化页面完成后&#xff0c;再对html的dom节点进行一些需要的操作。 如何从真实DOM到虚拟DOM …

springboot 全局 Date参数接收 String格式 转换异常报错

报错&#xff1a; .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot deserialize value of type java.util.Date from String "2022-12-13 11:22:11": not a v…

C语言实现c++对象和私有成员

C语言实现c对象和私有成员 C语言实现c对象 类是C中面向对象编程思想中比较重要的组成部分&#xff0c;与结构体一样类只是一个模板只有在创建对象时才会申请内存空间&#xff0c;类其实是把具有共同特性的数据或方法&#xff08;面向对象编程中&#xff0c;一般把函数称为方法…

录屏软件哪个好用?10个免费好用的「录屏软件」推荐

想知道如何录制自己的流媒体视频吗&#xff1f;有几个选项可以让您免费录制流媒体视频&#xff1a;桌面屏幕录像机、在线工具、浏览器扩展、iOS 应用程序和 Android 应用程序。 查看下表以获取有关不同直播流媒体录像机的更多信息&#xff1a;它们的主要用途和运行的操作系统。…

基于YOLOV5的火灾检测系统(含模型)+GUI界面

基于YOLOV5的火灾检测系统 本期我们带来的内容是基于YOLOV5的火灾检测系统&#xff0c;火灾检测系统还是比较有实际意义的&#xff0c;也方便大家在背景描述中展开。废话不多说&#xff0c;还是先看效果。 完整代码下载地址:基于YOLOV5的火灾检测系统&#xff08;含模型&…

MySQL高级 SQL优化【limitcountupdate优化】

目录 1&#xff1a;SQL优化 1.1&#xff1a;limit优化 1.2&#xff1a;count优化 1.2.1&#xff1a;概述 1.2.2&#xff1a;count用法 1.3&#xff1a;update优化 1&#xff1a;SQL优化 1.1&#xff1a;limit优化 在数据量比较大时&#xff0c;如果进行limit分页查询&a…

拉伯配资|战略新兴产业火了,高增长低估值股曝光

导读&#xff1a;2022年&#xff0c;A股商场值得记录的前史性大事件不断。这一年&#xff0c;A股商场上市公司数量正式打破5000家&#xff0c;战略新兴工业上市公司数量打破2500家&#xff0c;占比初次打破50%大关。这一年&#xff0c;A股商场顶住杂乱的外部环境要素&#xff0…

一文读懂:什么是CRM?企业如何通过CRM盈利?

今天和大家分享一篇干货文章&#xff0c;主要探讨什么是CRM&#xff0c;用大白话解释企业究竟如何通过CRM盈利。文章有点长&#xff0c;但看完&#xff0c;相信你会有所收获。 一、什么是CRM CRM——客户关系管理系统&#xff0c;它不仅是一个系统&#xff0c;一个技术解决方案…

云原生安全系列 4:6个 Kubernetes 安全最佳实践

引言&#xff1a; Kubernetes为我们提供了一套很好的核心软件安全原则&#xff0c;但我们仍然需要理解并实施它们。对于 Kubernetes 集群分布式部署&#xff0c;攻击向量的数量也会增加&#xff0c;了解并尽可能限制这些攻击面的最佳实践非常重要。 即使在使用托管的 Kuberne…

以系统思维推进零信任架构演进

声明 本文是学习零信任数据动态授权桔皮书. 下载地址 http://github5.com/view/55013而整理的学习笔记,分享出来希望更多人受益,如果存在侵权请及时联系我们 以工程化思维推进零信任架构演进 前文所述零信任数据动态授权能力&#xff0c;是围绕数据本身的库、表、字段构建安…

光点数据中台能干啥?怎么做?用在哪?_光点科技

如今&#xff0c;数据营销已经成为许多公司数字化转型的必要选择。数据中台的支持是企业中任何高层次的业务应用不可或缺的。数据中心面向的公司业务不再是单一业务线&#xff0c;而是从公司整体角度审视业务全景&#xff0c;寻找可重用的沉淀能力。 今天&#xff0c;让我们来了…

一个 Qml MenuBar 的问题

基本情况 使用 QQuick.Control 中的 MenuBar 实现主菜单栏。菜单栏包括 File、Edit、View、Help 菜单项。点击菜单项&#xff0c;会弹出对应的菜单。 ApplicationWindow {id: windowwidth: 320height: 260visible: truemenuBar: MenuBar {Menu {title: qsTr("&File&…

List集合

首先看这个框架图&#xff1a; List集合代表一个元素有序&#xff0c;可重复的集合&#xff0c;集合中每个元素都有对应的顺序索引。List接口中增加了一些根据索引操作元素的方法&#xff1a; void add(int index,E element ) 在列表的指定位置插入该元素。 boolean addAll(in…

【数据结构】LinkedList模拟实现与简单使用

文章目录模拟实现LinkedListLinkedList的简单使用LinkedList的一些方法LinkedList的遍历简单对比一下LinkedList和ArrayList模拟实现LinkedList 在上一篇的博客中&#xff0c;我们讲解了链表的基础知识&#xff0c;并且模拟实现了一个无头单向不循环链表&#xff0c;链表的基础…