En dépit du titre de ce billet, les questions que je me pose en fait sont en bas.
J'ai souvent lu des avertissements contre le RBAR ainsi que le conseil d'éviter APPLY tant que se faire se peut
Pourtant il m'est arrivé de noter un gain de performance très important en utilisant APPLY alors qu'un équivalent par jointure traditionnel était aussi possible.
Malheureusement, il ne m'est pas possible de mettre ici certains exemples bien réels (et tout aussi concrets) que j'ai pu croisé.
Mais je crois être parvenu à faire un petit script qui permet de tester un peu cela:
Création de table
Tests
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56 SET NOCOUNT ON CREATE TABLE t20130712_tmp ( ID INT UNIQUE , X INT IDENTITY ) GO CREATE TABLE t20130712_tmp2 ( ID INT , X INT IDENTITY , UNIQUE (ID, X) ) GO ; WITH T(n) AS ( SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM master.dbo.spt_values AS S ) , tally(n) AS ( SELECT (T2.n - 1) * 100 + T1.n FROM T AS T1 CROSS JOIN T AS T2 WHERE T1.n <= 100 AND T2.n <= 100 ) INSERT t20130712_tmp SELECT n FROM tally AS T1 WHERE n < 10000 GO ; WITH T(n) AS ( SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM master.dbo.spt_values AS S ) , tally(n) AS ( SELECT (T2.n - 1) * 100 + T1.n FROM T AS T1 CROSS JOIN T AS T2 WHERE T1.n <= 100 AND T2.n <= 100 ) INSERT t20130712_tmp2 SELECT T1.n FROM tally AS T1 CROSS JOIN T AS T2 WHERE T1.n < 10000 AND T1.n % 3 <> 0 AND T2.n < 1 + T1.n % 15 GO
Quand je vérifie les query plan, SQL Server indique clairement que la solution avec OUTER APPLY coûte plus que celle avec LEFT JOIN.
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44 SET STATISTICS IO ON SET STATISTICS TIME ON SELECT T1.* , T2.X FROM t20130712_tmp AS T1 OUTER APPLY ( SELECT * FROM t20130712_tmp2 AS T2 WHERE T2.ID = T1.ID AND T1.X % 14 = 0 UNION ALL SELECT * FROM t20130712_tmp2 AS T2 WHERE T2.ID = T1.ID + 1 AND T1.X % 14 = 1 ) AS T2 ORDER BY T1.ID SELECT T1.* , ISNULL(T2.X, T3.X) AS X FROM t20130712_tmp AS T1 LEFT JOIN t20130712_tmp2 AS T2 ON ( T2.ID = T1.ID AND T1.X % 14 = 0 ) LEFT JOIN t20130712_tmp2 AS T3 ON ( T3.ID = T1.ID + 1 AND T1.X % 14 = 1 ) ORDER BY T1.ID SET STATISTICS TIME OFF SET STATISTICS IO OFF
D'ailleurs quand on regarde le IO, la solution avec APPLY fait beacoup plus de reads.
Pourtant quand on se penche sur les durées la solution OUTER APPLY est légèrement plus rapide (et j'ai déjà fait face à des scénarios (impossibles a restitué ici) où la différence était énorme).
Du coup, je me demande.
- Est-ce que je donne trop d'importance aux durées et je sous-estime un autre point (peut-être à l'égard des LOCK) ?
- Est-ce qu'il y a une autre et meilleure façon d'écrire le query sans APPLY ?
Partager