索引设置 所以最好就是int 如果是 varchar 固定大小 时间索引也要固定大小
索引失效情况
页(Page)——数据存储的“书页”
定义
页是 SQL Server 中数据存储的最小单位,类似于一本书中的一页纸。每个页固定大小为 8KB(8,192字节),所有数据(包括表数据、索引、系统信息)均以页为单位存储和读取。
页的结构
每个页由三部分组成(见图1):
页头(96字节):存储系统信息(如页码、页类型、可用空间等)。
数据区(约8,000字节):存放实际数据行或索引条目。
行偏移表(行指针):记录每行数据的起始位置,类似书页的“页码索引”,从页尾向前倒序排列。
类比:想象一页纸,页头是标题和作者信息,数据区是正文内容,行偏移表是每段文字在页中的位置索引。
页的类型
数据页:存储用户表的具体数据行(如 varchar、int 等普通列)。
索引页:存储索引结构(如 B+ 树的中间节点),帮助快速定位数据。
特殊页:如管理空间分配的 GAM/SGAM 页、记录大文本的 Text/Image 页等。
区(Extent)——管理页的“章节”
定义
区是 8个连续页的集合(共64KB),用于高效管理页的分配。相当于将多个书页装订成章,避免零散存储。
区的类型
混合区:区内的8个页可分配给不同对象(如表、索引)。新创建的小表默认使用混合区,节省空间。
统一区:区内的8个页专属于同一个对象。当表或索引增长到8页以上时,自动升级为统一区,提高连续读写效率。
类比:混合区像共享办公室,多个租户共用;统一区像独立办公室,仅供一家公司使用
CREATE TABLE Students (ID INT, Name VARCHAR(50), Age INT);
| 页头 | ID=1, Name="Alice", Age=20 | ID=2, Name="Bob", Age=22 | ... | 行偏移表 |
行溢出:如果某行数据太大(超过 8060B),部分内容会存到其他页(类似书太厚,分上下册)。
2.1 聚集索引(主键索引)
叶子节点是数据页:数据按主键顺序物理存放(类似书按编号顺序排列)。
非叶子节点是索引页:存储键值范围和子页指针(类似目录的章节导航)。
也就是说 索引页然后找到 叶子数据页 然后根据行数直接找到数据
[索引页(根)]
/ | \
[索引页(中间)] ... [索引页(中间)]
/ | \ ...
[数据页] [数据页] [数据页]
2.2 非聚集索引(普通索引)
叶子节点指向数据页:存储索引键值 + 行定位符(RID 或主键值),回表查询数据(类似书末的索引表,标注“主题→页码”)。
也就是说 索引页找到叶子页这些都是索引页的内容,然后通过聚集索引建或RID 回表找到数据
也就是说 非聚集索引 过程多了一步 聚集索引建或RID 回表
[索引页(根)]
/ | \
[索引页(中间)] ... [索引页(中间)]
/ | \ ...
[叶子页(存RID)] ... [叶子页(存RID)]
2.3 覆盖索引是特殊的非聚集索引
覆盖索引本质上仍是非聚集索引,但通过扩展包含字段,使其能直接满足查询需求。
例如:联合索引 (birthday, user_name) 既是非聚集索引,也是覆盖索引(若查询仅涉及这两个字段)。
依赖相同数据结构
两者均基于 B+ 树实现,但覆盖索引的叶子节点存储更多字段数据
非聚集索引用于加速单列或多列的过滤和排序;覆盖索引专门针对高频查询,通过冗余存储字段避免回表
适用场景:
高频只读查询(如报表、统计)。
查询字段较少且固定(如 SELECT id, name)。
局限性
更新代价高:频繁更新的字段会导致索引维护成本剧增。
存储成本:需额外评估磁盘空间,避免索引占用超过数据表的 50%
总结
页是存储的基本单位(8KB),区是管理页的集合(8页=64KB)。
数据页直接存数据,索引页加速查询,两者通过B+树和指针协作
在使用的过程中肯定会有数据操作,那么 增删改查 势必会对页中的数据存储有一定影响。
数据少了 空间没用完,数据多了空间不够用就会出现以下这些情况。
碎片(Fragmentation)
碎片分为 内部碎片 和 外部碎片,影响查询性能和存储效率。
填充因子(Fill Factor)
行溢出(Row Overflow)
触发条件
变长列(如 varchar(max))数据超过页容量(8KB)。
行总大小(含系统信息)超过 8060 字节。
存储机制
行溢出指针:原页中保留 24 字节指针,指向溢出页(存储实际数据)。
区的类型
关键优化点
页碎片:频繁增删数据会导致页不连续,增加磁盘I/O。可通过重建索引调整空间 ALTER INDEX REBUILD 整理。
行溢出:变长列(如 varchar(max))超过8KB时会拆分到其他页,需谨慎设计表结构。
区分配策略:小表优先使用混合区,大表使用统一区提升性能。
。
设计数据库时需关注页/区分配策略和碎片问题,以优化性能
回表是针对 表 没有 聚集索引的情况下,非聚集索引怎么去寻找数据的,如果有 聚集索引,就不用通过RID回表直接去 跟着 聚集索引建 流程去数据页找数据
什么是 RID 回表?
RID(Row Identifier):当表 没有聚集索引(堆表)时,SQL Server 为每一行数据分配的唯一物理地址,格式为 (文件号:页号:槽号)。
回表(Bookmark Lookup):通过非聚集索引找到 RID 后,根据 RID 定位到数据页获取完整数据行的过程。
类比理解:
想象一本书的末尾有一个“关键词索引”,每个关键词后面标注了对应的页码(类似 RID)。回表就像根据页码翻到正文页面读取完整内容。
为什么需要回表?
非聚集索引的局限性:
非聚集索引的叶子节点 只存储索引键值和 RID(或聚集索引键),不包含其他列的数据。
若查询需要返回非索引列,必须通过 RID 回表读取数据页
示例:
表结构:
CREATE TABLE Students (
StudentID INT PRIMARY KEY NONCLUSTERED, -- 非聚集主键
Name VARCHAR(50),
Age INT
);
SELECT Name, Age FROM Students WHERE StudentID = 100;
非聚集索引 StudentID 的叶子节点只有 StudentID 和 RID。
为了获取 Name 和 Age,必须通过 RID 回表到数据页
SELECT
OBJECT_NAME(object_id) AS TableName,
index_type_desc AS IndexType,
index_level AS IndexLevel,
page_count AS TotalPages,
record_count AS TotalRows,
avg_fragmentation_in_percent AS Fragmentation
FROM
sys.dm_db_index_physical_stats(DB_ID(), NULL, NULL, NULL, 'DETAILED')
WHERE
index_type_desc IN ('CLUSTERED', 'NONCLUSTERED');
聚集索引的叶级页(index_level=0)是数据页,非叶级页(如根、中间节点)是索引页;非聚集索引的所有页均为索引页。
统计索引页总数
聚集索引:总索引页数 = 根页数(index_level=最高层级) + 中间层级页数(index_level>0)。
非聚集索引:总索引页数 = 所有层级的 page_count 之和(包括叶级和非叶级)。
单表总数据量
SELECT
SUM(p.rows) AS TotalRows,
SUM(au.total_pages) * 8 / 1024 AS TotalSpaceMB -- 总空间(MB)
FROM
sys.partitions p
INNER JOIN
sys.allocation_units au ON p.hobt_id = au.container_id
WHERE
p.object_id = OBJECT_ID('YourTableName');
索引页数量的存储原理
实际案例与优化建议
判断是否需分表的依据
数据量阈值
常规场景:
单表数据行数 > 5000万 或 存储空间 > 500GB,考虑分表。
索引页占比过高(如索引页占总页数 >40%),需优化索引或分表。
高并发场景:
频繁出现页锁竞争(通过 sys.dm_tran_locks 监控),需水平分表。
性能指标
查询延迟:关键查询响应时间超过业务容忍阈值(如 >1秒)。
I/O 瓶颈:通过 sys.dm_io_virtual_file_stats 发现数据文件读写延迟高。
维护成本
索引维护时间:重建索引耗时过长(如 >1小时),影响业务可用性。
备份/恢复时间:单表过大导致备份窗口无法接受。
分表策略选择
-- 创建分表(按年份)
CREATE TABLE Orders_2023 (
OrderID INT PRIMARY KEY,
OrderDate DATETIME,
CustomerID INT,
...
) ON FileGroup2023;
CREATE TABLE Orders_2024 (
OrderID INT PRIMARY KEY,
OrderDate DATETIME,
CustomerID INT,
...
) ON FileGroup2024;
-- 使用视图统一访问
CREATE VIEW AllOrders AS
SELECT * FROM Orders_2023
UNION ALL
SELECT * FROM Orders_2024;
-- 主表(高频字段)
CREATE TABLE Users (
UserID INT PRIMARY KEY,
UserName VARCHAR(50),
Email VARCHAR(100)
);
-- 扩展表(低频字段)
CREATE TABLE UserDetails (
UserID INT PRIMARY KEY,
Address NVARCHAR(200),
ProfileText TEXT,
FOREIGN KEY (UserID) REFERENCES Users(UserID)
);
分表后的优化建议