实战:上亿数据如何秒查?

实战:上亿数据如何秒查?

oschina 发布于: 2016年07月01日 ( 30评)
分享到: 
收藏 +281

数据背景

首先,项目是西门子中国在我司实施部署的MES项目,由于项目是在产线上运作(3 years+),数据累积很大。在项目的数据库中,大概上亿条数据的表有5个以上,千万级数据的表10个以上,百万级数据的表,很多...

(历史问题,当初实施无人监管,无人监控数据库这块的性能问题。ps:我刚入职不久...)

不多说,直接贴西门子中国的开发人员在我司开发的SSRS报表中的SQL语句:

?
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
57
58
59
60
61
62
63
     select  distinct  b.MaterialID  as  matl_def_id, c.Descript,  case  when  right (b.MESOrderID, 12) <  '001000000000'  then  right (b.MESOrderID, 9)  
 
     else  right (b.MESOrderID, 12)  end   as  pom_order_id, a.LotName, a.SourceLotName  as  ComLot, 
 
     e.DefID  as  ComMaterials, e.Descript  as  ComMatDes, d.VendorID, d.DateCode,d.SNNote, b.OnPlantID,a.SNCUST
 
     from  
 
     (
 
         select  m.lotname, m.sourcelotname, m.opetypeid, m.OperationDate,n.SNCUST  from  View1 m
 
         left  join  co_sn_link_customer  as  on  n.SNMes=m.LotName
 
         where 
 
         ( m.LotName  in  ( select  val  from  fn_String_To_Table(@sn, ',' ,1))  or  (@sn) =  '' and 
 
         ( m.sourcelotname  in  ( select  val  from  fn_String_To_Table(@BatchID, ',' ,1))  or  (@BatchID) =  '' )
 
         and  (n.SNCust  like  '%' + @SN_ext +  '%'  or  (@SN_ext)= '' )
 
     ) a
 
     left  join 
 
     (
 
         select  from  Table1  where  SNType =  'IntSN'
 
         and  SNRuleName =  'ProductSNRule'
 
         and  OnPlantID=@OnPlant
 
     ) b  on  b.SN = a.LotName
 
     inner  join  MMdefinitions  as  on  c.DefID = b.MaterialID
 
     left  join   Table1  as  on  d.SN = a.SourceLotName 
 
     inner  join  MMDefinitions  as  on  e.DefID = d.MaterialID
 
     where  not  exists (
 
      select  distinct  LotName, SourceLotName  from  ELCV_ASSEMBLE_OPS 
 
     where  LotName = a.SourceLotName  and  SourceLotName = a.LotName
 
    
 
     and  (d.DateCode  in  ( select  val  from  fn_String_To_Table(@DCode, ',' ,1))  or  (@DCode) =  '' )
 
     and  (d.SNNote   like  '%' +@SNNote+ '%'  or  (@SNNote) =  '' )
 
     and  (( case  when  right (b.MESOrderID, 12) <  '001000000000'  then  right (b.MESOrderID, 9)  
 
     else  right (b.MESOrderID, 12)  end in  ( select  val  from  fn_String_To_Table(@order_id, ',' ,1))  or  (@order_id) =  '' )
 
     and  (e.DefID  in  ( select  val  from  fn_String_To_Table(@comdef, ',' ,1))  or  (@comdef) =  '' )
 
     --View1是一个嵌套两层的视图(出于保密性,实际名称可能不同),里面有一张上亿数据的表和几张千万级数据的表做左连接查询
 
     --Table1是一个数据记录超过1500万的表

这个查询语句,实际上通过我的检测和调查,在B/S系统前端已无法查出结果,半小时,一小时 ... 。因为我直接在SQL查询分析器查,半小时都没有结果。

(原因是里面对一张上亿级数据表和3张千万级数据表做全表扫描查询)

不由感慨,西门子中国的素质(或者说责任感)就这样?

下面说说我的分析和走的弯路(思维误区),希望对你也有警醒。

探索和误区

首先相关表的索引,没有建全的,把索引给建上。

索引这步完成后,发现情况还是一样,查询速度几乎没有改善。后来想起相关千万级数据以上的表,都还没有建立表分区。于是考虑建立表分区以及数据复制的方案。

这里有必要说明下:我司报表用的是一个专门的数据库服务器,数据从产线订阅而来。就是常说的“读写分离”。

如果直接在原表上建立表分区,你会发现执行表分区的事物会直接死锁。原因是:表分区操作本身会锁表,产线还在推数据过来,这样很容易“阻塞”,“死锁”。

我想好的方案是:建立一个新表(空表),在新表上建好表分区,然后复制数据过来。

正打算这么干。等等!我好像进入了一个严重的误区!

分析: 原SQL语句和业务需求,是对产线的数据做产品以及序列号的追溯,关键是查询条件里没有有规律的"条件"(如日期、编号),贸然做了表分区,在这里几乎没有意义!反而会降低查询性能!

好险!还是一步一步来,先做SQL语句分析。

一、对原SQL语句的分析

1、查询语句的where条件,有大量@var in ... or (@var ='') 的片段

2、where条件有like '%'+@var+'%'

3、where条件有 case ... end 函数

4、多次连接同一表查询,另外使用本身已嵌套的视图表,是不是必须,是否可替代?

5、SQL语句有*号,视图中也有*号出现

二、优化设计

首先是用存储过程改写,好处是设计灵活。

核心思想是:用一个或多个查询条件(查询条件要求至少输入一个)得到临时表,每个查询条件如果查到集合,就更新这张临时表,最后汇总的时候,只需判断这个临时表是否有值。以此类推,可以建立多个临时表,将查询条件汇总。

这样做目前来看至少两点好处:

1、省去了对变量进行 =@var or (@var='')的判断;

2、抛弃sql拼接,提高代码可读性。

再有就是在书写存储过程,这个过程中要注意:

1、尽量想办法使用临时表扫描替代全表扫描;

2、抛弃in和not in语句,使用exists和not exists替代;

3、和客户确认,模糊查询是否有必要,如没有必要,去掉like语句;

4、注意建立适当的,符合场景的索引;

5、踩死 "*" 号;

6、避免在where条件中对字段进行函数操作;

7、对实时性要求不高的报表,允许脏读(with(nolock))。

三、存储过程

如果想参考优化设计片段的详细内容,请参阅SQL代码:

?
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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
/**
  * 某某跟踪报表
  **/
--exec spName1 '','','','','','','公司代号'
CREATE  Procedure  spName1
    @MESOrderID nvarchar(320),  --工单号,最多30个
    @LotName nvarchar(700),     --产品序列号,最多50个
    @DateCode nvarchar(500),    --供应商批次号,最多30个
    @BatchID nvarchar(700),     --组装件序列号/物料批号,最多50个
    @comdef nvarchar(700),      --组装件物料编码,最多30个
    @SNCust nvarchar(1600),     --外部序列号,最多50个
    @OnPlant nvarchar(20)       --平台
AS
BEGIN
     SET  NOCOUNT  ON ;  
     /**
      * 1)定义全局的临时表,先根据六个查询条件的任意一个,得出临时表结果
      **/
     CREATE  TABLE  #FinalLotName
     (
         LotName NVARCHAR(50),        --序列号
         SourceLotName NVARCHAR(50),  --来源序列号
         SNCust NVARCHAR(128)         --外部序列号
     )
     --1.1
     IF @LotName<> ''
     BEGIN
         SELECT  Val  INTO  #WorkLot  FROM  fn_String_To_Table(@LotName, ',' ,1)
         SELECT  LotPK,LotName  INTO  #WorkLotPK  FROM  MMLots  WITH (NOLOCK)  WHERE  EXISTS( SELECT  FROM  #WorkLot b  WHERE  b.Val=MMLots.LotID)
         
         --求SourceLotPK只能在这里求
         SELECT  a.LotPK,a.SourceLotPK  into  #WorkSourcePK  FROM  MMLotOperations a  WITH (NOLOCK)  WHERE  EXISTS( SELECT  FROM  #WorkLotPK b  WHERE  b.LotPK=a.LotPK)  AND  a.SourceLotPK  IS  NOT  NULL
         
         SELECT  a.LotPK,a.SourceLotPK,b.LotName  INTO  #WorkSourcePK2  FROM  #WorkSourcePK a  JOIN  #WorkLotPK b  ON  a.LotPK=b.LotPK
         
         INSERT  INTO  #FinalLotName  SELECT  a.LotName,b.LotName  AS  SourceLotName, NULL  FROM  #WorkSourcePK2 a  JOIN  ( SELECT  LotPK,LotName  FROM  MMLots  WITH (NOLOCK) ) b  on  a.SourceLotPK=b.LotPK  --b的里面加不加WHERE RowDeleted=0待确定
         SELECT  a.LotName,a.SourceLotName,b.SNCust  INTO  #FinalLotNameX1  FROM  #FinalLotName a  LEFT  JOIN  CO_SN_LINK_CUSTOMER b  WITH (NOLOCK)  ON  a.LotName=b.SNMes
         DELETE  FROM  #FinalLotName
         INSERT  INTO  #FinalLotName  SELECT  LotName,SourceLotName,SNCust  FROM  #FinalLotNameX1
     END
     --1.2
     IF @BatchID<> ''
     BEGIN
         SELECT  Val  INTO  #WorkSourceLot  FROM  fn_String_To_Table(@BatchID, ',' ,1)
         IF EXISTS( SELECT  FROM  #FinalLotName) --如果@LotName也不为空
         BEGIN
             SELECT  a.LotName,a.SourceLotName,a.SNCust  INTO  #FinalLotNameX2  FROM  #FinalLotName a  WHERE  EXISTS( SELECT  FROM  #WorkSourceLot b  WHERE  a.SourceLotName=b.Val)
             DELETE  FROM  #FinalLotName
             INSERT  INTO  #FinalLotName  SELECT  LotName,SourceLotName,SNCust  FROM  #FinalLotNameX2
         END
         ELSE  --@LotName条件为空
         BEGIN
             SELECT  LotPK  AS  SourceLotPK,LotName  AS  SourceLotName  INTO  #2  FROM  MMLots  WITH (NOLOCK)  WHERE  EXISTS( SELECT  FROM  #WorkSourceLot b  WHERE  b.Val=MMLots.LotID)
             SELECT  a.LotPK,a.SourceLotPK  into  #21  FROM  MMLotOperations a  WITH (NOLOCK)  WHERE  EXISTS( SELECT  FROM  #2 b  WHERE  b.SourceLotPK=a.SourceLotPK)
             SELECT  a.LotPK,a.SourceLotPK,b.SourceLotName  INTO  #22  FROM  #21 a  JOIN  #2 b  ON  a.SourceLotPK=b.SourceLotPK    
             INSERT  INTO  #FinalLotName  SELECT  b.LotName,a.SourceLotName, NULL  FROM  #22 a  JOIN  ( SELECT  LotPK,LotName  FROM  MMLots  WITH (NOLOCK) ) b  on  a.LotPK=b.LotPK  --b的里面加不加WHERE RowDeleted=0待确定    
             SELECT  a.LotName,a.SourceLotName,b.SNCust  INTO  #FinalLotNameX21  FROM  #FinalLotName a  LEFT  JOIN  CO_SN_LINK_CUSTOMER b  WITH (NOLOCK)  ON  a.LotName=b.SNMes
             DELETE  FROM  #FinalLotName
             INSERT  INTO  #FinalLotName  SELECT  LotName,SourceLotName,SNCust  FROM  #FinalLotNameX21        
         END
     END
     --1.3
     IF @SNCust<> ''
     BEGIN
         SELECT  Val  INTO  #WorkCustomSN  FROM  fn_String_To_Table(@SNCust, ',' ,1)
         IF EXISTS( SELECT  FROM  #FinalLotName) --前面两个条件至少有一个有值
         BEGIN
             SELECT  a.LotName,a.SourceLotName,a.SNCust  INTO  #FinalLotNameX3  FROM  #FinalLotName a  WHERE  EXISTS( SELECT  FROM  #WorkCustomSN b  WHERE  a.SNCust=b.Val)
             DELETE  FROM  #FinalLotName 
             INSERT  INTO  #FinalLotName  SELECT  LotName,SourceLotName,SNCust  FROM  #FinalLotNameX3
         END
         ELSE
         BEGIN
             SELECT  a.SNMes  INTO  #WorkLotX  FROM  CO_SN_LINK_CUSTOMER a  WITH (NOLOCK)  WHERE  EXISTS( SELECT  FROM  #WorkCustomSN b  WHERE  a.SNCust=b.Val)
             -------------------以下逻辑和变量1(@LotName)类似[先根据外部序列号求解序列号,再照搬第一个判断变量的方式]
             SELECT  LotPK,LotName  INTO  #WorkLotPKX  FROM  MMLots  WITH (NOLOCK)  WHERE  EXISTS( SELECT  FROM  #WorkLotX b  WHERE  b.SNMes=MMLots.LotID)
         
             --求SourceLotPK只能在这里求
             SELECT  a.LotPK,a.SourceLotPK  into  #WorkSourcePKX  FROM  MMLotOperations a  WITH (NOLOCK)  WHERE  EXISTS( SELECT  FROM  #WorkLotPKX b  WHERE  b.LotPK=a.LotPK)  AND  a.SourceLotPK  IS  NOT  NULL
             
             SELECT  a.LotPK,a.SourceLotPK,b.LotName  INTO  #WorkSourcePK2X  FROM  #WorkSourcePKX a  JOIN  #WorkLotPKX b  ON  a.LotPK=b.LotPK
             
             INSERT  INTO  #FinalLotName  SELECT  a.LotName,b.LotName  AS  SourceLotName, NULL  FROM  #WorkSourcePK2X a  JOIN  ( SELECT  LotPK,LotName  FROM  MMLots  WITH (NOLOCK) ) b  on  a.SourceLotPK=b.LotPK  --b的里面加不加WHERE RowDeleted=0待确定
             SELECT  a.LotName,a.SourceLotName,b.SNCust  INTO  #FinalLotNameX31  FROM  #FinalLotName a  LEFT  JOIN  CO_SN_LINK_CUSTOMER b  WITH (NOLOCK)  ON  a.LotName=b.SNMes
             DELETE  FROM  #FinalLotName
             INSERT  INTO  #FinalLotName  SELECT  LotName,SourceLotName,SNCust  FROM  #FinalLotNameX31
             -----------------------
         END
     END
     
     /**
      * 2)定义全局的临时表,用于替换第一个全局临时表。
      **/
     CREATE  TABLE  #FinalCO_SN
     (
         SN NVARCHAR(50),
         SourceSN NVARCHAR(50),
         SNCust NVARCHAR(128),
         matl_def_id NVARCHAR(50), --sn的物料ID
         ComMaterials NVARCHAR(50),   --SourceSN的物料ID
         MESOrderID NVARCHAR(20),
         OnPlantID NVARCHAR(20),
         VendorID NVARCHAR(20),
         DateCode NVARCHAR(20) ,
         SNNote NVARCHAR(512)
     )
     --2.1
     IF @MESOrderID<> ''
     BEGIN
         -------------------------------将MESOrderID做特殊处理-----------------------------------
         SELECT  Val  INTO  #WorkMESOrderID  FROM  fn_String_To_Table(@MESOrderID, ',' ,1)
         IF @OnPlant= 'Comba'
         BEGIN
             UPDATE  #WorkMESOrderID  SET  Val= 'C000' +Val  WHERE  LEN(Val)=9
         END
         ELSE
         BEGIN
             UPDATE  #WorkMESOrderID  SET  Val= 'W000' +Val  WHERE  LEN(Val)=9
         END
         SELECT  SN,MaterialID,MESOrderID,OnPlantID  INTO  #WorkCO_SN1  FROM  CO_SN_GENERATION a  WITH (NOLOCK)
         WHERE  SNType= 'IntSN'  AND  SNRuleName =  'ProductSNRule'  AND  OnPlantID=@OnPlant
         AND  EXISTS( SELECT  FROM  #WorkMESOrderID b  WHERE  a.MESOrderID=b.Val)
         ------------------------------------------------------------------------------------------
         --条件判断(逻辑分析)开始
         IF EXISTS( SELECT  FROM  #FinalLotName) --如果前面判断的查询条件有值
         BEGIN
             --查出SourceLotName对应的查询字段
             SELECT  a.SN  AS  SourceLotName,a.VendorID,a.DateCode,a.SNNote,a.MaterialID  AS  ComMaterials  INTO  #SourceLotNameTable  FROM  CO_SN_GENERATION a  WITH (NOLOCK)  WHERE  EXISTS( SELECT  FROM  #FinalLotName b  WHERE  a.SN=b.SourceLotName)
             
             INSERT  INTO  #FinalCO_SN
             SELECT  a.LotName,a.SourceLotName,d.SNCust,b.MaterialID,c.ComMaterials,b.MESOrderID,b.OnPlantID,c.VendorID,c.DateCode,c.SNNote  FROM  #FinalLotName a 
             LEFT  JOIN  #WorkCO_SN1 b  ON  a.LotName=b.SN
             LEFT  JOIN  #SourceLotNameTable c  ON  a.SourceLotName=c.SourceLotName
             LEFT  JOIN  CO_SN_LINK_CUSTOMER d  WITH

你可能感兴趣的:(实战:上亿数据如何秒查?)