【原創】一個億級數據庫優化過程


第一部分 棉花數據庫問題和分析

1.問題sql

數據庫的版本是9i,問題sql有兩個:

Sql1:

SELECT

 c_lotno

         FROM b_ctn_normal

 WHERE d_prodatetime BETWEEN to_date('2011-07-01', 'yyyy-mm-dd HH24:MI:SS') AND

                             to_date('2012-07-03', 'yyyy-mm-dd HH24:MI:SS')

                             AND n_madein = 65

                             AND rownum < 31

Sql2:

SELECT count(c_bale)

         FROM b_ctn_normal

 WHERE d_prodatetime BETWEEN to_date('2011-07-01', 'yyyy-mm-dd HH24:MI:SS') AND

                             to_date('2012-07-03', 'yyyy-mm-dd HH24:MI:SS')

 

這倆sql其實非常簡單,就是一個按照時間的分頁查詢,一個查詢時間范圍內的總數據量。

但是這個表的數據量很大,41803656條數據,單表容量超過21G。因此查詢非常慢,僅僅查詢30條數據就需要耗費十幾分鍾。甚至查不出結果。

 

2 表概況

表b_ctn_normal是一個分區表,按照D_VERIFYDATETIME進行了range分區,分區的策略為2010年前每年一個分區,2010年后每月一個分區.該表的數據量為41803656條。

partition by range (D_VERIFYDATETIME)

partition PART_20080101 values less than (TO_DATE(' 2008-01-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS', 'NLS_CALENDAR=GREGORIAN'))

  partition PART_20090101 values less than (TO_DATE(' 2009-01-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS', 'NLS_CALENDAR=GREGORIAN'))后續的省略

 

另外該表上建了大量的索引,見表1:

3.表存在的問題

以下是索引的概況統計信息。

 

NDEX_NAME

DISTINCT_KEYS

NUM_ROWS

SAMPLE_SIZE

I_CTN_NORMAL_99

4

41805079.0630631

8459008

I_NORMAL_MADEIN1

17

41804897.3546108

9544621

I_CTN_NORMAL_66

80

41767143.7096454

9580744

I_CTN_NORMAL_77

86

41473125.0963043

9479665

I_CTN_NORMAL_22

366

41875654.4438373

9937607

I_CTN_NORMAL_11

889

41867424.9314059

11007070

I_CTN_NORMAL_55

1957

40169648.695544

9058949

I_NORMAL_UPLOADTIME1

17253

41866396.7193889

10087608

I_CTN_NORMAL_33

384472

41842227.8696896

10621842

I_NORMAL_D_COLORGRADETIME

1485863

41727490.2734861

9119757

GLOBAL_INDEX_D_VERIFYDATETIME

21162573

41804256

9592128

PRIMARY1_ID

41804473

41804473

9540784

UNI_NORMAL_C_BALE1

41841809

41841809.1781587

7023237

【表1】索引概況

 

l  表不是很寬,但是竟然建了13個索引,而且8個索引可選性很差,每個索引都占據不少段空間,極大的浪費了存儲空間。

l  索引沒有分區,千萬級別的數據量,本身查找索引就很耗時,因此應當對索引分區其高索引檢索性能。

l  表的索引和表應該建立在不同的表空間分開存放,同時表的分區在不同的表空間存放。

l  分區的記錄不均勻,分區不合理

分區的統計信息顯示,大量的數據集中在了PART_20100101和PART_20090101分區,分區很不合理,大大削弱了分區表的作用。應該對分區進行細粒度的划分,均勻分布數據。

TABLE_NAME

PARTITION_NAME

NUM_ROWS

SAMPLE_SIZE

B_CTN_NORMAL

PART_20100101

15580400

2883811

B_CTN_NORMAL

PART_20090101

13007483

2420607

B_CTN_NORMAL

PART_20101201

3809673

709735

B_CTN_NORMAL

PART_20110101

2656138

494675

B_CTN_NORMAL

PART_20101101

2641196

492471

B_CTN_NORMAL

PART_20110201

1169697

217919

B_CTN_NORMAL

PART_20100201

1106187

205854

B_CTN_NORMAL

PART_20110401

662618

123426

B_CTN_NORMAL

PART_20110301

271190

50600

B_CTN_NORMAL

PART_20100501

205173

37933

B_CTN_NORMAL

PART_20100401

194223

35804

B_CTN_NORMAL

PART_20100601

154195

28641

B_CTN_NORMAL

PART_20110501

137085

25587

B_CTN_NORMAL

PART_20100301

105747

19575

B_CTN_NORMAL

PART_20101001

64424

11960

B_CTN_NORMAL

PART_20100701

33743

6206

B_CTN_NORMAL

PART_20100801

4044

725

B_CTN_NORMAL

PART_20100901

283

283

B_CTN_NORMAL

PART_20111001

80

80

B_CTN_NORMAL

PART_20080101

53

53

B_CTN_NORMAL

PART_20111201

21

21

B_CTN_NORMAL

PART_20120601

2

2

B_CTN_NORMAL

PART_20120301

1

1

B_CTN_NORMAL

PART_20110601

0

 

B_CTN_NORMAL

PART_20110701

0

 

B_CTN_NORMAL

PART_20120401

0

 

B_CTN_NORMAL

PART_20120801

0

 

B_CTN_NORMAL

PART_20120701

0

 

B_CTN_NORMAL

PART_20120501

0

 

B_CTN_NORMAL

PART_20120201

0

 

B_CTN_NORMAL

PART_20120101

0

 

B_CTN_NORMAL

PART_20111101

0

 

B_CTN_NORMAL

PART_20110801

0

 

B_CTN_NORMAL

PART_20110901

0

 

 

注:以上的統計信息都是最新收集的.

4.分析制定策略

結合問題sql發現, 兩個查詢共同依賴於d_prodatetime字段的過濾,但是該字段重復值很多,根據統計信息,該列的NUM_DISTINCT為456,因此只依靠索引沒有意義,CBO不會選擇索引而是全表掃描。執行這兩個查詢的時候對數據沒有區分度,選擇了4K萬數據進行全表掃描,效率可寫而至。

而該查詢較為簡單,經過仔細的分析並研究目前的分區策略,我認為最佳的策略是增加范圍分區字段,將表重新分區,分區條件納入d_prodatetime字段。這樣查詢時可以以d_prodatetime進行分區裁減從而減少掃描的數據量。需要分析字段的值的分布區間,平均分配到各分區。經過分析表的數據分布,從節省時間上考慮,就以每兩個月為一個范圍進行分區。

partition by range (D_VERIFYDATETIME, d_prodatetime)

partition PART_20080101 values less than (TO_DATE(' 2008-01-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS', 'NLS_CALENDAR=GREGORIAN'), TO_DATE(' 2008-01-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS', 'NLS_CALENDAR=GREGORIAN'))

  partition PART_20090101 values less than (TO_DATE(' 2009-01-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS', 'NLS_CALENDAR=GREGORIAN'), TO_DATE(' 2009-01-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS', 'NLS_CALENDAR=GREGORIAN'))

 

考慮到sql1中有對n_madein的過濾,分析該字段,字段值總共為17種,為number類型,而且該字段值在字段D_VERIFYDATETIME, d_prodatetime表示的區間中分布很均勻,因此,在上述分區基礎上增加list分區,使分區策略變為Rang-list復合分區:

partition by range (D_VERIFYDATETIME, d_prodatetime)

subpartition by list(N_MADEIN)

partition PART_20080101 values less than (range1,range2)

(

subpartition p31 values(64),

 subpartition p32 values(37),

 subpartition p33 values(48),

 subpartition p34 values(55),

……………

),

  partition PART_20090101 values less than (range3,range4)

(

subpartition p31 values(64),

 subpartition p32 values(37),

 subpartition p33 values(48),

 subpartition p34 values(55),

…………..

),

)

 

這樣,sql1在執行時,會首先根據d_prodatetime裁減掉部分數據,然后再根據N_MADEIN再次裁減掉一部分,這樣sql1的性能應該會得到很大提升。而對於僅僅含有N_MAADIN的過濾條件的查詢,都會進行分區裁減,減少數據量。具體性能提高多少,需要測試。

同時,之前的分區字段D_VERIFYDATETIME的粒度應該適當的減小。因為d_prodatetime的重復值較多,以之前的分區粒度,2010年前的是每年一個分區,這樣會導致d_prodatetime分區后數據會很不均勻,若查詢2010年之前的數據,則d_prodatetime裁減的效果會不好,因此需要考慮d_prodatetime的字段值,重新規划分區粒度。分區粒度的大小需要考慮到d_prodatetime的范圍分布情況。通過分析決定和d_prodatetime字段使用同樣的分區范圍。

 

由於需要對表重新分區,因此需要重建表。如果在已有的分區策略下增加分區,則直接alter表即可,oracle提供了豐富的方法為不同的分區增加新的分區;但是修改分區策略,必須重建表。而表數據量巨大,單表超過20G,因此數據的加載成為頭疼的問題,如果在生產環境,產生的日志也很巨大。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

第二部分 試驗過程結果及分析

為了能試驗本文預測的效果,於是我在我本機騰出了30G的空間,將庫置於非歸檔模式,然后用database link以insert append的方式直接加載數據。通過仔細的權衡,我創建的分區表如下(限於時間關系,將所有表分區和索引分區建在一個表空間內):

--ddl過長,省略(見附件)

(注:復合分區可以為二級分區創建一個template,從而減少建表DDL的篇幅)

考慮只有本文開頭的兩個查詢問題突出,因此我只建立了倆索引,選擇了全局范圍分區索引。

--創建分區索引GLOBAL_INDEX_D_VERIFYDATETIME

CREATE INDEX GLOBAL_INDEX_D_VERIFYDATETIME ON b_ctn_normal(D_VERIFYDATETIME)

   GLOBAL PARTITION BY RANGE(D_VERIFYDATETIME)(

partition part_index_0 values less than(to_date('2008-03-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS')),

partition part_index_1 values less than(to_date('2008-05-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS')),

partition part_index_2 values less than(to_date('2008-07-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS')),

partition part_index_3 values less than(to_date('2008-09-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS')),

partition part_index_4 values less than(to_date('2008-11-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS')),

partition part_index_5 values less than(to_date('2009-01-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS')),

partition part_index_6 values less than(to_date('2009-03-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS')),

partition part_index_7 values less than(to_date('2009-05-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS')),

partition part_index_8 values less than(to_date('2009-07-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS')),

partition part_index_9 values less than(to_date('2009-09-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS')),

partition part_index_10 values less than(to_date('2009-11-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS')),

partition part_index_11 values less than(to_date('2010-01-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS')),

partition part_index_12 values less than(to_date('2010-03-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS')),

partition part_index_13 values less than(to_date('2010-05-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS')),

partition part_index_14 values less than(to_date('2010-07-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS')),

partition part_index_15 values less than(to_date('2010-09-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS')),

partition part_index_16 values less than(to_date('2010-11-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS')),

partition part_index_17 values less than(to_date('2011-01-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS')),

partition part_index_18 values less than(to_date('2011-03-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS')),

partition part_index_19 values less than(to_date('2011-05-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS')),

partition part_index_20 values less than(to_date('2011-07-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS')),

partition part_index_21 values less than(to_date('2011-09-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS')),

partition part_index_22 values less than(to_date('2011-11-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS')),

partition part_index_23 values less than(to_date('2012-01-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS')),

partition part_index_24 values less than(to_date('2012-03-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS')),

partition part_index_25 values less than(to_date('2012-05-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS')),

partition part_index_26 values less than(to_date('2012-07-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS')),

partition part_index_27 values less than(to_date('2012-09-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS')),

partition part_index_28 values less than(to_date('2012-11-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS')),

partition part_index_29 values less than(maxvalue)

)

注:分區索引也可以使用本地前綴索引,可以減少DDL篇幅

考慮到D_PRODATETIME的值較多,當查詢來臨時,oracle首先以查詢where條件中的D_PRODATETIME進行裁減,到目標分區之后,如果有索引的話,應該可以進行INDEX RANGE SCAN進行掃描,因此先建立分區索引試試。同樣選擇了全局范圍分區索引。

--創建分區索引GLOBAL_INDEX_D_PRODATETIME

CREATE INDEX GLOBAL_INDEX_D_PRODATETIME ON b_ctn_normal(D_PRODATETIME)

   GLOBAL PARTITION BY RANGE(D_PRODATETIME)(

partition part_index1_0 values less than(to_date('2008-03-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS')),

partition part_index1_1 values less than(to_date('2008-05-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS')),

partition part_index1_2 values less than(to_date('2008-07-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS')),

partition part_index1_3 values less than(to_date('2008-09-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS')),

partition part_index1_4 values less than(to_date('2008-11-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS')),

partition part_index1_5 values less than(to_date('2009-01-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS')),

partition part_index1_6 values less than(to_date('2009-03-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS')),

partition part_index1_7 values less than(to_date('2009-05-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS')),

partition part_index1_8 values less than(to_date('2009-07-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS')),

partition part_index1_9 values less than(to_date('2009-09-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS')),

partition part_index1_10 values less than(to_date('2009-11-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS')),

partition part_index1_11 values less than(to_date('2010-01-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS')),

partition part_index1_12 values less than(to_date('2010-03-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS')),

partition part_index1_13 values less than(to_date('2010-05-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS')),

partition part_index1_14 values less than(to_date('2010-07-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS')),

partition part_index1_15 values less than(to_date('2010-09-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS')),

partition part_index1_16 values less than(to_date('2010-11-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS')),

partition part_index1_17 values less than(to_date('2011-01-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS')),

partition part_index1_18 values less than(to_date('2011-03-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS')),

partition part_index1_19 values less than(to_date('2011-05-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS')),

partition part_index1_20 values less than(to_date('2011-07-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS')),

partition part_index1_21 values less than(to_date('2011-09-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS')),

partition part_index1_22 values less than(to_date('2011-11-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS')),

partition part_index1_23 values less than(to_date('2012-01-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS')),

partition part_index1_24 values less than(to_date('2012-03-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS')),

partition part_index1_25 values less than(to_date('2012-05-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS')),

partition part_index1_26 values less than(to_date('2012-07-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS')),

partition part_index1_27 values less than(to_date('2012-09-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS')),

partition part_index1_28 values less than(to_date('2012-11-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS')),

partition part_index1_29 values less than(maxvalue)

))

下面是加載數據以及實施步驟:

l  表數據加載

創建db link “ctn”.

Insert /*+append*/ into b_ctn_normal select * from b_ctn_normal@ctn;

加載速度還可以,大概30分鍾加載完,加載數據量4400W。

l  分別按照上述的策略創建分區索引

GLOBAL_INDEX_D_VERIFYDATETIME和GLOBAL_INDEX_D_PRODATETIME

索引創建約為30分鍾

l  執行sql1和sql2和原始庫進行對比

 

Sql1的測試結果對比:

 

返回行數

Pl/sql查詢

Sqlplus跟蹤

優化前

30

>15分鍾

00: 04: 31.62

優化后

30

<0.4秒

00: 00: 00.03

 

Sql2的測試結果對比:

 

返回行數

Pl/sql查詢

Sqlplus跟蹤

優化前

5529

00: 05: 42.67

優化后

5529

<0.02秒

00: 00: 00.01

 

通過以上對比結果,顯示新的分區策略帶來了巨大的性能提升,顯示了oracle分區技術的強大威力。原來十幾分鍾甚至返回不了結果的查詢現在毫秒就返回數據。

下面分析優化后的執行計划:

 

Sql1執行計划:

 

通過執行計划可以看出,查詢正確的使用了d_prodatetime字段進行了分區裁減,然后使用到了該列的分區索引,但是並沒有使用n_madein進行裁減。於是改變了下查詢條件,將查詢的數據量增大:

SELECT

 c_lotno

         FROM b_ctn_normal

 WHERE d_prodatetime BETWEEN to_date('2011-07-01', 'yyyy-mm-dd HH24:MI:SS') AND

                             to_date('2012-07-03', 'yyyy-mm-dd HH24:MI:SS')

                             AND n_madein = 65

                             AND rownum < 5000

 

這次,查詢使用了二級分區裁減。先是對一級分區進行裁減,然后又對二級分區進行裁減,最后對二級分區使用N_MADEIN進行全表掃描。執行計划顯示,查詢5000條數據時耗時增加了很多,因為掃描的數據量實在太大了,查詢需要掃描很多分區。這樣只能通過減少一次查詢的數據量來保證性能。通過和開發人員確認,一次查詢一般不用返回這么多數據。

 

Sql2執行計划:

 

執行計划已經顯示的很明確,一級分區按照新分區的字段進行裁減,然后使用建立的分區索引,性能很高。

           雖然新的分區策略顯示了巨大的性能提升,有效的解決了性能問題,但是仔細分析一下,仍然存在一些問題:

u  分區較多,在4K萬級別的表上,分區多達493個,這有些過分了。需要減少分區數量。目前的分區是每倆月一個分區,目前的數據分布比重新分區前均勻了很多,但是仍然存在不均勻現象,而且每倆月一個分區仍然較多。因此需要維持現在的范圍分區字段不變,將現在的倆月一個分區的條件變化一下,分析數據的分布區間,制定一個不均勻的分區條件。如2010年8月的數據很多,那可以分別以2010-08-01~2010-08-15~2010-08-30為區間划分。如果2010年9-12月數據很少,那么可以將9-12月合並為一個分區。盡可能的均勻划分分區記錄數,也減少分區數量。

u  評估二級分區的必要性。總的分區數是1級分區和二級分區的乘積,為M*N的關系。二級分區的增加,大大增加了分區數。分析發現,有接近一半的二級分區是空閑的,並沒有記錄裝入,浪費了大量的空間。而且目前的sql並沒有使用到二級分區裁減,因此需要評估二級分區帶來的性能提高。然后考慮是否將二級分區去掉只采用范圍分區。去掉二級分區,目前對性能是沒有壞處的,而且未來如果用到對N_MADEIN字段的裁剪,直接alter表即可增加二級分區,不用重建。因此建議去掉。

l  總結

分區是處理大表的首要應對策略,但是分區字段的選取和分區的方法需要仔細權衡,一般第一想到的分區字段都是合理的,但是一些隱含的字段沒有考慮到,未來數據量上去了,這些隱含的條件造成的性能問題就暴露出來了,因此還是需要全面的分析。

對表進行了分區,相應的也要對索引進行分區,這樣可以裁減掉部分索引,然后裁減掉記錄,雖然是海量數據,但是卻擁有極高的查詢速度。記得在一本書上看過,作者說,正是因為有了分區技術,oracle才敢號稱是海量數據庫。

 

Reference:

http://docs.oracle.com/cd/B19306_01/server.102/b14231/partiti.htm#i1009216


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM