分享

SQL查询艺术(T-SQL)[节选]SQL调优之大事务分段处理

 命運之輪 2010-04-01
经常我们在论坛上看到很多人提问题:一条语句实现*****.
几年前我也追求这种,别人几个循环嵌套实现的,自己力争一条语句利用关系逻辑来实现,弄完之后自我感觉良好,在数据仓库部门工作一段时间后越来越发现这些东西的不实用。
在这很多情况下,最原始的写法可能最好:
1,具有最好的可读性, 像下面这种,不看原始需求,光看语句,我以前自己写的我都不知道这语句是做什么用的
SQL code
create table t_1(ID int, MID int, Date datetime) insert t_1 select 1, NULL, '2007-1-21' insert t_1 select 2, NULL, '2007-3-25' insert t_1 select 3, NULL, '2007-3-26' create table t_2(ID int, Date datetime) insert t_2 select 1, '2007-1-22' insert t_2 select 2, '2007-1-25' insert t_2 select 3, '2007-1-29' update a set mid=b.id from t_1 a left join t_2 b on b.date= (select max(date) from t_2 x where date<=a.date and not exists(select 1 from t_1 y where id<a.id and date>=x.date and not exists(select 1 from t_2 where date>x.date and date<=y.date) ) ) --or update a set mid=b.id from t_1 a left join t_2 b on b.date= (select max(date) from t_2 x where date <=a.date and (select count(*) from t_2 where date>=x.date and date<=a.date)= (select count(*) from t_1 where id<a.id and date>=x.date)+1 ) drop table t_1,t_2

2, 具有可能较好的性能,即便不具有最快的执行时间,也会从整个系统角度来说影响最小。

以下是几个例子.


1)
SQL code
select a.*,cnt = (select count(*) from tb where group_id= a.group_id) from ta a

这条语句,很直白,也很容易理解,但是从逻辑上来看它的效率呢,确实很差,从逻辑执行上来看,每扫一条ta表记录,都要在tb中count一次。(这里我们暂不管你查询优化器多么智能,只讲语句的逻辑写法上)
语句稍做改进就成为
SQL code
select a.*,isnull(b.cnt,0) from ta a, left join (select group_id,count(*) from tb group by group_id) b on a.group_id = b.group_id

为什么这里用left join而不inner join, 只是说可能tb中并不存在ta中group_id对应的记录,为防止结果数据丢失所以左连。如果业务规则上tb中必有ta中对应的group_id之存在,那么内连接获取更好的性能。
与第一句相比,先聚合形成较少的结果集, 再连接, 这是从逻辑上 对事务的分批。

2)
在一个stored-proc中

SQL code
select distinct b.sku_no, b.vend_no into #sku from part b, inv_qty a where a.sku_no = b.sku_no and a.inv_type = 300

以上为原始语句, 分析实际情况及数据:
part 为产品表, 百万级 inv_qty 为库存表 , 每个part都会在其中有记录, inv_type 值从1-300有 300种值,但不一定每个part都有300个inv_type
另外,也可能part表中的一些变更,有的inv_qty 维护不及时,在inv_qty表中的sku并不存在于part表。
part表聚集索引sku_no, inv_qty 聚集索引 sku_no, inv_type为普通索引。

在实际上执行这条语句时,实际IO约为1M , 单条语句执行时间为 70-90s
实际上随着时间的变更,数据的变化,有可能某时inv_qty 中type为300的part会剧增, 会有更大的执行成本和IO开销

调优处理:

SQL code
create table #sku(sku_no int null, vend_no int null) insert #sku(sku_no) select distinct sku_no from inv_qty where inv_type =300 exec('create index idx1 on #sku(sku_no)') exec('update #sku set vend_no = b.vend_no from #sku a,part b where a.sku_no = b.sku_no') delete from #sku where vend_no is null


实际最大IO 为200K, 批执行时间为 15-30s.
这里有几个需要说明的问题:
(a) ,先create 再 insert 来替代select into
select into 在创建表并复制数据时会锁定系统表,特别是当大事务或大批量数据处理时,会对整个系统造成比较大的影响,即便从单个进程上来select into比之insert select减少了一些对于目的表结构、约束的检查,但是它对整个系统是不利的
(b) ,这里为什么用动态语句去创建索引及update
在整个sp中,以上五个语句是一个批,
如果写成:
SQL code
create table #sku(sku_no int null, vend_no int null) insert #sku(sku_no) select distinct sku_no from inv_qty where inv_type =300 create index idx1 on #sku(sku_no) update #sku set vend_no = b.vend_no from #sku a,part b where a.sku_no = b.sku_no delete from #sku where vend_no is null

在这个批中,优化器进行操作时,实际上语句并未执行,所以这里貌似有索引,实际上用不到
用动态语句,那么,在新的内存空间中创建索引,在另一新的内存空间执行update时,动态语句会又开始选择优化,就会用到先前创建的索引
(c) ,为什么要有最后的一条delete语句
上面说了,inv_qty的sku并不一定存在于part表,调优的目的在于性能及对整个系统的影响,如果改变了结果,那么调优是绝对失败的。

3)
SQL code
update #orders set cust_no = b.cust_no from #orders a,Cur..history_header b where a.order_no = b.order_no and a.order_type = b.order_type

#orders 300w左右记录的临时表, Cur..history_header 数千万, HIS..history_header更大好多个数量级
#orders order_no + order_type 普通索引, history_header order_no + order_type 聚集索引
直接运行时,马上报错 IO over the limit 5MB

改用循环来更新
SQL code
set rowcount 10000 select @@rowcount while @@rowcount>0 update #orders set cust_no = b.cust_no from #orders a,Cur..history_header b where a.order_no = b.order_no and a.order_type = b.order_type and a.cust_no is null and b.cust_no is not null set rowcount 0

这里用了set rowcount, 当然有些人会使用在#order创建时就加identity列,再建索引于其上,然后以此来操作
and a.cust_no is null 这个容易理解, 为什么要加 and b.cust_no is not null
当history_header中cust_no 本身是null时,如果不限制,会陷入死循环, 因为它不停的又把null赋给#order.cust_no。

以上并没有什么技术难度,入门者都可以做的,写在这里,只是代表一种观念,花里胡梢的不一定是好的

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多