橫表與豎表性能淺析


概念介紹

橫表概念

 

橫表就是普通的建表方式,每一個字段代表一個KPI指標。舉個列子,一個學生的成績表:學號、數學成績、語文成績、英語成績、

物理成績、化學成績......如下所示:

SQL> DESC STUDENT_SCORE
Name             Type       Nullable Default Comments 
---------------- ---------- -------- ------- -------- 
STUDENT_NO       NUMBER(10)                  學號     
CHINESE_SCORE    NUMBER     Y                語文成績 
ENGLISH_SCORE    NUMBER     Y                英語成績 
MATH_SOCRE       NUMBER     Y                數學成績 
PHYSICAL_SCORE   NUMBER     Y                物理成績 
SPORTS_SCORE     NUMBER     Y                體育成績 
CHEMICAL_SCORE   NUMBER     Y                化學成績 
BIOLOGICAL_SCORE NUMBER     Y                生物成績

豎表概念

 

SQL> DESC STUDENT_SOCRES;
Name         Type         Nullable Default Comments 
------------ ------------ -------- ------- -------- 
STUDENT_NO   NUMBER(10)   Y                學號     
SUBJECT_CODE VARCHAR2(12) Y                科目編碼 
SUBJECT_NAME VARCHAR2(12) Y                科目名稱 
SCORES       NUMBER       Y                成績

優劣比較

指標項目

橫表

豎表

可擴展性

性能方面

優於豎表

比橫表差

業務描述

代碼復雜

代碼簡單

代碼復雜,大部分時候需要進行轉換

 

橫表優點

1:業務描述:橫表的好處是清晰可見,一目了然,數據描敘很清晰。每個字段就是一個KPI指標。

2:性能方面:橫表從數據庫映射到內存的速度比豎表要快很多。

3:代碼復雜:橫表不需要做行列轉換,代碼比較簡單

橫表缺點:

1:可擴展性:如果需求變更,例如需要增加一個指標,那么就必須修改表結構或重建表。對於需求不明確或變更頻繁的情況,橫表需要

大的改動,涉及改動的腳本也較多。

豎表優點:

1:可擴展性:對於豎表來說,不必修改表結構,只需增加一條記錄就可搞定。對於需求不明確或變更頻繁的情況,豎表基本不用改動,

涉及改動的腳本也較少。

豎表缺點:

1:業務描述:豎表的數據描敘很不清晰,舉例說明:學生成績表的豎表形式,成績這個字段,即可是數學成績、也可是語文成績,不像

橫表形式數學成績、語文成績各成一個字段描述KPI指標來得清晰明了。

2:性能方面:系統展現的報表大部分是橫表,這意味着豎表要進行轉列。這樣需要額外的性能開銷。尤其是當報表進行聚合計算時,性能

更糟糕。這是因為豎表從數據庫映射到內存比橫表要慢。

3:代碼復雜:需要做行列轉換,代碼量、復雜性都會增加很多。

實驗對比

 

橫表STUDENT_SCORE有語文成績、英語成績等7個KPI指標,三個學生的三條記錄。

SQL> SELECT * FROM STUDENT_SCORE;
 
 STUDENT_NO CHINESE_SCORE ENGLISH_SCORE MATH_SOCRE PHYSICAL_SCORE SPORTS_SCORE CHEMICAL_SCORE BIOLOGICAL_SCORE
----------- ------------- ------------- ---------- -------------- ------------ -------------- ----------------
      10001          87.4            63         92             86           75             85               89
      10002            91            89         98             62           76             82               73
      10006            74            63         57             42           76             59               67

對應於豎表,這三個學生的7個KPI指標需要21條記錄才能描述清楚。

SQL> SELECT * FROM STUDENT_SOCRES;
 
 STUDENT_NO SUBJECT_CODE SUBJECT_NAME     SCORES
----------- ------------ ------------ ----------
      10001 CH           語文成績           87.4
      10001 EG           英語成績             63
      10001 MT           數學成績             92
      10001 PH           物理成績             86
      10001 SP           體育成績             75
      10001 CE           化學成績             85
      10001 BI           生物成績             89
      10002 CH           語文成績             91
      10002 EG           英語成績             89
      10002 MT           數學成績             98
      10002 PH           物理成績             62
      10002 SP           體育成績             76
      10002 CE           化學成績             82
      10002 BI           生物成績             73
      10006 CH           語文成績             74
      10006 EG           英語成績             63
      10006 MT           數學成績             57
      10006 PH           物理成績             42
      10006 SP           體育成績             76
      10006 CE           化學成績             59
      10006 BI           生物成績             67

所以我們從這個小實驗中可以看到,橫表轉成豎表,對應的記錄會翻倍增長,這對應於數據量大的表或寬表,都是一件不好的消息。很多時候,

數據量上去了,性能問題就出來了。它們之間的記錄關系如下所示:

 

豎表行數 = 橫表行數 * KPI指標個數。

 

個數據量500萬,KPI指標個數為10的橫表,轉成豎表后的記錄數會飈增到5000萬。

我們拿表ODS.TO_BUSS_WNMS_BSCPMHR來做實驗,該表的表結構對應如下所示:

SQL> DESC ODS.TO_BUSS_WNMS_BSCPMHR
Name                        Type       Nullable Default Comments                
--------------------------- ---------- -------- ------- ----------------------- 
COLLECT_DT                  NUMBER(8)                   采集日期                 
DATE_CD                     NUMBER(8)                   日期編碼                 
HR_CD                       NUMBER(2)                   時段編碼                 
CITY_ID                     NUMBER(10)                  地市標識                 
SYSTEM_ID                   NUMBER(10)                  網元編碼                 
TBF_CLEAN_CNT               NUMBER     Y                TBF清空次數              
UPTBF_TRY_CNT               NUMBER     Y                上行TBF請求數            
UPTBF_SUCC_RAT              NUMBER     Y                上行TBF建立成功率        
DOWNTBF_SUCC_CNT            NUMBER     Y                下行TBF成功建立次數      
DOWNTBF_SUCC_RAT            NUMBER     Y                下行TBF建立成功率        
DOWNTBF_TRY_CNT             NUMBER     Y                下行TBF建立嘗試次數      
UPTBF_SUCC_CNT              NUMBER     Y                上行TBF成功建立次數      
GPRSDOWNTBF_ABNM_CNT        NUMBER     Y                GPRS下行TBF異常中斷次數  
GPRSDOWNTBF_SUCC_CNT        NUMBER     Y                GPRS下行TBF建立成功次數  
GPRSDOWNTBF_DROP_RAT        NUMBER     Y                GPRS下行TBF掉線率        
DROP_CALL_TCH               NUMBER     Y                TCH掉話總次數            
TCH_CALL_SEIZ               NUMBER     Y                話音信道占用總次數(含切換) 
GSMTCH_DROP_RAT             NUMBER     Y                TCH掉話率(GSM)          
TDTCH_DROP_PCT              NUMBER     Y                TCH話務掉話比            
TCH_ERL                     NUMBER     Y                TCH話務量               
TCH_CNT                     NUMBER     Y                TCH信道數               
TCH_GT_RAT                  NUMBER     Y                TCH接通率               
ATT_TCH_OVRFL               NUMBER     Y                話音信道溢出總次數(含切換) 
TCH_CALL_REQ                NUMBER     Y                話音信道試呼總次數(含切換) 
TCH_CONG_RAT                NUMBER     Y                TCH擁塞率               
DROP_CALL_SDCCH             NUMBER     Y                SDCCH掉話總次數          
GSMSDCCH_DROP_RAT           NUMBER     Y                SDCCH掉話率(GSM)        
SDCCH_ALLOT_SUCC_RAT        NUMBER     Y                SDCCH分配成功率          
SDCCH_GT_RAT                NUMBER     Y                SDCCH接通率             
ATT_SDCCH_OVRFL             NUMBER     Y                SDCCH溢出總次數          
SDCCH_CONG_RAT              NUMBER     Y                SDCCH擁塞率             
SDCCH_USE_CNT               NUMBER     Y                SDCCH占用次數            
SDCCH_TRY_CNT               NUMBER     Y                SDCCH試呼次數            
SDCCH_AV_HOLD_T             NUMBER     Y                SDCCH信道平均占用時長    
RLC_TRAFIC                  NUMBER     Y                RLC流量                 
EDGE_RLCTSTP_RAT_FZ         NUMBER     Y                EGPRS RLC層單時隙吞吐率-分子 
EDGE_RLCTSTP_RAT_FM         NUMBER     Y                EGPRS RLC層單時隙吞吐率-分母 
EGPRS_RLC_THRUPUT_RAT       NUMBER     Y                EGPRS RLC層單時隙吞吐率  
GPRS_RLCTSTP_RAT_FZ         NUMBER     Y                GPRS RLC層單時隙吞吐率-分子 
GPRS_RLCTSTP_RAT_FM         NUMBER     Y                GPRS RLC層單時隙吞吐率-分母 
GPRS_RLC_THRUPUT_RAT        NUMBER     Y                GPRS RLC層單時隙吞吐率   
EGPRS_RETRAN_RAT            NUMBER     Y                EGPRS重傳率             
RLCDOWN_REPLYDATA_EGPRS_CNT NUMBER     Y                RLC層下行鏈路無線數據塊重傳數(EGPRS) 
EGPRS_RLC_CNT               NUMBER     Y                RLC層總塊數(EGPRS)       
GPRS_RETRAN_RAT             NUMBER     Y                GPRS重傳率              
RLCDOWN_REPLYDATA_GPRS_CNT  NUMBER     Y                RLC層下行鏈路無線數據塊重傳數(GPRS) 
GPRS_RLC_CNT                NUMBER     Y                RLC層總塊數(GPRS)        
LOW_CODE_RAT                NUMBER     Y                低編碼比例               
MID_CODE_RAT                NUMBER     Y                中編碼比例               
USE_PDCH_AVG_CNT            NUMBER     Y                占用的PDCH的平均數       
PDCH_REUSE                  NUMBER     Y                PDCH復用度              
PDCH_ALLOT_SUCC_RAT         NUMBER     Y                PDCH信道分配成功率       
PDCH_CNT                    NUMBER     Y                PDCH信道數量             
GSL_MAX_CNT                 NUMBER     Y                PCU(GSL最大設備數)       
GSL_USERAT                  NUMBER     Y                GSL利用率               
PDCH_ALLOT_CNT              NUMBER     Y                PDCH信道分配次數         
PDCH_ALLOT_SUCC_CNT         NUMBER     Y                PDCH信道分配成功次數     
HO_REQ_CNT                  NUMBER     Y                切換請求總次數           
HO_SUCC_CNT                 NUMBER     Y                切換成功總次數           
AVAIL_TCH_NBR               NUMBER     Y                可配置信道數             
TCH_TRAFFIC_H               NUMBER     Y                半速率話務量             
CH_CNT_PDCH                 NUMBER     Y                總業務信道數             
TCH_SEIZE_NHO               NUMBER     Y                話音信道占用總次數(不含切換) 
AVG_DISTR_PDCH_CNT          NUMBER     Y                平均分配PDCH數           
UPIP_FLOW                   NUMBER     Y                上行IP層流量             
DOWNIP_FLOW                 NUMBER     Y                下行IP層流量

建立這張橫表對應的豎表TO_BUSS_WNMS_BSCPM_H_TEST

CREATE TABLE TO_BUSS_WNMS_BSCPM_H_TEST
(
       COLLECT_DT     NUMBER(8)    ,
       DATE_CD        NUMBER(8)    ,
       HR_CD          NUMBER(2)    ,
       CITY_ID        NUMBER(10)  ,
       SYSTEM_ID      NUMBER(10)  ,
       KPI_CODE       VARCHAR2(32),
       KPI_NAM        VARCHAR2(32),
       KPI_VALUE      NUMBER      ,
       CONSTRAINT PK_TO_BUSS_WNMS_BSCPM_H_TEST PRIMARY KEY ("COLLECT_DT", "DATE_CD", "HR_CD", "CITY_ID", "SYSTEM_ID","KPI_CODE")
) PARTITION BY RANGE(COLLECT_DT) 
( 
  PARTITION "PART201111"  VALUES LESS THAN (20111199) TABLESPACE TBS_KFT_DATA, 
  PARTITION "PART201112"  VALUES LESS THAN (20111299) TABLESPACE TBS_KFT_DATA,
  PARTITION "PART201201"  VALUES LESS THAN (20120199) TABLESPACE TBS_KFT_DATA,
  PARTITION "PART201202"  VALUES LESS THAN (20120299) TABLESPACE TBS_KFT_DATA,
  PARTITION "PART201203"  VALUES LESS THAN (20120399) TABLESPACE TBS_KFT_DATA,
  PARTITION "PART201204"  VALUES LESS THAN (20120499) TABLESPACE TBS_KFT_DATA,
  PARTITION "PART201205"  VALUES LESS THAN (20120599) TABLESPACE TBS_KFT_DATA,
  PARTITION "PART201206"  VALUES LESS THAN (20120699) TABLESPACE TBS_KFT_DATA,
  PARTITION "PART201207"  VALUES LESS THAN (20120799) TABLESPACE TBS_KFT_DATA,
  PARTITION "PART201208"  VALUES LESS THAN (20120899) TABLESPACE TBS_KFT_DATA,
  PARTITION "PART201209"  VALUES LESS THAN (20120999) TABLESPACE TBS_KFT_DATA,
  PARTITION "PART201210"  VALUES LESS THAN (20121099) TABLESPACE TBS_KFT_DATA,
  PARTITION "PART201211"  VALUES LESS THAN (20121199) TABLESPACE TBS_KFT_DATA,
  PARTITION "PART201212"  VALUES LESS THAN (20121299) TABLESPACE TBS_KFT_DATA,
  PARTITION "PART201301"  VALUES LESS THAN (20130199) TABLESPACE TBS_KFT_DATA
)

把ODS.TO_BUSS_WNMS_BSCPMHR的2012年12月1號以后的數據導入到TO_BUSS_WNMS_BSCPM_H_TEST,然后收集統計該表的相關信息。另外新建橫表

TO_BUSS_WNMS_BSCPMHR_S_TEST(表結構和ODS.TO_BUSS_WNMS_BSCPMHR一樣),把2012年12月1號以后的數據導入到TO_BUSS_WNMS_BSCPMHR_S_TEST

SQL> exec dbms_stats.gather_table_stats(ownname=>'DWKONGLINGBO',tabname=>'TO_BUSS_WNMS_BSCPMHR_S_TEST',partname=>'PART201212',granularity=>'PARTITION',estimate_percent=> dbms_stats.auto_sample_size,force=>true,degree=>8);

PL/SQL procedure successfully completed

SQL> exec dbms_stats.gather_table_stats(ownname=>'DWKONGLINGBO',tabname=>'TO_BUSS_WNMS_BSCPMHR_S_TEST',partname=>'PART201301',granularity=>'PARTITION',estimate_percent=> dbms_stats.auto_sample_size,force=>true,degree=>8);

PL/SQL procedure successfully completed

SQL> exec dbms_stats.gather_table_stats(ownname=>'DWKONGLINGBO',tabname=>'TO_BUSS_WNMS_BSCPM_H_TEST',partname=>'PART201212',granularity=>'PARTITION',estimate_percent=> dbms_stats.auto_sample_size,force=>true,degree=>8);

PL/SQL procedure successfully completed

SQL> exec dbms_stats.gather_table_stats(ownname=>'DWKONGLINGBO',tabname=>'TO_BUSS_WNMS_BSCPM_H_TEST',partname=>'PART201301',granularity=>'PARTITION',estimate_percent=> dbms_stats.auto_sample_size,force=>true,degree=>8);

PL/SQL procedure successfully completed

 

查看表的相關信息:

SELECT TABLE_OWNER,
       TABLE_NAME,
       PARTITION_NAME,
       NUM_ROWS,
       BLOCKS,
       AVG_ROW_LEN,
       LAST_ANALYZED
  FROM DBA_TAB_PARTITIONS
 WHERE TABLE_NAME = 'TO_BUSS_WNMS_BSCPMHR_S_TEST'
   AND PARTITION_NAME IN ('PART201212', 'PART201301')

clip_image001

 

SELECT TABLE_OWNER,
       TABLE_NAME,
       PARTITION_NAME,
       NUM_ROWS,
       BLOCKS,
       AVG_ROW_LEN,
       LAST_ANALYZED
  FROM DBA_TAB_PARTITIONS
 WHERE TABLE_NAME = 'TO_BUSS_WNMS_BSCPM_H_TEST'
   AND PARTITION_NAME IN ('PART201212', 'PART201301')

clip_image002

對比上面統計信息,即可發現:

SQL> SELECT 43926464/721062 FROM DUAL;
 
    43926464/721062
    ---------------
    60.919122072720

SQL> SELECT 26921828/443940 FROM DUAL;
 
    26921828/443940
    ---------------
    60.642942740009

可見數據量翻了60~61倍,數據所占存儲空間增長了7倍,也就說數據冗余大量增加。可見在存儲方面,橫表要比豎表有優勢。如果系統大量使用豎表,

存儲浪費就比較嚴重了。

SQL> SELECT (102240-12753)/12753 FROM DUAL;
 
(102240-12753)/12753
--------------------
    7.01693719124912

SQL> SELECT (63303 -7622)/7622 FROM DUAL;
 
(63303-7622)/7622
-----------------
 7.30530044607715

 

查詢腳本對比

從下面的執行計划,以及實際執行結果可以看出,橫表比豎表的性能要優很多。我測試了好幾次。都是如此,而且腳本越復雜,執行效率差異越大。

 SQL> SET AUTOTRACE TRACEONLY
 
 SQL> SET AUTOTRACE TRACEONLY SQL> SELECT A.COLLECT_DT AS COLLECT_DT, A.DATE_CD AS DATE_CD, A.HR_CD AS HR_CD, A.CITY_ID AS CITY_ID, A.SYSTEM_ID AS BSC_ID, B.MSC_ID AS MSC_ID, MAX(DECODE(A.KPI_CODE, 'HO_REQ_CNT', KPI_VALUE, 0)) AS HO_REQ, MAX(DECODE(A.KPI_CODE, 'HO_SUCC_CNT', KPI_VALUE, 0)) AS S_HO FROM dwkonglingbo.TO_BUSS_WNMS_BSCPM_H_TEST A, REF.TR_WGG_BSC_INFO B WHERE A.SYSTEM_ID = B.BSC_ID AND A.COLLECT_DT = 20121218
    AND (KPI_CODE = 'HO_REQ_CNT' OR KPI_CODE = 'HO_SUCC_CNT') GROUP BY A.COLLECT_DT, A.DATE_CD, A.HR_CD, A.CITY_ID, A.SYSTEM_ID, B.MSC_ID; 22728 rows selected. Execution Plan
 ----------------------------------------------------------
 Plan hash value: 2342193993
 
 ---------------------------------------------------------------------------------------------------------------------------------------------
 | Id  | Operation                            | Name                         | Rows  | Bytes |TempSpc| Cost (%CPU)| Time     | Pstart| Pstop |
 ---------------------------------------------------------------------------------------------------------------------------------------------
 |   0 | SELECT STATEMENT                     |                              | 46458 |  2722K|       |   239   (3)| 00:00:06 |       |       |
 |   1 |  HASH GROUP BY                       |                              | 46458 |  2722K|  7328K|   239   (3)| 00:00:06 |       |       |
 |*  2 |   HASH JOIN                          |                              | 46458 |  2722K|       |     9  (45)| 00:00:01 |       |       |
 |   3 |    TABLE ACCESS FULL                 | TR_WGG_BSC_INFO              |  1963 | 21593 |       |     2   (0)| 00:00:01 |       |       |
 |   4 |    TABLE ACCESS BY GLOBAL INDEX ROWID| TO_BUSS_WNMS_BSCPM_H_TEST    | 46458 |  2223K|       |     7  (58)| 00:00:01 |    14 |    14 |
 |*  5 |     INDEX RANGE SCAN                 | PK_TO_BUSS_WNMS_BSCPM_H_TEST | 46458 |       |       |     6  (67)| 00:00:01 |       |       |
 ---------------------------------------------------------------------------------------------------------------------------------------------
 Predicate Information (identified by operation id): ---------------------------------------------------
 
    2 - access("A"."SYSTEM_ID"="B"."BSC_ID") 5 - access("A"."COLLECT_DT"=20121218) filter("KPI_CODE"='HO_REQ_CNT' OR "KPI_CODE"='HO_SUCC_CNT') Statistics
 ----------------------------------------------------------
           1 recursive calls 0 db block gets 49816 consistent gets 89 physical reads 6408 redo size 1177246  bytes sent via SQL*Net to client 17157  bytes received via SQL*Net from client 1517  SQL*Net roundtrips to/from client 0 sorts (memory) 0  sorts (disk) 22728 rows processed SQL> SELECT A.COLLECT_DT  AS COLLECT_DT, 2         A.DATE_CD     AS DATE_CD, 3         A.HR_CD       AS HR_CD, 4         A.CITY_ID     AS CITY_ID, 5         A.SYSTEM_ID   AS BSC_ID, 6         B.MSC_ID      AS MSC_ID, 7         A.HO_REQ_CNT  AS HO_REQ, 8         A.HO_SUCC_CNT AS S_HO 9    FROM dwkonglingbo.TO_BUSS_WNMS_BSCPMHR_S_TEST A, REF.TR_WGG_BSC_INFO B 10   WHERE A.SYSTEM_ID = B.BSC_ID 11     AND A.COLLECT_DT = 20121218; 22728 rows selected. Execution Plan
 ----------------------------------------------------------
 Plan hash value: 2525980157
 
 --------------------------------------------------------------------------------------------------------------------------------------
 | Id  | Operation                           | Name                           | Rows  | Bytes | Cost (%CPU)| Time     | Pstart| Pstop |
 --------------------------------------------------------------------------------------------------------------------------------------
 |   0 | SELECT STATEMENT                    |                                | 23260 |  1044K|    31   (4)| 00:00:01 |       |       |
 |*  1 |  HASH JOIN                          |                                | 23260 |  1044K|    31   (4)| 00:00:01 |       |       |
 |   2 |   TABLE ACCESS FULL                 | TR_WGG_BSC_INFO                |  1963 | 21593 |     2   (0)| 00:00:01 |       |       |
 |   3 |   TABLE ACCESS BY GLOBAL INDEX ROWID| TO_BUSS_WNMS_BSCPMHR_S_TEST    | 23260 |   795K|    28   (0)| 00:00:01 |     2 |     2 |
 |*  4 |    INDEX RANGE SCAN                 | PK_TO_BUSS_WNMS_BSCPMHR_S_TEST | 23260 |       |     2   (0)| 00:00:01 |       |       |
 --------------------------------------------------------------------------------------------------------------------------------------
 Predicate Information (identified by operation id): ---------------------------------------------------
 
    1 - access("A"."SYSTEM_ID"="B"."BSC_ID") 4 - access("A"."COLLECT_DT"=20121218) Statistics
 ----------------------------------------------------------
           1 recursive calls 0 db block gets 3449 consistent gets 434 physical reads 0 redo size 1175708  bytes sent via SQL*Net to client 17157  bytes received via SQL*Net from client 1517  SQL*Net roundtrips to/from client 0 sorts (memory) 0  sorts (disk) 22728 rows processed SQL>

 

 

 

clip_image003

clip_image004

結論總結:(上述測試列子比較少,也沒有排除其他因素的影響,但是足以說明實質問題

豎表只適合數據量少,需求變更比較頻繁或配置比較靈活的報表,例如概覽視圖等。不適合數據量大的表。也不適合在數據倉庫中大量存在。


免責聲明!

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



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