1. 当你按下"执行"时,数据库在做什么?
每次我们在SSMS里点击执行按钮时,SQLServer其实在进行一场紧张的计算博弈。想象一下你是公交车调度员,面对数以千计的乘车需求,你需要决定发车时间、路线规划、车型选择。优化器就是这个决策者,而统计信息就是它手中的乘客需求清单。
上周我遇到一个真实的案例:某电商平台的订单查询突然变慢10倍。经过排查发现,商品状态字段的统计信息过期,导致优化器错误地选择全表扫描。这让我深刻意识到——统计信息是优化器的眼睛,而直方图和密度向量就是这只眼睛的两种关键视觉模式。
2. 直方图:分桶艺术的极致表现
2.1 直方图的构造原理
直方图就像把全校学生的身高数据绘制成统计表。假设要统计2万名学生,SQLServer默认会创建200个直方图桶(最大可到201)。让我们用真实的数据来演示:
-- 创建测试表(技术栈:SQLServer 2019)
CREATE TABLE Products (
ProductID INT PRIMARY KEY,
Price DECIMAL(10,2) NOT NULL,
CategoryID INT NOT NULL
);
-- 插入价格呈阶梯分布的测试数据(设置不同区间的价格分布)
INSERT INTO Products
SELECT TOP 5000
n = ROW_NUMBER() OVER (ORDER BY (SELECT NULL)),
Price = CASE
WHEN n%10 = 0 THEN 100.00
WHEN n%5 = 0 THEN 200.00
ELSE n*10.00
END,
CategoryID = n%50+1
FROM sys.all_columns a CROSS JOIN sys.all_columns b;
-- 手动创建统计信息
CREATE STATISTICS st_Price ON Products(Price) WITH FULLSCAN;
现在用DBCC查看直方图:
DBCC SHOW_STATISTICS('Products', 'st_Price') WITH HISTOGRAM;
典型输出节选:
RANGE_HI_KEY RANGE_ROWS EQ_ROWS DISTINCT_RANGE_ROWS
100.00 0 500 0
200.00 4500 500 4
......
这里的秘密在于:
- RANGE_HI_KEY 每个区间的上限值
- RANGE_ROWS 该区间内的行数
- DISTINCT_RANGE_ROWS 区间内不同值的数量
2.2 查询优化的实战演示
当执行范围查询时:
SELECT * FROM Products
WHERE Price BETWEEN 150.00 AND 300.00;
优化器会遍历直方图的各个区间,计算满足条件的总行数。如果发现覆盖大量直方图区间,可能选择索引扫描;若只需要少量区间,则可能选择索引查找。
3. 密度向量:不为人知的隐式指南
3.1 密度是如何计算的
密度向量就像班级花名册中的地区分布表。它的计算公式令人着迷:
密度 = 1 / 唯一值总数
但这个简单的公式背后隐藏着重大玄机。来看具体示例:
DBCC SHOW_STATISTICS('Products', 'st_Price');
在输出的头信息中,重点观察:
All density Average Length Columns
0.02 8 CategoryID
0.005 8 ProductID
0.5 8 Price
这里的密度参数说明:
- Price字段的密度为0.5,说明该字段共有1/0.5=2个唯一值
- CategoryID的密度为0.02,对应50个不同值(1/0.02=50)
3.2 多条件查询的秘密武器
当查询条件涉及多个字段时,优化器就会亮出密度向量这张底牌。例如:
SELECT * FROM Products
WHERE CategoryID = 5 AND Price > 100.00;
优化器会这样计算:
- 用CategoryID的密度(0.02)估算该条件的行数:总行数5000 * 0.02 = 100行
- 结合Price字段的统计信息进一步过滤
- 将两个条件的估算结果进行综合计算
4. 两种统计方式的生死较量
4.1 等值查询的决战场
当我们精确查询某个具体值时,直方图和密度向量会擦出怎样的火花?
SELECT * FROM Products
WHERE Price = 200.00;
这时:
- 直方图派:直接查找RANGE_HI_KEY等于200的桶,使用EQ_ROWS值(500)
- 密度向量派:总行数5000 * 密度0.5 = 2500(明显错误!)
这里暴露了关键差异:直方图能精确统计高频值,而密度向量采用平均值假设。
4.2 范围查询的精度比拼
当查询条件是范围时:
SELECT * FROM Products
WHERE Price > 150.00 AND Price < 250.00;
直方图会:
- 找出包含150和250的区间
- 计算每个区间的覆盖比例
- 累加估算行数
密度向量则是: 总行数 * 密度 * 范围比例 → 5000 * 0.5 * 0.2(假设)= 500
这时直方图的优势展露无遗,特别是当数据分布不均匀时。
5. 应用场景的生存法则
5.1 什么时候信任直方图?
- 数据存在明显的热点值
- 查询条件使用高频值
- 范围查询的边界明确
例如电商网站的爆款商品查询,就非常适合依赖直方图统计。
5.2 密度向量更适用的战场
- 多条件联合查询
- 数据分布较为均匀的字段
- 统计信息更新延迟时
比如用户画像系统中的年龄段筛选,数据如果是均匀分布的,密度向量反而更高效。
6. 实践中的血泪教训
6.1 自动更新的双面性
某次线上事故:统计信息自动更新后,执行计划突然变为全表扫描。经查是某个时间点的自动采样导致统计信息失真。
解决办法:
-- 修改统计信息更新策略
UPDATE STATISTICS Products st_Price
WITH FULLSCAN, PERSIST_SAMPLE_PERCENT = ON;
6.2 组合统计信息的威力
当关联字段存在强相关性时,需要创建联合统计信息:
CREATE STATISTICS st_CategoryPrice
ON Products(CategoryID, Price);
这可以让优化器了解两个字段的联合分布情况。
7. 技术选择的哲学思考
7.1 直方图的优势清单
- 精确反映数据分布特征
- 擅长处理偏态数据
- 支持范围查询估算
7.2 密度向量的存在价值
- 计算成本低廉
- 内存占用小
- 支持多条件计算
- 快速响应数据变化
8. 必须牢记的黄金准则
- 定期健康检查:每月检查sys.dm_db_stats_properties
- 警惕采样陷阱:对于超过1GB的表,优先使用FULLSCAN
- 索引重建后必做:重建重要字段的统计信息
- 监控谓词选择:关注执行计划中的实际行数vs估算行数
9. 来自实战的终极建议
- 为高频查询字段建立覆盖索引的同时,必须监控其统计信息
- 使用查询存储(Query Store)捕获执行计划变更
- 对于关键业务表,设置统计信息更新告警
-- 查询统计信息更新时间
SELECT
name AS stats_name,
STATS_DATE(object_id, stats_id) AS last_updated
FROM sys.stats
WHERE object_id = OBJECT_ID('Products');
10. 总结:统计即力量
通过对直方图和密度向量的深入探索,我们发现:优化器就像一个精明但视力欠佳的决策者,统计信息就是它的眼镜。只有正确维护这副眼镜,才能确保它做出最优决策。
在实践中要注意:
- 直方图擅长描述细节但维护成本高
- 密度向量简便但可能失真
- 二者需要配合使用才能发挥最大效果
评论