一、背景:我不是DBA,我只是个传数据的
先交代一下我的角色。在我们跨境电商公司,存放核心业务数据的SQL Server数据库一直是同事负责的,我这边只管每天上传数据------把各个渠道的订单、退款、广告消耗等数据整理好,灌进数据库里。
之所以先说这个,是因为接下来的优化过程里,我的思路很大程度上被我"相信同事DBA水平"这个前提影响了。
近期公司业务扩张,需要生成的报表数量变多了,而且很多报表不再像以前那样只在夜里跑,而是直接在白天业务比较繁忙的时段生成。以前报表少,偶尔慢一点大家忍忍就过去了;现在报表一多,问题就藏不住了。
最典型的是产品核心数据日报,空负载时跑一次要600~700秒,接近12分钟。更麻烦的是,因为它占着资源,后面的报表一个接一个地堵。
同事也发现问题了,先做了一件事:删了不少旧数据。他觉得数据量太大了,清理一下应该会好转。
结果呢?问题并没有解决。
二、第一刀:动系统参数(结果:几乎没用)
既然同事删数据都没解决,而且我对他的DBA水平一直比较信任,那我就先怀疑是不是SQL Server的系统参数有问题。
我查了服务器的情况:
-
CPU:8核
-
内存:48G(SQL Server缓冲池原本30G)
然后看了两个关键的并行参数:
| 参数 | 原值 | 说明 |
|---|---|---|
| max degree of parallelism | 0(无限制) | 小查询也开并行,反而增加开销 |
| cost threshold for parallelism | 5 | 阈值太低,并行滥用 |
咨询AI后,结合10核服务器的实际情况,我修改为:
sql
EXEC sp_configure 'show advanced options', 1;
RECONFIGURE;
EXEC sp_configure 'max degree of parallelism', 4;
EXEC sp_configure 'cost threshold for parallelism', 50;
RECONFIGURE;
另外,我看服务器内存总体占用率不到70%,又把缓冲池从30G加到35G。
结果呢?基本没变化。
同一个报表,参数调整前后,耗时从680秒降到了655秒左右,提升了不到5%。加缓冲池更是心理安慰------业务繁忙时缓冲池依然被吃满。
这一刀,砍空了。
三、第二刀:检查索引(这次找对方向了)
参数调完没效果,我开始自己翻表结构。发现多数业务表的索引都是以country字段开头的,比如:
sql
CREATE NONCLUSTERED INDEX countryasin ON listingdatas(country, asin, sku, idate, seqno);
这是早期留下的习惯------因为报表经常按国家拆分。但问题是,生成报表时一定会选时间范围,却未必会选国家。
我抓了一个典型的慢查询:
sql
SELECT
country,
sku,
asin,
price,
ratings,
...
FROM listingdatas
WHERE idate BETWEEN '2026-03-28' AND '2026-04-27'
修改前的执行计划
sql
|--Hash Match (Aggregate)
|--Clustered Index Scan (countryasin) ❌
索引第一个字段是country,但查询条件里根本没有国家,SQL Server只能扫整个索引。几千万行数据,IO和CPU全浪费在无效扫描上。
修改后的索引
我把大部分表的索引改成以日期开头,如:
sql
CREATE NONCLUSTERED INDEX idate_index
ON listingdatas (idate, country, asin, sku, seqno);
DROP INDEX countryasin ON skustatus
修改后的执行计划
sql
|--Hash Match (Aggregate)
|--Index Seek (idate_index) ✅
直接按InsertDate范围定位,只读需要的一个月数据,扫描量从几千万行降到几十万行。
这次效果立竿见影。
同样的报表,空负载环境下从600~700秒直接降到了75秒,提升了将近90%。白天业务繁忙时段跑报表,响应时间也明显改善了。
四、但是问题还没彻底解决
| 指标 | 优化前 | 参数调整后 | 索引调整后 |
|---|---|---|---|
| 空负载(秒) | 600~700 | ~655 | 75 |
| 业务繁忙时段(秒) | 经常超时 | 基本不变 | 200~300 |
在白天业务繁忙时段,生成报表还是会慢到200多秒,有时候甚至更久。
原因很简单:报表查询和数据上传、其他业务操作共用同一个SQL Server实例。即使索引再合理,一次报表也要扫描上亿行数据,IO压力和锁等待在高峰期根本躲不掉。
五、第三刀(进行中):用Kettle卸载负载
既然数据库内部已经调得差不多了,下一步就只能从架构层面动手。
我选型了**Kettle(PDI)**作为ETL工具,方案如下:
-
在另一台独立服务器上部署Kettle
-
定时从SQL Server读取源数据
-
在Kettle里完成报表所需的数据清洗、聚合、关联
-
将结果数据写回SQL Server的单独报表表
-
业务查询直接读这张已经算好的结果表
这样做的好处:
-
报表查询不再消耗业务库的CPU/IO/缓冲池
-
即使Kettle跑得慢,也不影响白天的数据上传和其他操作
-
可以一天跑多次,实现准实时报表
预计上线后,业务繁忙时段下的报表响应能降到5秒以内。
六、总结:相信同事不等于放弃自己动手
这次优化让我记住了几件事:
1. 参数调优不是万能药。 AI推荐的参数不一定适合你的场景。在我的案例里,MAXDOP和阈值调整基本没效果------因为问题根本不在并行上。
2. 索引的字段顺序就是生命线。 在跨境电商这种强时间属性的业务里,date往往比country更值得放在索引第一位。索引建反了,参数调出花来也没用。
3. 删旧数据解决不了索引问题。 同事好心删了不少旧数据,但查询慢的原因不是数据量大,而是索引设计不合理------扫描方式不对,数据删一半照样慢。
4. 最后一刀往往不在数据库内部。 当SQL Server本身已经调得差不多,就该考虑架构层面的卸载------ETL、只读副本、读写分离。
最后想说的是:相信同事的专业能力没问题,但关键时刻还是得自己动手翻一翻索引结构。 有时候问题不在别人没做好,而在所有人都习惯了某种"理所当然"的写法。