SQLCASEWHEN语句性能优化
背景:性能应该是功能的⼀个重要参考,特别是在⼤数据的背景之下!写SQL语句时如果仅考虑业务逻辑,⽽不去考虑语句效率问题,有可能导致严重的效率问题,导致功能不可⽤或者资源消耗过⼤。其中的⼀种情况是,处理每⽇增量数据的程序,实际执⾏过程中可能会进⾏全表扫描,效率与全量程序并⽆⼆致。
案例:
mio_log数据量:134,092,418条记录
freph_a01_fromtask3数据量:176,581,388条记录
⽣产系统上按照业务处理逻辑编写的SQL语句核⼼代码如下:
1SELECT (CASE
2 WHEN c.in_force_dateISNOT NULL
3 THEN (CASE
4 WHEN a.mio_date>=c.in_force_dateTHENa.mio_date
5 ELSE c.in_force_date
6 END )
7 WHEN c.in_force_dateISNULL THEN (CASE
8 WHEN a.mio_date>=a.plnmio_dateTHENa.mio_date
9 ELSE a.plnmio_date
10 END )
11 ELSE a.mio_date
12 END ) mio_date
13FROM dbo.mio_loga
14 INNER JOIN dbo.freph_a01_fromtask3c
15 ON atr_no = ctr_no
16 AND a.pol_code=c.pol_code
17WHERE ((c.in_force_dateISNOT NULL
18 AND((CASE
19 WHEN a.mio_date>=c.in_force_dateTHENa.mio_date
20 ELSE c.in_force_date
21 END ) BETWEEN @stat_begindateAND@stat_enddate))
22 OR(c.in_force_dateISNULL
23 AND((CASE
24 WHEN a.mio_date>=a.plnmio_dateTHENa.mio_date
25 ELSE a.plnmio_date
26 END ) BETWEEN @stat_begindateAND@stat_enddate)) )
导致虽然mio_log表的mio_date、plnmio_date字段,以及freph_a01_fromtask3表的in_force_date字段上均有索引,但是由于两表不同字段进⾏CASE WHEN⽐较,执⾏计划为聚集索引扫描:
优化思路:
由于mio_log表的mio_date、plnmio_date字段,以及freph_a01_fromtask3表的in_force_date字段上均有索引,可先通过单个
mio_date、in_force_date、plnmio_date索引取出增量时间段数据,在增量数据上进⾏不同表、字段的⽐对。
1SELECT (CASE
2 WHEN in_force_date IS NOT NULL
3 THEN ( CASE
4 WHEN mio_date >= in_force_dateTHENmio_date
5 ELSE in_force_date
6 END )
7 WHEN in_force_date IS NULL
8 THEN ( CASE
9 WHEN mio_date >= plnmio_dateTHENmio_date
10 ELSE plnmio_date
11 END )
12 ELSE mio_date
13 END ) mio_date
14from(
15 SELECT a.mio_date,
16 c.in_force_date,
17 a.plnmio_date,
18 a.MIO_LOG_ID
19 FROM dbo.mio_loga
20 INNER JOIN dbo.freph_a01_fromtask3c
21 ON atr_no = ctr_no
22 ANDa.pol_code=c.pol_code
23 WHERE
24 a.mio_dateBETWEEN@stat_begindateAND@stat_enddate
25union
26 SELECT a.mio_date,
27 c.in_force_date,
28 a.plnmio_date,
29 a.MIO_LOG_ID
30 FROM dbo.mio_loga
31 INNER JOIN dbo.freph_a01_fromtask3c
32 ON atr_no = ctr_no
33 ANDa.pol_code=c.pol_code
34 WHERE
35 c.in_force_dateBETWEEN@stat_begindateAND@stat_enddate
36union
37 SELECT a.mio_date,
38 c.in_force_date,
39 a.plnmio_date,
40 a.MIO_LOG_ID
41 FROM dbo.mio_loga
42 INNER JOIN dbo.freph_a01_fromtask3c
43 ON atr_no = ctr_no
44 ANDa.pol_code=c.pol_code
45 WHERE
46 a.plnmio_dateBETWEEN@stat_begindateAND@stat_enddate
47
48) T
49WHERE ((in_force_dateIS NOT NULL
50 AND((CASE
51 WHEN mio_date>= in_force_dateTHENmio_date
52 ELSE in_force_date
53 END ) BETWEEN @stat_begindateAND@stat_enddate))
54 OR(in_force_dateIS NULL
55 AND((CASE
56 WHEN mio_date>= plnmio_dateTHENmio_date
57 ELSE plnmio_date
58 END ) BETWEEN @stat_begindateAND@stat_enddate)) )该语句存在两个问题:
1. 如果⼦查询中mio_log、freph_a01_fromtask3没有主键,则需通过ROWID标识不同记录,即如果没有主键,可以通过ROWID进⾏替换。
ROWID这个概念在Oracle中⾮常重要,使⽤也⾮常⼴泛,其意义如下:
ROWIDPseudocolumn
Foreach row in the database, the ROWID pseudocolumn returns the address of therow. Oracle Database rowid values contain information necessary to locate arow:
· The dataobject number of the object
· The datablock in the datafile in which the row resides
· The positionof the row in the data block (first row is 0)
· The datafilein which the row resides (first file is 1). The file number is relative to thetablespace.
SQLServer中并没有ROWID这个概念, SQL Server2008及以后版本中%%physloc%%虚拟列与ROWID最相近,信息如下:
The closest equivalent tothis in SQL Server is the rid which has three components File:Page:Slot.
In SQL Server 2008 it ispossible to use the undocumented and unsupported %%physloc%% virtual column to see this. Thisreturns a binary(8) value with the Page ID in the firstfour bytes, then 2 bytes for File ID, followed by 2 bytes for the slot locationon the page.
The scalar function sys.fn_PhysLocFormatter or the sys.fn_PhysLocCracker TVF can be used to convert this into amore readable form.sql语句优化方式
1CREATE TABLET(XINT);
2
3INSERT INTOTVALUES(1),(2)
4
5SELECT %%physloc%%AS[%%physloc%%],
6 sys.fn_PhysLocFormatter(%%physloc%%)AS[File:Page:Slot]
7FROM T
%%physloc%%File:Page:Slot
0x7600000001000000(1:118:0)
0x7600000001000100(1:118:1)
Note that this is not leveraged by the queryprocessor. Whilst it is possible to use this in a WHERE clause
1SELECT *FROMT
2WHERE %%physloc%%=0x7600000001000000
SQL Server will not directly seek to thespecified row. Instead it will do a full table scan, evaluate %%physloc%% foreach row and return the one that matches (if any do).
2. 该语句有parameter sniffing问题:
当使⽤存储过程的时候,总是要使⽤到⼀些变量。变量有两种,⼀种是在存储过程的外⾯定义的,当调⽤存储过程的时候,必须要给它代⼊值,SQLServer在编译时知道它的值是多少。还有⼀种变量是在存储过程⾥⾯定义的。它的值是在存储过程的语句执⾏过程中得到的。对这种本地变量,SQLServer在编译时不知道它的值是多少。
SQLServer在处理存储过程时,为了节省编译时间,是⼀次编译多次使⽤的。那么计划重⽤就有两个潜在问题:
(1) 对于第⼀类变量,根据第⼀次运⾏时带⼊的值⽣成的执⾏计划,是不是就能够适合所有可能的变量值?
(2) 对于第⼆类本地变量,SQL Server在编译时并不知道它的值是多少,那怎么选择“合适”的执⾏计划?
parametersniffing”问题的定义:因为语句的执⾏计划对变量值很敏感,⽽导致重⽤执⾏计划会遇到性能问题。本地变量做出来的执⾏计划是⼀种⽐较“中庸”的⽅法,⼀般不会有parameter sniffing那么严重,很多时候,它还是解决parametersniffing的⼀个候选⽅案。
解决parameter sniffing问题的⽅法:
(1) ⽤exec()⽅式运⾏动态SQL语句:如果在存储过程⾥不是直接运⾏语句,⽽是把语句带上变量,⽣成⼀个字符串,再让exec()命令多动态语句运⾏,那SQL Server就会在运⾏到这个语句的时候,对动态语句进⾏编译。这时,SQLServer已经知道了变量的值,会根据值⽣成优化的执⾏计划,从⽽绕过parametersniffing问题。
(2) 使⽤本地变量:如果把变量值赋给⼀个本地变量,SQLServer在编译的时候是没有办法知道这个本地变量的值的。所以它会根据表格⾥数据的⼀般分布情况“猜测”⼀个返回值。不管⽤户在调⽤存储过程的时候带⼊的变量值是多少,做出来的执⾏计划都是⼀样的。⽽这样的执⾏计划⼀般⽐较“中庸”,不会是最优的执⾏计划,但是对⼤多数变量值来讲,也不会是⼀个很差的执⾏计划。该⽅法的好处是保持了存储过程的优点,缺点是要修改存储过程,⽽执⾏计划也不是最优的。
(3) 在语句⾥使⽤query hint指定执⾏计划:
在SELECT、INSERT、UPDATE、DELETE语句最后,可以加⼀个“Option(<query_hint>)”⼦句,对SQL Server将要⽣成的执⾏计划进⾏指导。⽬前的query_hint很强⼤,有⼗⼏种hint。完整的定义如下:
1<query_hint>::=
2{ {HASH| ORDER } GROUP
3 | {CONCAT| HASH | MERGE} UNION
4 | {LOOP| MERGE | HASH} JOIN
5 | FASTnumber_rows
6 | FORCEORDER
7 | MAXDOPnumber_of_processors
8 | OPTIMIZEFOR( @vaariable_name= literal_constant[ , ...n ])
9 | PARAMETERIZATION{SIMPLE | FORCED }
10 | RECOMPILE
11 | ROBUSTPLAN
12 | KEEPPLAN
13 | KEEPFIXEDPLAN
14 | EXPANDVIEWS
15 | MAXRECURSIONnumber
16 | USEPLANN'xml_plan'
17}
这些hint的⽤途不⼀样。有些是引导执⾏计划使⽤什么样的运算的,例如{HASH|ORDER}GROUP、{CONCAT|HASH|MERGE} UNION、{LOOP|MERGE|HASH}JOIN。有些是防⽌重编译的,例如PARAMETERIZATION{SIMPLE|FORCED
}、KEEPPLAN、KEEPFIXEDPLAN,有些是强制重编译的,如RECOMPILE。有些是影响执⾏计划的选择的,
如FAST number_rows、FORCEORDER、MAXDOP number_of_processors、OPTIMIZE FOR(@vaariable_name=
literal_constant[ , ...n ]),它们是和在不同的场合。具体定义参见SQL Server联机帮助。
为避免parameter sniffing问题,主要有以下⼏种常见query hint
(1)Recompile
Recompile这个查询提⽰告诉SQL Server,语句在每⼀次存储过程运⾏的时候,都要重新编译⼀下。这样就能够使SQL Server根据当前变量的值,选⼀个最好的执⾏计划。对前⾯的那个例⼦,我们可以这么改写。
1CREATE PROCNosniff_queryhint_recompile(@iINT)
2AS
3 SELECT Count(b.SalesOrderID),
4 Sum(p.Weight)
5 FROM dbo.SalesOrderHeader_testa
6 INNER JOIN dbo.SalesOrderDetail_testb
7 ON a.SalesOrderID=b.SalesOrderID
8 INNER JOIN Production.Productp
9 ON b.ProductID=p.ProductID
10 WHERE a.SalesOrderID=@i
11 OPTION (recompile)
12go
和这种⽅法类似的,是在存储过程的定义⾥直接指定"recompile",也能达到避免parameter sniffing的效果。
1CREATE PROCNosniff_spcreate_recompile(@iINT)
2WITH recompile
3AS
4 SELECT Count(b.SalesOrderID),
5 Sum(p.Weight)
6 FROM dbo.SalesOrderHeader_testa
7 INNER JOIN dbo.SalesOrderDetail_testb
8 ON a.SalesOrderID=b.SalesOrderID
9 INNER JOIN Production.Productp
10 ON b.ProductID=p.ProductID
11 WHERE a.SalesOrderID=@i
12
13go
(2) 指定JOIN运算
1CREATE PROCNosniff_queryhint_joinhint(@iINT)
2AS
3 SELECT Count(b.SalesOrderID),
4 Sum(p.Weight)
5 FROM dbo.SalesOrderHeader_testa
6 INNER JOIN dbo.SalesOrderDetail_testb
7 ON a.SalesOrderID=b.SalesOrderID
8 INNER hash JOIN Production.Productp
9 ON b.ProductID=p.ProductID
10 WHERE a.SalesOrderID=@i
11go
(3) OPTIMIZEFOR(@variable_name= literal_constant[ , …n] )
使⽤OPTIMIZE FOR 这个查询指导,就能够让SQL Server做到这⼀点。这是SQL 2005以后的⼀个新功能。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论