zoukankan      html  css  js  c++  java
  • 横表与竖表性能浅析

    概念介绍

    横表概念

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

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

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

    扫描上面二维码关注我
    如果你真心觉得文章写得不错,而且对你有所帮助,那就不妨帮忙“推荐"一下,您的“推荐”和”打赏“将是我最大的写作动力!
    本文版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接.
  • 相关阅读:
    azkaban使用--指定executor
    azkaban使用--依赖dependencies作业
    azkaban用户管理及权限配置
    azkaban架构介绍
    azkaban工作流调度器及相关工具对比
    azkaban的简单使用
    Azkaban安装及分布式部署(multiple-executor)
    配置yum源
    idea搭建Eureka注册中心及配置密码登陆
    kudu导入文件(基于impala)
  • 原文地址:https://www.cnblogs.com/kerrycode/p/2957092.html
Copyright © 2011-2022 走看看