1.2.2 OLAP 数据库

我一直认为OLAP数据库在内存上可优化的余地很小,甚至觉得增加CPU处理速度和磁盘I/O速度是最直接的提高数据库性能的方式,但这将意味着系统成本的增加。实际上,用户对OLAP系统性能的期望远远没有对OLTP性能的期望那么高。

内存的优化,对OLAP来讲影响很小,比如我曾经遇到的一个数据库,每天晚上运行的报表程序,基本上都是对几亿条或者几十亿条数据进行聚合处理,这种海量的数据,全部在内存中操作是很难的,同时也完全没有必要,因为这些数据块很少重用,缓存起来没有实际意义,倒是物理I/O相当大,这种系统的瓶颈往往是在磁盘I/O上面。

对于OLAP系统,SQL的优化显得非常重要,试想,如果一张表中只有几千条数据,无论执行全表扫描或是使用索引,对我们来说差异都很小,几乎感觉不出来,但是当数据量提升到几亿或者几十亿甚至更多的时候,全表扫描、索引可能导致极大的性能差异,因此SQL的优化显得重要起来。

看下面的一个例子,它对比了索引和全表扫描的效率:

    **********************************************************************
    select *
    from
    t where object_id<100

    call    count       cpu     elapsed     disk    query   current     rows
    ----    ------      ----    --------   -----   -----   -------     -----
    Parse       1      0.01       0.00         0         0        0         0
    Execute     1      0.00       0.00         0         0        0         0
    Fetch       8      0.00       0.00         0        17        0        98
    ----    ------      ----    --------   -----   -----   -------     -----
    total       10     0.01      0.00          0        17        0        98
    Misses in library cache during parse: 1
    Optimizer mode: ALL_ROWS
    Parsing user id: 55

    Rows     Row Source Operation
    -------  ---------------------------------------------------
        98  TABLE ACCESS BY INDEX ROWID T (cr=17 pr=0 pw=0 time=95 us)
        98   INDEX RANGE SCAN T_INX (cr=9 pr=0 pw=0 time=2383 us)(object id 51627)
    **********************************************************************

    select /*+ full(t) */ *
    from
    t where object_id<100

    call    count   cpu     elapsed     disk    query   current     rows
    ----    ------  ------  --------   ------  -----   -------     -----
    Parse       1   0.00       0.00        0        0         0         0
    Execute     1   0.00       0.00        0        0         0         0
    Fetch       8   0.01       0.00        0       695        0        98
    ----    ------  ------  --------   ------  -----   -------     -----
    total      10   0.01       0.01        0       695        0        98

    Misses in library cache during parse: 1
    Optimizer mode: ALL_ROWS
    Parsing user id: 55

    Rows     Row Source Operation
    -------  ---------------------------------------------------
        98  TABLE ACCESS FULL T (cr=695 pr=0 pw=0 time=116 us)
    **********************************************************************

我们看到,在这个只有几万条记录的表中,相同的SQL语句,全表扫描扫过的数据块(一致性读)是695个,而索引只扫过了17个,差别还是非常大的。

分区技术在OLAP数据库中很重要,这种重要主要体现在数据管理上,比如数据加载,可以通过分区交换的方式实现,备份可以通过备份分区表空间实现,删除数据可以通过分区进行删除;至于分区在性能上的影响,不能一概而论,认为分区的性能将始终好于非分区,这个结论是不成立的,至少是片面的,我们通过以下几种情况来分析它。

1. 当查询的范围正好落在某个分区的时候

这时候分区的效率自然是高于没有分区的,因为SQL在有分区的表上只扫过一个分区的数据,而对于没有分区,需要扫描整个表,这也是大多数人认为分区会提高性能的一个原因吧,比如下面的例子:

    **********************************************************************
    select count(*)
    from
    t where x<1000

    call    count       cpu     elapsed     disk    query   current     rows
    ----    ------  -------     --------   ----    -----   -------     -----
    Parse       1      0.00       0.00         0         0         0          0
    Execute     1      0.00       0.00         0         0         0          0
    Fetch       2      0.00       0.00         0        23         0          1
    ----    ------  -------     --------   ----    -----   -------     -----
    total       4     0.00       0.00         0        23         0          1

    Misses in library cache during parse: 1
    Optimizer mode: ALL_ROWS
    Parsing user id: 55

    Rows     Row Source Operation
    -------  ---------------------------------------------------
          1  SORT AGGREGATE (cr=23 pr=0 pw=0 time=2495 us)
        999   PARTITION RANGE SINGLE PARTITION: 1 1 (cr=23 pr=0 pw=0 time=9085 us)
        999    TABLE ACCESS FULL T PARTITION: 1 1 (cr=23 pr=0 pw=0 time=4077 us)

    **********************************************************************

    select count(*)
    from
    t1 where x<1000

    call    count       cpu     elapsed     disk    query   current     rows
    ----    ------  -------     --------   ----    -----   -------     -----
    Parse       1      0.00       0.00         0         0         0          0
    Execute     1      0.00       0.00         0         0         0          0
    Fetch       2      0.01       0.00         0        84         0          1
    ----    ------  -------     --------   ----    -----   -------     -----
    total       4     0.01       0.01         0        84         0          1
    Misses in library cache during parse: 1
    Optimizer mode: ALL_ROWS
    Parsing user id: 55

    Rows     Row Source Operation
    -------  ---------------------------------------------------
          1  SORT AGGREGATE (cr=84 pr=0 pw=0 time=9015 us)
        999   TABLE ACCESS FULL T1 (cr=84 pr=0 pw=0 time=4077 us)

第一个SQL只扫过了一个分区的数据,扫过的数据块为23个;第二个SQL做了全表扫描,扫过的数据块为84个,这种情况下肯定是分区表的效率要高一些。

2. 当查询的范围跨越几个分区时

这时候分区可能并不绝对是最优的,比如下面的例子,我们把查询的范围扩大到分区表的13个分区,让CBO使用FAST INDEX FULL SCAN的方式扫描索引,另外我们创建另一张非分区表,表结果和数据同分区表完全一样,我们使用同一条SQL,并且也让CBO强制使用FAST INDEX FULL SCAN的方式访问非分区表上的全局索引。我们要验证的一个观点是,分区索引并不一定比全局索引在任何时候都快,有时候它反而会慢。下面是输入的结果:

    Select  /*+ index_ffs(t t_ind) */  count(*)
    from
    t where x<13000

    call    count       cpu     elapsed     disk    query   current     rows
    ----    ------  -------     --------   ----    -----   -------     -----
    Parse       1      0.00       0.00         0         0         0          0
    Execute     1      0.00       0.00         0         0         0          0
    Fetch       2      0.03       0.02         0       164         0          1
    ----    ------  -------     --------   ----    -----   -------     -----
    total       4     0.03       0.03         0       164         0          1

    Misses in library cache during parse: 1
    Optimizer mode: ALL_ROWS
    Parsing user id: 55

    Rows     Row Source Operation
    -------  ---------------------------------------------------
          1  SORT AGGREGATE (cr=164 pr=0 pw=0 time=29234 us)
    12999   PARTITION RANGE ALL PARTITION: 1 13 (cr=164 pr=0 pw=0 time=117074
    us)12999  INDEX FAST FULL SCAN T_IND PARTITION: 1 13 (cr=164 pr=0 pw=0
    time=52408 us)(object id 51774)
    select /*+ index_ffs(t1 t1_ind) */ count(*)
    from
    t1 where x<13000

    call    count       cpu     elapsed     disk    query   current     rows
    ----    ------  -------     --------   ----    -----   -------     -----
    Parse       1      0.00       0.00         0         0         0        0
    Execute     1      0.00       0.00         0         0         0        0
    Fetch       2      0.03       0.02         0       117         0        1
    ----    ------  -------     --------   ----    -----   -------     -----
    total       4      0.03       0.02         0       117         0        1

    Misses in library cache during parse: 1
    Optimizer mode: ALL_ROWS
    Parsing user id: 55

    Rows     Row Source Operation
    -------  ---------------------------------------------------
          1  SORT AGGREGATE (cr=117 pr=0 pw=0 time=24755 us)
    12999   INDEX FAST FULL SCAN T1_IND (cr=117 pr=0 pw=0 time=52082 us)(object
    id 51788)

    **********************************************************************

在这个例子里面,分区索引之所以扫过了更多的数据块,是因为分区索引在做FFS(INDEX FAST FULL SCAN)的时候只能够在本地索引上进行,如果涉及其他的分区,还需要按照访问索引的方式去访问其他索引(比如先找到其他分区索引的根数据块,再找到最左边的叶块,然后执行FFS操作),这样,查询跨过的分区越多,这种额外的代价就越大;而在这种情况下,全局索引只需要定位到一个叶块,然后执行一次FFS就能够扫过所有的索引叶块,这样性能就会好于分区索引。

上面的例子是想说明,OLAP环境中,分区主要的功能是管理上的方便性,它并不能绝对保证查询性能的提高,有时候分区会带来性能上的提高,有时候甚至会降低,就像我们在例子中看到的一样。