代码改变世界

横表与竖表性能浅析

2013-03-13 10:18  潇湘隐者  阅读(8201)  评论(2编辑  收藏  举报

概念介绍

横表概念

 

横表就是普通的建表方式,每一个字段代表一个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

结论总结:(上述测试列子比较少,也没有排除其他因素的影响,但是足以说明实质问题

竖表只适合数据量少,需求变更比较频繁或配置比较灵活的报表,例如概览视图等。不适合数据量大的表。也不适合在数据仓库中大量存在。