MySQL Query Cache 小结

2010-07-05

有3人发表了评论 | 赶紧发表评论吧 | 作者:sky

最近经常有人问我 MySQL Query Cache 相关的问题,就整理一点 MySQL Query Cache 的内容,以供参考。

顾名思义,MySQL Query Cache 就是用来缓存和 Query 相关的数据的。具体来说,Query Cache 缓存了我们客户端提交给 MySQL 的 SELECT 语句以及该语句的结果集。大概来讲,就是将 SELECT 语句和语句的结果做了一个 HASH 映射关系然后保存在一定的内存区域中。

在大部分的 MySQL 分发版本中,Query Cache 功能默认都是打开的,我们可以通过调整 MySQL Server 的参数选项打开该功能。主要由以下5个参数构成:

  • query_cache_limit:允许 Cache 的单条 Query 结果集的最大容量,默认是1MB,超过此参数设置的 Query 结果集将不会被 Cache
  • query_cache_min_res_unit:设置 Query Cache 中每次分配内存的最小空间大小,也就是每个 Query 的 Cache 最小占用的内存空间大小
  • query_cache_size:设置 Query Cache 所使用的内存大小,默认值为0,大小必须是1024的整数倍,如果不是整数倍,MySQL 会自动调整降低最小量以达到1024的倍数
  • query_cache_type:控制 Query Cache 功能的开关,可以设置为0(OFF),1(ON)和2(DEMAND)三种,意义分别如下:
    • 0(OFF):关闭 Query Cache 功能,任何情况下都不会使用 Query Cache
    • 1(ON):开启 Query Cache 功能,但是当 SELECT 语句中使用的 SQL_NO_CACHE 提示后,将不使用Query Cache
    • 2(DEMAND):开启 Query Cache 功能,但是只有当 SELECT 语句中使用了 SQL_CACHE 提示后,才使用 Query Cache
  • query_cache_wlock_invalidate:控制当有写锁定发生在表上的时刻是否先失效该表相关的 Query Cache,如果设置为 1(TRUE),则在写锁定的同时将失效该表相关的所有 Query Cache,如果设置为0(FALSE)则在锁定时刻仍然允许读取该表相关的 Query Cache。

Query Cache 如何处理子查询的?
这是我遇到的最为常见的一个问题。其实 Query Cache 是以客户端请求提交的 Query 为对象来处理的,只要客户端请求的是一个 Query,无论这个 Query 是一个简单的单表查询还是多表 Join,亦或者是带有子查询的复杂 SQL,都被当作成一个 Query,不会被分拆成多个 Query 来进行 Cache。所以,存在子查询的复杂 Query 也只会产生一个Cache对象,子查询不会产生单独的Cache内容。UNION[ALL] 类型的语句也同样如此。

Query Cache 是以 block 的方式存储的数据块吗?
不是,Query Cache 中缓存的内容仅仅只包含该 Query 所需要的结果数据,是结果集。当然,并不仅仅只是结果数据,还包含与该结果相关的其他信息,比如产生该 Cache 的客户端连接的字符集,数据的字符集,客户端连接的 Default Database等。

Query Cache 为什么效率会非常高,即使所有数据都可以 Cache 进内存的情况下,有些时候也不如使用 Query Cache 的效率高?
Query Cache 的查找,是在 MySQL 接受到客户端请求后在对 Query 进行权限验证之后,SQL 解析之前。也就是说,当 MySQL 接受到客户端的SQL后,仅仅只需要对其进行相应的权限验证后就会通过 Query Cache 来查找结果,甚至都不需要经过 Optimizer 模块进行执行计划的分析优化,更不许要发生任何存储引擎的交互,减少了大量的磁盘 IO 和 CPU 运算,所以效率非常高。

客户端提交的 SQL 语句大小写对 Query Cache 有影响吗?
有,由于 Query Cache 在内存中是以 HASH 结构来进行映射,HASH 算法基础就是组成 SQL 语句的字符,所以必须要整个 SQL 语句在字符级别完全一致,才能在 Query Cache 中命中,即使多一个空格也不行。

一个 SQL 语句在 Query Cache 中的内容,在什么情况下会失效?
为了保证 Query Cache 中的内容与是实际数据绝对一致,当表中的数据有任何变化,包括新增,修改,删除等,都会使所有引用到该表的 SQL 的 Query Cache 失效。

为什么我的系统在开启了 Query Cache 之后整体性能反而下降了?
当开启了 Query Cache 之后,尤其是当我们的 query_cache_type 参数设置为 1 以后,MySQL 会对每个 SELECT 语句都进行 Query Cache 查找,查找操作虽然比较简单,但仍然也是要消耗一些 CPU 运算资源的。而由于 Query Cache 的失效机制的特性,可能由于表上的数据变化比较频繁,大量的 Query Cache 频繁的被失效,所以 Query Cache 的命中率就可能比较低下。所以有些场景下,Query Cache 不仅不能提高效率,反而可能造成负面影响。

如何确认一个系统的 Query Cache 的运行是否健康,命中率如何,设置量是否足够?
MySQL 提供了一系列的 Global Status 来记录 Query Cache 的当前状态,具体如下:

  • Qcache_free_blocks:目前还处于空闲状态的 Query Cache 中内存 Block 数目
  • Qcache_free_memory:目前还处于空闲状态的 Query Cache 内存总量
  • Qcache_hits:Query Cache 命中次数
  • Qcache_inserts:向 Query Cache 中插入新的 Query Cache 的次数,也就是没有命中的次数
  • Qcache_lowmem_prunes:当 Query Cache 内存容量不够,需要从中删除老的 Query Cache 以给新的 Cache 对象使用的次数
  • Qcache_not_cached:没有被 Cache 的 SQL 数,包括无法被 Cache 的 SQL 以及由于 query_cache_type 设置的不会被 Cache 的 SQL
  • Qcache_queries_in_cache:目前在 Query Cache 中的 SQL 数量
  • Qcache_total_blocks:Query Cache 中总的 Block 数量

可以根据这几个状态计算出 Cache 命中率,计算出 Query Cache 大小设置是否足够,总的来说,我个人不建议将 Query Cache 的大小设置超过256MB,这也是业界比较常用的做法。

MySQL Cluster 是否可以使用 Query Cache?

其实在我们的生产环境中也没有使用 MySQL Cluster,所以我也没有在 MySQL Cluster 环境中使用 Query Cache 的实际经验,只是 MySQL 文档中说明确实可以在 MySQL Cluster 中使用 Query Cache。从 MySQL Cluster 的原理来分析,也觉得应该可以使用,毕竟 SQL 节点和数据节点比较独立,各司其职,只是 Cache 的失效机制会要稍微复杂一点。

原文出自:MySQL Query Cache 小结



ORACLE BITMAP INDEX

2010-05-24

有5人发表了评论 | 赶紧发表评论吧 | 作者:vogts

ORACLE的bitmap索引,我们用的很少。在我们的常理认识当中,我们用的最多就是类似性别,类型这种属性的字段,因为他们字段的种类很少。其实 BITMAP还是有很多优势以及一些特性的。

首先在传统的INDEX里,我们在索引的存储上,一般是分为:header+index length+index value+rowid组成。如图:

2010-05-24_142946

bitmap的存储结果相对来说,复杂一点。 bitmap 不存储rowid。那么rowid存储在哪里呢?答:每一个bitmap的头部,都存储了rowid的启示位置与结束位置。ORACLE通过自己的内部算 法,算出来相应的ROWID。
位图中的每一位,都记录是否有值。
如表的记录是这样存储的:

row-value
male
female
female
male

那么对应的bitmap则是这样存储的:

rowid的启示位置与结束位置 rowid的启示位置与结束位置
male female
1 0
0 1
0 1
1 0

由 此可见,存储的空间大大的节省了,另外带来的收益就是扫描的BLOCK也大大减少了。

如果查找性别是male的数据,ORACLE只会去 搜索MALE这一列,然后是1的记录,返回即可。

如果是针对BITMAP字段本身做OR,AND这样的查询,那么ORACLE会在 BITMAP索引内部,先做一次判断,找出符合结果的,再去计算ROWID,最后给出相应的VALUE,示意图如下:

dwhsg093

bitmap join index
bitmap join index,它的特点就是将多张表的JOIN结果,存储在一个索引里面,然后使用BITMAP的形式进行存储。图难画了,请见谅,改天有空的时候,再把图 补上。这个对于类似DW那样的多表join效率提高很明显。
我今天做了一个测试,是用3张表join来做的,原来的SQL是这样的:
test@DB>                       select wt_cust.company_name,wt_cust.gmt_create
2   from wt_cust,wt_CUST_EXT ,wt_CUST_BOOK
3  where wt_cust.id=wt_CUST_EXT.Cust_Id
4  and wt_CUST_BOOK.Cust_Id=wt_cust.id;

58 rows selected.

Elapsed: 00:00:00.01

Execution Plan
———————————————————-

—————————————————————————————————-
| Id  | Operation                     | Name                          | Rows  | Bytes | Cost (%CPU)|
—————————————————————————————————-
|   0 | SELECT STATEMENT              |                               |    54 |  2484 |   179   (0)|
|   1 |  NESTED LOOPS                 |                               |    54 |  2484 |   179   (0)|
|   2 |   NESTED LOOPS                |                               |   177 |  7257 |   179   (0)|
|   3 |    INDEX FULL SCAN            | wt_CUST_BOOK_UK     |   177 |  1062 |     1   (0)|
|   4 |    TABLE ACCESS BY INDEX ROWID| wt_CUST             |     1 |    35 |     2   (0)|
|*  5 |     INDEX UNIQUE SCAN         | wt_CUST_PK          |     1 |       |     1   (0)|
|*  6 |   INDEX RANGE SCAN            | wt_CUST_EXT_CID_IND |     1 |     5 |     0   (0)|

—————————————————————————————————-

一 个3表join,效率很差。如果我们创建BITMAP JOIN INDEX则可以避免这种情况的发生:
test@DB>CREATE BITMAP INDEX cust_wt_test
2  ON     wt_cust(wt_cust.company_name)
3  FROM   wt_cust,wt_CUST_EXT ,wt_CUST_BOOK
4  WHERE  wt_cust.id=wt_CUST_EXT.Cust_Id
5  and wt_CUST_BOOK.Cust_Id=wt_cust.id
6  tablespace test_ind  ;

Index created.

Elapsed: 00:00:00.08
再来看看SQL的执行计划:
xx@DB>select  wt_cust.company_name,wt_cust.gmt_create
2   from wt_cust,wt_CUST_EXT ,wt_CUST_BOOK
3  where wt_cust.id=wt_CUST_EXT.Cust_Id
4  and wt_CUST_BOOK.Cust_Id=wt_cust.id;

58 rows selected.

Elapsed: 00:00:00.00

Execution Plan
———————————————————-

—————————————————————————————
| Id  | Operation                    | Name              | Rows  | Bytes | Cost (%CPU)|
—————————————————————————————
|   0 | SELECT STATEMENT             |                   |  1834K|    61M|   219K  (1)|
|   1 |  TABLE ACCESS BY INDEX ROWID | wt_CUST |  1834K|    61M|   219K  (1)|
|   2 |   BITMAP CONVERSION TO ROWIDS|                   |       |       |            |
|   3 |    BITMAP INDEX FULL SCAN    | CUST_WT_TEST      |       |       |            |
—————————————————————————————

请 着重注意红色部分。逻辑读大大降低!!
BTW:
我觉得我们除了传统的NESTLOOP,MERGE JOIN,HASH JOIN。这样看来又多了一种优化JOIN的方式。

总结:
bitmap我们可能平时使用的不多,但是觉得它在特殊的应用场景,还 是有优势的。bitmap join index更是一种多表JOIN的新方式,很有意思。



在Vmware上安装Oracle 11gR2 的安装简易手册

2010-05-20

有2人发表了评论 | 赶紧发表评论吧 | 作者:jametong

有感于今天内部被问到的一个问题

1. 运行如下的rpm命令,找到安装Oracle需要而没有安装的rpm包.并mount Redat安装盘安装这些包.

rpm -qv binutils-2.17.50.0.6 \
compat-libstdc++-33-3.2.3 \
elfutils-libelf-0.125 \
elfutils-libelf-devel-0.125 \
elfutils-libelf-devel-static-0.125 \
gcc-4.1.2 \
gcc-c++-4.1.2 \
glibc-2.5-24 \
glibc-common-2.5 \
glibc-devel-2.5 \
glibc-headers-2.5 \
kernel-headers-2.6.18  \
ksh-20060214 \
libaio-0.3.106 \
libgcc-4.1.2 \
libgomp-4.1.2 \
libstdc++-4.1.2  \
libstdc++-devel-4.1.2 \
make-3.81 \
sysstat-7.0.2 \
libaio-devel-0.3.106  \
unixODBC-2.2.11  \
unixODBC-devel-2.2.11
mkdir /media/cdrom/
mount -t iso9660 /dev/cdrom /media/cdrom/
cd /media/cdrom/Server/
rpm -ivh sysstat-7.0.2-1.el5.i386.rpm libaio-devel-0.3.106-3.2.i386.rpm unixODBC-2.2.11-7.1.i386.rpm unixODBC-devel-2.2.11-7.1.i386.rpm

2. 关闭大部分无用的服务(可能因人因环境而异,这只是我的个人喜好).

chkconfig NetworkManager --level 2345 off
chkconfig NetworkManagerDispatcher --level 2345 off
chkconfig acpid --level 2345 off
chkconfig apmd --level 2345 off
chkconfig auditd --level 2345 off
chkconfig avahi-dnsconfd --level 2345 off
chkconfig bluetooth --level 2345 off
chkconfig capi --level 2345 off
chkconfig conman --level 2345 off
chkconfig cpuspeed --level 2345 off
chkconfig cups --level 2345 off
chkconfig dhcdbd --level 2345 off
chkconfig dund --level 2345 off
chkconfig firstboot --level 2345 off
chkconfig haldaemon --level 2345 off
chkconfig hidd --level 2345 off
chkconfig ip6tables --level 2345 off
chkconfig ipmi --level 2345 off
chkconfig iptables --level 2345 off
chkconfig irda --level 2345 off
chkconfig isdn --level 2345 off
chkconfig kdump --level 2345 off
chkconfig kudzu --level 2345 off
chkconfig lvm2-monitor --level 2345 off
chkconfig mcstrans --level 2345 off
chkconfig mdmonitor --level 2345 off
chkconfig mdmpd --level 2345 off
chkconfig messagebus --level 2345 off
chkconfig microcode_ctl --level 2345 off
chkconfig multipathd --level 2345 off
chkconfig netconsole --level 2345 off
chkconfig netfs --level 2345 off
chkconfig netplugd --level 2345 off
chkconfig nfs --level 2345 off
chkconfig nfslock --level 2345 off
chkconfig nscd --level 2345 off
chkconfig ntpd --level 2345 off
chkconfig pand --level 2345 off
chkconfig pcscd --level 2345 off
chkconfig portmap --level 2345 off
chkconfig psacct --level 2345 off
chkconfig rdisc --level 2345 off
chkconfig readahead_later --level 2345 off
chkconfig restorecond --level 2345 off
chkconfig rhnsd --level 2345 off
chkconfig rpcgssd --level 2345 off
chkconfig rpcidmapd --level 2345 off
chkconfig rpcsvcgssd --level 2345 off
chkconfig saslauthd --level 2345 off
chkconfig sendmail --level 2345 off
chkconfig setroubleshoot --level 2345 off
chkconfig smartd --level 2345 off
chkconfig vncserver --level 2345 off
chkconfig vsftpd --level 2345 off
chkconfig wdaemon --level 2345 off
chkconfig winbind --level 2345 off
chkconfig wpa_supplicant --level 2345 off
chkconfig ypbind --level 2345 off

3. 修改系统参数(这只是我的测试环境,具体环境需要做适应性的调整,而且我这个虚拟机,毋需考虑BigPage,生产环境可能需要考虑,特别是64位环境下的11g环境).

--修改/etc/sysctl.conf
fs.aio-max-nr = 1048576
fs.file-max = 6815744
kernel.shmall = 2097152
kernel.shmmax = 536870912
kernel.shmmni = 4096
kernel.sem = 250 32000 100 128
net.ipv4.ip_local_port_range = 9000 65500
net.core.rmem_default = 262144
net.core.rmem_max = 4194304
net.core.wmem_default = 262144
net.core.wmem_max = 1048586

--修改完毕后, 运行sysctl -p使其生效.

4. 添加用户/组信息.

groupadd -g 501 oinstall
groupadd -g 502 dba
groupadd -g 503 oper
groupadd -g 504 asmadmin
groupadd -g 505 asmdba
groupadd -g 506 asmoper

useradd -g oinstall -G dba,oper,asmdba oracle
useradd -g oinstall -G asmadmin,asmdba,asmoper grid

5. 修改安全配置信息,

  • 1) . Add the following lines to the /etc/security/limits.conf file: (the following example shows the software account owner oracle):
  • 
    oracle              soft    nproc   2047
    oracle              hard    nproc   16384
    oracle              soft    nofile  1024
    oracle              hard    nofile  65536
    
    grid              soft    nproc   2047
    grid              hard    nproc   16384
    grid              soft    nofile  1024
    grid              hard    nofile  65536
    
  • 2). Add or edit the following line in the /etc/pam.d/login file, if it does not already exist:
  • session    required     pam_limits.so
    

    6. 修改/etc/profile 信息,修改Oracle/grid用户登陆后的系统权限.
    For the Bourne, Bash, or Korn shell, add the following lines to the /etc/profile file (or the file
    on
    SUSE Linux Enterprise Server systems /etc/profile.local):

    
    if [ $USER = "oracle" ]; then
      if [ $SHELL = "/bin/ksh" ]; then
        ulimit -p 16384
        ulimit -n 65536
      else
        ulimit -u 16384 -n 65536
      fi
      umask 022
    fi
    
    if [ $USER = "grid" ]; then
      if [ $SHELL = "/bin/ksh" ]; then
        ulimit -p 16384
        ulimit -n 65536
      else
        ulimit -u 16384 -n 65536
      fi
      umask 022
    fi
    

    7. 创建基本的安装目录.并赋予其权限.

    mkdir -p /opt/app/
    mkdir -p /opt/app/oracle/products/11.2.0/
    chown -R oracle:oinstall /opt/app/oracle
    
    mkdir -p /opt/app/grid/products/11.2.0/
    chown -R grid:oinstall /opt/app/grid
    
    chown grid:oinstall /opt/
    chown grid:oinstall /opt/app/
    chown grid:oinstall /opt/app/oracle/
    

    8. 修改用户profile信息.

  • 1). 修改grid用户的profile文件/home/grid/.bash_profile
  • export ORACLE_BASE=/opt/app/oracle
    export ORACLE_HOME=/opt/app/grid/products/11.2.0
    export ORACLE_SID=+ASM1
    CLASSPATH=$ORACLE_HOME/JRE:$ORACLE_HOME/jlib:$ORACLE_HOME/rdbms/jlib:$ORACLE_HOME/network/jlib
    export ORACLE_BASE ORACLE_HOME ORACLE_SID ORA_NLS33 PATH  CLASSPATH TNS_ADMIN
    
    PATH=$ORACLE_HOME/bin:/usr/bin:/etc:/usr/bin/X11:/usr/ccs/bin:/usr/bin:/etc:/usr/sbin:/usr/ucb:/usr/bin/X11:/sbin:/usr/java14/jre/bin:/usr/java14/bin:/usr/local/bin:.:$PATH
    export  PATH
    export PATH=$PATH:$ORACLE_HOME/OPatch
    
    # ORACLE_TERM=xterm; export ORACLE_TERM
    NLS_LANG=AMERICAN_AMERICA.US7ASCII; export NLS_LANG
    
    LD_LIBRARY_PATH=$ORACLE_HOME/lib:/lib:/usr/lib:/usr/openwin/lib
    LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/td/lib:/usr/ucblib:/usr/local/lib
    export LD_LIBRARY_PATH
    export NLS_DATE_FORMAT="YYYY-MM-DD HH24:MI:SS"
    export UDUMP=/opt/oracle/admin/forum/udump
    export BDUMP=/opt/oracle/admin/forum/bdump
    export SQLPATH=$ORACLE_HOME/sqlplus/admin:~/admin/sql/:~/admin/mysql
    alias sql='sqlplus /nolog'
    alias lm='ls -al'
    export DISPLAY=192.168.68.1:0.0
    
  • 2). 修改Oracle用户profile信息.
  • 
    export ORACLE_BASE=/opt/app/oracle
    export ORACLE_HOME=/opt/app/grid/products/11.2.0
    export ORACLE_SID=rac1
    CLASSPATH=$ORACLE_HOME/JRE:$ORACLE_HOME/jlib:$ORACLE_HOME/rdbms/jlib:$ORACLE_HOME/network/jlib
    export ORACLE_BASE ORACLE_HOME ORACLE_SID ORA_NLS33 PATH  CLASSPATH TNS_ADMIN
    
    PATH=$ORACLE_HOME/bin:/usr/bin:/etc:/usr/bin/X11:/usr/ccs/bin:/usr/bin:/etc:/usr/sbin:/usr/ucb:/usr/bin/X11:/sbin:/usr/java14/jre/bin:/usr/java14/bin:/usr/local/bin:.:$PATH
    export  PATH
    export PATH=$PATH:$ORACLE_HOME/OPatch
    
    # ORACLE_TERM=xterm; export ORACLE_TERM
    NLS_LANG=AMERICAN_AMERICA.US7ASCII; export NLS_LANG
    
    LD_LIBRARY_PATH=$ORACLE_HOME/lib:/lib:/usr/lib:/usr/openwin/lib
    LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/td/lib:/usr/ucblib:/usr/local/lib
    export LD_LIBRARY_PATH
    export NLS_DATE_FORMAT="YYYY-MM-DD HH24:MI:SS"
    export UDUMP=/opt/oracle/admin/forum/udump
    export BDUMP=/opt/oracle/admin/forum/bdump
    export SQLPATH=$ORACLE_HOME/sqlplus/admin:~/admin/sql/:~/admin/mysql
    alias sql='sqlplus /nolog'
    alias lm='ls -al'
    

    9. 修改/etc/hosts信息,配置RAC信息.

    
    192.168.68.128    rac1-priv.localdomain rac1-priv
    192.168.68.129    rac2-priv.localdomain rac2-priv
    
    192.168.130.128    rac1.localdomain rac1
    192.168.130.129    rac2.localdomain rac2
    
    192.168.130.200   rac-cluster
    192.168.130.210   rac1-vip.localdomain rac1-vip
    192.168.130.220   rac2-vip.localdomain rac2-vip
    

    10. 配置两台主机的ssh 通道.
    分别在root/oracle/grid用户下运行下述命令.

    
    ssh-keygen -b 1024 -t dsa
    

    11. 关闭两个VM节点, 为其添加一块基于Nat的网卡,以及新增两块磁盘(一块作为OCR/Voting Disk用,一块作为SharedStorage使用).
    修改VM文件的配置,新增的磁盘配置为.

    
    disk.locking = "FALSE"
    diskLib.dataCacheMaxSize = "0"
    diskLib.dataCacheMaxReadAheadSize = "0"
    diskLib.dataCacheMinReadAheadSize = "0"
    diskLib.dataCachePageSize = "4096"
    diskLib.maxUnsyncedWrites = "0"
    
    scsi1.present = "TRUE"
    scsi1.virtualDev = "lsilogic"
    scsi1.sharedBus = "VIRTUAL"
    
    scsi1:0.redo = ""
    scsi1:0.present = "TRUE"
    scsi1:0.fileName = "E:\vm\SharedDisk\OCRDisk.vmdk"
    scsi1:0.mode = "independent-persistent"
    scsi1:0.deviceType = "disk"
    
    scsi1:1.redo = ""
    scsi1:1.present = "TRUE"
    scsi1:1.fileName = "E:\vm\SharedDisk\RacShareDisk.vmdk"
    scsi1:1.mode = "independent-persistent"
    scsi1:1.deviceType = "disk"
    

    12. 运行下面的脚本给/dev/sdb;/dev/sdc 进行分区

    
    fdisk /dev/sdc <<_END_
    n
    e
    1
    
    n
    l
    1
    128
    n
    l
    
    256
    n
    l
    
    384
    n
    l
    
    512
    n
    l
    
    640
    n
    l
    
    p
    w
    _END_
    

    13. 在/etc/udev/rules.d/50-udev.rules中添加下面这行配置.

    
    KERNEL=="sd[bc]*",OWNER="grid",GROUP="oinstall",MODE="0660",OPTIONS="last_rule"
    

    并运行/sbin/start_udev使其权限修改生效,否则每次Os重启,文件的权限都会被改成root:disk



    Linux一些页的东西

    2010-05-06

    有7人发表了评论 | 赶紧发表评论吧 | 作者:vogts

    在Linux世界里,分为Page cache,Buffer cache两个层面。其中page cache包含了buffer cache,内存只和page cache交互。

    标准的LINUX总 是假定处理器有三级页表,分别为页目录表(PGD),中间页目录表(PMD)和页表(PTE)。如果程序在进行物理地址转换的时候,中是通过页目录表来索 引中间页目录表,再通过中间页目录表来索引页表,从而查找到某页与内存BLOCK块的对应关系。我把书上的图,拍下来了,哈:

    我们平时只说,page in,page out。file,page cahe,buffer cache,block的关系如下图:

    我们主要讨论下 linux的页的生命:

    在讨论page之前,必须要提及 LINUX的几个关键进程:  kscand, kswapd, kupdated, bdflush. 他们负责LINUX的内存管理。
    一般内存的一页,生命周期如下图:
    free –  在系统启动以后,初始化状态均为FREE。
    active -- 当前被OS使用或者用户进程所使用的状态,均为active。
    inactive dirty(脏数据) -- 当数据被修改过以后,kscand进程会周期性的对内存中的数据进行扫描。如果最近被访问过了,计数器就会加1,否则被减1。如果kscand发现计数器 是0的时候,就会把该页,放到非队列(inactive laundry)里去了。
    inactive laundry(非活动队列) -- 当被放到inactive laundry里的时候,OS才真正的进行I/O操作,写入磁盘。
    inactive clean -- 意味着数据已经被sync到了本地磁盘,该页也已经标示为free。
    另:
    如果想自己强行把脏数据,写到磁盘 上,就使用“sync”命令。
    个人总结:
    今天看了一个晚上这方面的书和文档,感觉LINUX的机制和ORACLE大同小异啊。其实这个世界什么东西,都大同小异啊,关键 是你要想明白,计算机世界永远离不开if,else,for;就如同汽车世界永远离不开发动机,地盘,等等。LINUX本身也有LRU,只是有的地方叫 “Buddy Allocator”。


    Oracle高可用架构

    2010-03-19

    目前还没有人发表评论 | 赶紧发表评论吧 | 作者:jametong

    今天在Uwe Hesse的Blog上看到这篇文章,感觉很不错,简要地描述Oracle MAA架构的所有相关产品,虽然之前就有接触所有这些解决方案,但解释的如此清楚明了的还是第一次看到,特将其翻译如下. 原文: Oracle Database HA Architecture

    Oracle高可用架构
    作者: Uwe Hesse, 译者: Jametong
    Oracle高可用架构是我所讲课程里的一个热门话题.本文尝试对此话题做一个总体的说明,内容涵盖”普通的”单实例数据库,DataGuard,RAC以及扩展RAC(有时也被称为”伸展集群”).Rac与Dataguard组合在一起就是Oracle公司推广的最大可用性架构(Maximum Availability Architecture,MAA).除这些Oracle的HA解决方案外,我还会简单介绍一个第三方的HA解决方案(远程镜像,Remote Mirroring).我不准备深入介绍所有这些解决方案的细节,而只是想做出一点区分的工作,并简要介绍它们各自的优势以及可能的缺陷.
    首先,我们将考察目前为止仍然是应用最为广泛的Oracle数据库架构:单实例数据库.一个Oracle数据库总是由一个数据库(由数据文件,在线重做日志控制文件组成)与一个实例(有内存结构,比如数据库高速缓冲区;以及后台进程,例如数据库写进程)组成.如果我们有一个数据库以及多个访问这个数据库的实例,这就是一个RAC.如果只有一个实例访问这个数据库,就是单实例数据库.下图是一个所有组件都存储在一个服务器上的简单安装版本:

    将数据库文件放置在SAN(存储区域网络)的配置也是目前比较常见的配置,如下图所示:

    从高可用的角度来看,这个架构是非常脆弱的:服务器A与服务器B都是单点故障,数据库A与数据库B也都是单点故障.从而这些服务器所在的站点也是单点故障.这样,只要其中一个单点发生故障,整个数据库将不可用.一个”普通的”RAC就是为了解决其中的服务器单点故障的,如下图所示:

    如果两个服务器的其中一个发生故障,数据库C将仍然可用.当然,使用RAC并不仅仅是为了实现HA.在使用RAC的其它的理由中,一个比较可靠的理由是为了实现伸缩性(Scalability):如果应用需求在将来出现增长,我们可以通过添加新的节点(Node)到集群中来解决.另外,通过使用RAC我们还可以选择使用服务管理(Service-Management)与负载均衡(Load Balance).简言之,RAC不仅仅是HA,在此详述其它原因已经超出本文的范畴了.从HA的视角看,使用RAC的缺陷是:数据库C以及相应的站点C是单点.如果站点C发生故障(比如火灾),数据库C将不可用.因此,将数据库伸展到两个站点就成为我们的选择了,这也是通常所说的扩展RAC.

    现在,这两个站点就不再是单点了.数据库D是在两个站点之间做镜像的.这个架构的缺陷是两个站点之间的网络连接的成本,如果两个站点之间的距离比较远的话.这很关键,因为需要镜像的数据量会非常大.实际上,这使得两个站点之间的距离局限于几公里以内,而这与想要实现的灾难保护目标之间有冲突.这时,Dataguard就隆重登场了:利用DataGuard,我们很容易就可以实现长距离的灾难保护,因为,此时我们不再需要传输所有的数据量,而仅仅需要传输(相对小)的重做日志.在下图中,每个服务器都像上面的服务器A与服务器B一样,只有一个实例:

    备用数据库由来自主数据库的重做日志. 当主库发生故障时,我们可以失败切换(Failover)到备库上并继续有效工作.这个失败切换工作可以由一个Observer(观察员程序,通常称为快速启动失败切换,Fast-Start Failover)自动实现.两个站点之间的距离达到几千公里(依赖于重做日志的传输策略与保护级别(protection level)).如果我们将RAC与Dataguard集合起来,我们就实现了MAA.显然,MAA是一个昂贵的解决方案,不过它也同时享有RAC与Dataguard的所有好处.
    远程镜像是一个广受欢迎的第三方HA解决方案.总体上,它的架构与下图类似:

    这时,也没有哪个站点是单点的,类似于使用扩展RAC架构.这个解决方案的缺陷是:站点间的距离也是严格受限制的,理由与扩展RAC架构类似.同时,在镜像进行的时候,第二个站点是无法提供服务的,这一点与上述的Oracle提供的HA解决方案不同.使用RAC时,所有的服务器与相应的站点都可以提供服务.哪怕是使用Dataguard,备用数据库也不仅仅是等待主库发生故障.除此之外,它还可以提供只读访问服务,这将可以有效降低主库的负载.

    上图展示了Oracle 11g的新特性”物理备用数据库上的实时查询”.在恢复过程中时,备用数据库也在同时提供只读访问.另外,还可以在物理备库(Physical Standby)上做离线备份(OffLoad Backup).

    附注:
    其实还有另外一种可选择的架构. 基于Data Guard与单实例的一个结合.
    类似于上面介绍的远程Data Guard方案, 做了一点修正: 将当前主库的一组Redo 日志放到远程(当然也受限于距离所产生的San 访问延时).



    使用数据库存图片

    2010-03-11

    有12人发表了评论 | 赶紧发表评论吧 | 作者:八神

    图片是网站上很重要的资源,用户发布的产品图片,用户的logo,AV男,兽兽女,犀利哥等等等等,一个稍具规模的网站,图片的数量可能是千万级,这种资源的特点就是文件小,数量大,每个文件在几字节到几K不等,所以针对图片的访问,基本是非常离散的IO,考验的是系统的磁盘并发和CPU处理能力

    一般网站,图片都是存放专门的图片服务器,可集中也可以分布式,比如有条件的可以购买昂贵的NAS存储,由主机拖着,以NFS或者HTTP的方式,供前面的应用访问,或者使用多台廉价PC,图片分布打散到每个机器,前台应用解决图片存取和访问策略,以便充分利用所有资源,在应用与图片服务器中间还可能加一层cache,来缓冲前台的访问压力

    上面所说的方法,弊端是有很多的
    1.文件系统一定是要使用的,为了管理数千万的图片,必须进行目录分层,因为一个目录下不可能存放太多的文件,前期的目录规划要考虑后期的扩展,还有图片分布均匀,这样做下来,往往目录的深度会达到5层甚至7层
    2.数据迁移,备份怎么做?高级的NAS存储,有自己的卷复制,但这个粒度太粗,如果要细分到底层或中间层,会有点力不从心,对于这种大数据量的小文件拷贝,PC也是非常吃力
    3.每一个图片的访问,基本会做5-7次的目录跳转,转换到磁盘,也可能有10次物理IO了,在并发量上来的时候,磁盘会是个瓶颈,当然,分布式是个方向

    这里想到了另外一种方法,利用数据库来存放图片,存储的方式就是现在最流行的key-value

    create table mypic
    (
    key number not null,
    pic blob
    );

    create index ind_picid on mypic(key);

    key是图片名称,事先最好统一规划一下,使用number数字来命名,并针对key建立索引
    VALUE就是图片内容,用blob来保存
    访问一个图片的方式就是:
    获取图片名称  数据key
    走key上的索引,定位到记录表记录
    根据表记录,定位到图片的blob,并读取

    这个方法的优点:
    1.单个图片的访问路径缩短,数字索引比较小,分区做的好的话可能小到两层,从索引到表,到blob段,大概是4-5跳
    并且KEY是区分度非常高的数据类型,在索引每层的横向检索中,不会超过1个数据块,而文件目录结构中,子目录繁多,
    要遍历这层的inode后,才知道具体跳转的下层位置
    2.备份方式比较灵活,可以基于整个数据库,或者单独的表,数据的迁移,删除,都是基于表级别的,比较直观,方便
    3.可以在数据库层面,考虑图片的水平拆分,垂直拆分,分表,分库,都不错
    4.数据库的复制技术可以派上用场,读写分离

    这里数据库完全是当成一个KEY-VALUE的存储在用,我们可以考虑将一些廉价的PC堆在一起,做成一个picdb的群集,
    大致画了一个草图,当然在WEB与picdb间应该还有层cache的,这个方法是YY出来的,可能很多地方不成熟,但SY强身,YY强国,没有想法,哪来的动力?欢迎各位专业人士拍砖

    ppp1



    不平衡的索引?

    2010-03-02

    有1人发表了评论 | 赶紧发表评论吧 | 作者:jametong

    本文翻译自Jonathan Lewis早年写在dbazine上的文章unbalanced indexes? 本文的word版本可以到此处下载

    原文参见: 不平衡的索引?

    不平衡的索引?
    by Jonathan Lewis

    网络上有多篇介绍Oracle索引实现机制的文章,都提及需要经常重建索引.在这些文章中的某处,总是会出现这样一段简短的描述,索引会如何变的不平衡,以及可能导致的后果.很不幸,它们好像忽视了这样一个事实,Oracle使用的B-tree机制是一种”平衡B-tree”索引,也就是说,索引无法变得不平衡.

    “平衡”到底意味着什么?

    既然Oracle的索引使用的是平衡B-tree,为什么还有如此多的人相信他们的索引会变得不平衡呢?
    另外,平衡B-tree到底又是什么呢?
    第二个问题的答案可能能够帮助我们得到第一个问题的答案.
    从技术角度讲,平衡B-tree的显要特性是,在任意时间点,任何叶子节点(leaf block)到根节点(root block)的距离都相等,平衡是指从顶部到底部的平衡.
    就Oracle来说,执行一个treedump命令就可以很容易发现这一点,如图-1所示:

    select	object_id
    from	user_objects
    where	object_type = 'INDEX'
    and	object_name = 'T1_IDX1'
    -- and 	subobject_name = . . .
    ;
    alter session set events '
    'immediate trace name treedump level N';

    图-1: 一次索引树转储涉及到的步骤

    首先,需要找到你想要转储(dump)索引或者索引分区(index partition)的object_id;接着,将object_id作为level的参数来调用treedump事件. 如果检查这个索引树(tree dump)转储生成的跟踪文件,你将发现类似于图-2中所示的结果.

    branch: 0x14001aa 20971946 (.. level 2)
      branch: 0x14003ef 20972527 (.. level 1)
        leaf: 0x14001ab 20971947 (..)
        leaf: 0x14001ac 20971948 (..)

    图-2: 从索引树转储结果中提取出的一段内容

    这个跟踪文件以递归降序的方式展示索引的分支块(branch block,根块只是分支块的一个特例)与叶子块.注意,转储内容的第一个块(根块)记录了一个高度(level),并且它下面的每一个分支块也都记录了一个高度,但是叶子块没有记录高度.
    根块的高度就是索引的blevel(执行analyze index命令之后会记录在视图dba_indexes中).索引的高度(height,执行命令validate index后会记录在视图index_stats中)就是blevel + 1.
    每一个叶子块到根块的距离就正好有这么多步.索引总是平衡的.
    那么为什么有那么多人相信Oracle会允许索引变得不平衡呢?

    我应负的责任

    此时,我必须承认我有罪,仅仅在一年前(2002年5月),我也重复了一个关于块分裂(index block split)的众所周知但是却完全错误的描述.虽然我知悉(在我的书(2000年12月,Practical Oracle 8i)中对此做了说明) treedump的细节,但我还是这样做了.
    我猜想,这种错误的观念最初产生于Oracle5(很多年以前)手册的说明,其大意是,因为”没有一个叶子块到根块的距离比任何其他叶子块到根块的距离远.”, 所以Oracle索引是平衡的.将这个表述与一个过于简化的块分裂的图结合到一起,瞧,一个几乎牢不可破的神话就诞生了.
    图-3和图-4描绘了一个非常常见,但是完全错误的关于叶子块如何分裂的概念.

    图-3: 叶子块将要分裂前的索引结构

    图-4 这不是叶子块分裂发生的方式
    有这样一个流传广泛的说法,叶子块分裂到两个全新的块,这两个块分享它的所有数据;接着, Oracle在原来的叶子块的位置插入一个新的分支块来持有指向这两个块的指针.因此,在这样一个错误的视角下,这个索引的右边就会比左边更深.(经常会有人说,创建在基于序列的字段上的索引会带来最大的问题,因为,从这个理论来推导,最右边的叶子块到根块的距离会越来越远,没分裂一次,就会降低一层.)
    事实上,这个工作Oracle做得更加精致前瞻并有效得多. 图-5所示是一个复杂的叶子块分裂的结果.

    图-5: 一次递归分裂后的索引结构图
    由于这个叶子块分裂成了两个块,Oracle会尝试往当前指向这个叶子块的分支块中插入一个额外的指针.
    但是,如果这个分支块也满了,Oracle会继续将这个分支块也分裂成两个块,并且在两个分支块之间分配现存块里的指向叶子块的指针,并且(递归地) 在这个分支块的上一级的分支块中插入一个指向这个新的分支块的新的分支指针.
    如果在这个过程中,Oracle抵达了根块,然而这个根块也满了,那么根块也必须分裂.在这种情况下,Oracle将创建一个新的根块来持有这两个分支指针.(事实上,Oracle处理根块分裂的方式与处理普通的分支块的分裂有一点细微的差异,以确保无论根块上发生了多少次的分裂,总是可以在物理段的同一个位置找到根块.)
    注意,这个递归的分裂操作这样沿着索引树不断攀升意味着,无论何时,索引总能保持平衡.

    为什么这个神话如此牢固?

    为什么这个关于不平衡的索引的神话能够长盛不衰,是否存在一些原因呢?我这答案是确实存在一些原因.
    要切记,当我们讨论B-tree的时候,单词”balanced”的定义有非常严格的含义.然而,这个单词还有一种完全不同的解释.
    例如,你将如何描述图-6中的这个索引,其中根块指向六个叶子块,但是其中一个叶子块是空的,有三个块几乎是空的,还有两个块塞的很满.(注意,从根块到叶子块的这些额外的说明是为了强调索引填充分布的多么不均匀;实际上,根块到每个叶子块都只有一个指针.)

    图-6 “不平衡”的一种非技术理解

    看到这种图案的索引,一个”人”的真实反应都会人为它”不平衡”.很明显,索引的右手边要比左手边”重”.很不幸,当技术表达意味着完全不同的东西的时候,这种非正式的人为表达应该更加恰当.
    或许正是这种技术表达与非正式的人为表达之间的冲突导致了这种混淆.
    在这种非正式的意义上,在基于序列值的字段上索引很容易就会变得”不平衡”,特别是它们被用来表征/处理先进先出(FIFO)队列机制的时候.然而,即使它们(在非正式语境中)是不平衡的,它们(在技术上)仍然是平衡的B-tree.
    (推动使用类似于”扭曲”或者”分布不均匀”作为术语来描述这种类型的索引,或许会是个好主意.)
    有时,仅仅几篇草率地使用术语的文章或报告就可能构建一个神话,在这个例子中,就是一个导致众多DBA浪费无数小时的时间去做不必要的索引重建的神话.
    记住,你下一次认为Oracle表现愚蠢或者低效的时候,很可能问题是出在一个古老的误解上面,而不是Oracle软件本身的问题.

    告警提醒

    如果你还想进一步地研究,treedump选项还有一系列的问题需要注意.对于大部分的Oracle版本,它看似对索引段中的每个块都生成一行输出,这样可能会非常昂贵并且速度缓慢,因为它需要按顺序访问索引中的每一个数据块.然而,在Oracle 9.0中,跟踪文件看似会对每一个块做一个整块转储,这样会使得转储文件非常巨大,转储速度也会非常缓慢.
    第二个问题是所有版本都一致的.如果这个索引是在定义主键约束或者唯一键约束时生成的,Oracle就会设置ind$表的flags字段的第13位,而这将导致treedump程序崩溃并报出错误”invalid value.”分区索引的分区段不会产生这个问题,但是对于所有其他类型的主键索引与唯一索引(包含非分区索引组织表,IOT),这都很恼人.先创建索引,再基于这个索引创建约束通常是个好主意, 这样处理可以避免除索引组织表外的其他所有条件下的问题. 在紧急情况下,你可以修改ind$表来清除这一位,但是很明显,需要先取得Oracle支持的认可.

    结论

    当谈到平衡B-tree索引的时候,术语”平衡”指的是从顶部到底部,而不是从左到右.
    Oracle确实实现了一个版本的”平衡B-tree索引”,因此在任何时候,索引中的所有叶子块到根块的距离都是完全相同的,如果最近对这个索引作过分析的话,可以从视图user_indexes的字段blevel找到它,如果刚刚对这个索引执行过validate index的话,可以从视图index_stats的height字段(等价于blevel+1)得到.
    当听到你应该经常重建索引,因为”这些索引已经变得不平衡”时,要抵制这种理由.因为它不是一个靠得住的理由.



    为什么Oracle不使用我的索引?!

    2010-03-02

    目前还没有人发表评论 | 赶紧发表评论吧 | 作者:jametong

    本文翻译自Jonathan Lewis发表在DBAZine上的文章:Why Isn’t Oracle Using My Index?!,可以从此处下载本文的Word版本.

    原文参见: 为什么Oracle不使用我的索引?!

    为什么Oracle不使用我的索引?!
    by Jonathan Lewis
    标题的这个问题可能是在Metalink论坛与Usenet新闻组出现的最频繁的问题了.这篇文章使用一个测试用例(可以在你自己的系统来重现的)来演示基于成本的优化器的基本工作原理.在看完这篇文章之后,当再次遇到这个令人讨厌的问题时,你应该就可以自信的解答了.
    由于在安装Oracle的时候存在大量的选项,因此当某人执行一条你口授的脚本时,通常很难精确的预测即将出现什么结果. 当时我想要尝试一下,希望你的数据库选择了一个相对普通的安装选项,并且最常用的关键的参数是取得默认值. 这个例子是在Oracle 8.1.7下创建并测试的,参数db_block_size被设置成最常用的值(8k),参数db_file_multiblock_read_count也设置了一个很常用的值(8).在Oracle 9.2下跑图-1中的这个脚本(创建了一组表,在表上添加索引并分些表与索引),结果可能出现部分差异.

    create table t1 as
    select 	trunc((rownum-1)/15)	n1,  trunc((rownum-1)/15)	n2, rpad('x', 215)		v1
    from all_objects
    where rownum <= 3000;
    create table t2 as
    select
        mod(rownum,200) n1,
        mod(rownum,200) n2,
        rpad('x',215) v1
        from all_objects
        where rownum <= 3000;
    create index t1_i1 on t1(N1);
    create index t2_i1 on t2(n1);
    analyze table t1 compute
    statistics;
    analyze table t2 compute statistics;
    

    图 1: 测试的数据集
    在你准备好数据之后,你可能认为这两组数据是一样的,尤其是,在两个数据集中字段N1有的值范围相同(从0-199),并且每个值都出现了15次.你可能会这样检查数据:

    select n1, count(*)
    from t1
    group by n1;
    

    查询表T2也会证实你的上述观点.
    如果你接着执行下面的查询语句:

    select * from t1 where n1 = 45;
    select * from t2 where n1 = 45;
    

    你将发现每个查询语句都返回了15条记录.然而如果你执行

    set autotrace traceonly explain
    

    语句,你将发现这两个查询语句拥有两种不同的执行路径.针对表T1的查询使用了索引,而针对表T2的查询则做了一个全表扫描.
    因此,在拥有完全相同的数据的情况下,同一个查询语句戏剧性地出现了两个不同的执行路径.

    索引到底怎么了?

    注意: 如果你曾经听到如下的关于使用索引的”魔法”准则,例如,”在数据少于23%/10%/2%(随机取一个数字)的时候,Oracle将使用索引,”那么,此时你将怀疑他们的准确性.例如,在这个例子中,Oracle对于一个在3000记录中取15条的查询使用了全表扫描,仅仅0.5%的数据.
    要调查诸如此例的问题,只有一个手段(我经常首先尝试使用的):添加一些索引以使得Oracle选择我们认为它应该选择的执行路径,并检查这能否给我们部分提示.
    在这个例子中,添加一个简单的提示:
    /*+ index(t2, t2_i1) */
    就足够让Oracle从选择全表扫描切换到选择使用索引访问.图-2展示了这三种路径的成本(简化为C=nnn).

    select * from t1 where n1 = 45;
    EXECUTION PLAN
    --------------
    TABLE ACCESS BY INDEX ROWID OF T1 (C=2)
      INDEX(RANGE SCAN) OF T1_I1 (C=1)
    
    select * from t2 where n1 = 45;
    EXECUTION PLAN
    --------------
    TABLE ACCESS FULL OF T2 (C=15)
    
    select /*+ index(t2 t2_i1) */
        *
    from t1
    where n1 = 45;
    EXECUTION PLAN
    --------------
    TABLE ACCESS BY INDEX ROWID OF T2 (C=16)
      INDEX(RANGE SCAN) OF T2_I1 (C=1)
    

    图 2: 不同的查询以及它们的成本
    因此,为什么在T2相关的查询中Oracle没有选择索引作为默认的执行路径?很简单,正如执行计划所展示,选择执行全表扫描的成本要低于使用索引的成本.

    为什么使用全表扫描成本更低?

    当然,这仅仅是在重复问题.为什么全表扫描的成本会低于使用索引的成本呢?
    通过深入调查这个问题,你将揭开了基于成本的优化器的关键机制(也是致命的错误假设).
    让我们通过运行下面这个查询来开始我们的考查:

    select
         table_name,
         blevel,
    	   avg_data_blocks_per_key,
    	   avg_leaf_blocks_per_key,
    	   clustering_factor
    from	user_indexes;
    

    下表是对应的输出结果:

      T1 T2
    Blevel 1 1
    Data block / key 1 1
    Leaf block / key 1 15
    Clustering factor 96 3000

    请特别关注”data block per key”的值. 如果你执行一个完全基于这个索引的键值的等值校验的查询语句,这个值就是Oracle认为必须访问的不同数据块的数量.
    因此,我们的查询语句的成本是从哪儿来得呢?就Oracle而言,如果输入的键值为45,当从表T1获取数据时,我们就可以访问一个索引叶子块以及一个表块(两个块),因此成本为2.
    如果对表T2执行同样的查询,我们就必须访问一个索引叶子块以及15个表块(总共16个块),因此成本为16.
    很明显,根据这种观点,表T1的索引比表T2的索引更加理想.不过,这就带来了两个未解决的问题:
    全表扫描的成本来自何处,为什么两张表之间的avg_data_blocks_per_key的数值差别如此大?
    第二个问题的答案很简单.回头再看看表T1的定义,它使用trunc()函数来生成N1的值,使用”rownum -1″除以15并截成整数.

         Trunc(675/15) = 45
         Trunc(676/15) = 45
               …
         Trunc(689/15) = 45
    

    所有值为45的记录实际上都是一条接着一条连续(可能所有的都能够保存到一个数据块中)的出现的.
    表T2使用mod()函数来生成N1的值,使用对rownum取200的模的方式.

          mod(45,200) = 45
          mod(245,200) = 45
                …
          mod(2845,200) = 45
    

    值为45的记录要隔200才会在表中出现一次(可能导致每一个相关数据块中都不会超过1条记录).
    通过对表的分析,Oracle可以得到我们表中的数据分布的完美的描述.从而优化器才确切的明白,对于我们的查询,Oracle将需要访问多少个数据块,在这个简单的例子中,这个查询的成本就是需要访问的数据块的数量.

    但是为什么是全表扫描呢?

    我们看到,对于同样的执行路径,对表T2进行索引访问的成本要远远高于对表T1的索引访问成本,但是为什么会选择使用全表扫描呢?
    这个问题将让我们发现Oracle做的两个过于简单甚至不恰当的假设.
    第一个假设是,每个块访问都视为一次物理磁盘读,第二个假设是,多块读的速度与单块读一样.
    因此,这些假设将对我们的这个实验产生什么影响?
    如果使用下面的查询语句查询user_tables表:

    select
          table_name,
          blocks
    from user_tables;
    

    你将发现这两张表每个都是占用96个数据块.
    在文章开头,我说过这个测试用例运行在一个db_file_multiblock_read_count的值为8的版本为8的Oracle系统系统上.
    粗略地讲,Oracle认为它可以通过12(96/8)次磁盘读请求来读出所有的96个数据块.
    由于通过索引访问表需要16个块(等于物理读)请求, 从Oracle的可悲的受骗的视角看的话,选择全表扫描显得更清晰也更快捷.
    瞧!如果你要访问的数据适当地散布在表上,即使只有很小比例的数据也会选择使用全表扫描,在数据块非常多(也就是表很大)而返回的记录数很少的时候,这个问题还会被放大.

    校正

    实际上,可能你已经发现,我计算出来的扫描读次数为12,而执行计划中报告的成本是15.一种轻微的简化版本认为表扫描(或者索引快速全扫描)的成本为
    ‘number of blocks’ /
    db_file_multiblock_read_count.

    Oracle使用一种”调整后”的多块读的值来做这种计算(然而,在扫描开始之后,它仍然尽力使用这个真实的请求值来扫描).
    为了方面查询,下表对比了几个真实值与调整后的值:

    Actual Adjusted
    4 3.175
    8 6.589
    16 10.398
    32 16.409
    64 25.895
    128 40.865

    你还将发现,当你为这个参数提供一个不切实际的很大的值后,Oracle可以为你提供保护来避免发生错误.
    顺便提一下,Oracle 9中有一点小小的改变,表扫描的成本还会做进一步的调整(对相除之后的结果加1),这意味着Oracle 9中的表扫描的成本会比Oracle 8中大一点点,从而索引会变得更可能被使用一点点.

    修正

    我们已经看到,优化器有两个内置的假设,而这两个假设又不是很合理.

    • 一次单块读的成本与一次多块读的成本一样-(实际上不大可能,特别是运行在没有顺序的文件系统上的时候)
    • 一次块访问就是一次物理磁盘读-(那么Buffer Cache是干什么吃的?)

    从Oracle 8.1刚发布开始,就有多个参数可供我们以一种相当切合实际的方式来修正这些假设.
    Tim Gorman的文章为这些参数提供了一个切实的描述,下面是简单的描述:

    • Optimizer_index_cost_adj的值范围为1到10000,默认为100.实际上,这个参数描述的是,相对于一次多块读来讲,一次单块读有多么便宜.如果它的值为30,也就是高速Oracle一次单块读的成本是一次多块读的成本的30%. 从而Oracle就会因此这个参数的值很小而更多的倾向于选择使用索引访问.
    • Optimizer_index_caching的值访问为0到100,默认为0.这个参数告诉Oracle,假定索引块将在Buffer Cache中存在百分比.在这个例子中,将这个值设置为接近100的值将助长使用索引而不是表扫描.

    关于这些参数的真正美好的事情是,可以将他们设置成”符合实际”的值.
    将optimizer_index_caching设置成”buffer cache hit ratio”范围内的一个值(你需要自己决定具体是按照default pool,keep pool还是这两个的某种组合来得到这个数值).
    Optimizer_index_cost_adj的值的设置要更加复杂一点.检查v$system_event视图中等待事件”db file scattered read”(多块读取)与”db file sequential read”(单块读取)的有代表性的等待时间.用后者(单块读的等待时间)除以前者(多块读的等待时间)并乘以100.

    改进

    不要忘了,这两个参数可能需要一天(周)的不同时段进行调整以反映终端用户的工作负载.仅仅取得一组数字,就一直使用下去,是不可行的.
    很高兴,在Oracle 9中,情况得到了改善.你可以收集系统统计信息,通常就包含以下四个统计数据:

    • 单块读的平均读取时间
    • 多块读的平均读取时间
    • 实际发生的多块读的平均读取块数
    • CPU的理论可用速度

    要详细介绍这个特性足够配得上一整篇文章,但是这里我特别强调一点,前三个统计值使得Oracle可以明白对多块读的真实成本(相对单块读来讲).实际上,CPU速度使得Oracle可以得出不适宜的访问机制的CPU成本,比如,读取一个数据块中的每条记录以找出特定的数据值,以及与此相似的行为.
    当你将系统升级到Oracle 9时,你首先需要检查的事情就是是否正确使用系统统计信息.单单这个特性就可能大大降低你尝试”优化”的糟糕的SQL的时间.
    顺便提一下,尽管系统统计信息带来了惊人的效果,这两个优化器调整参数仍然有效,虽然使用它们的确切地公式在Oracle 8与Oracle 9之间发生了变化.

    主题的变种

    当然,我选择了一个非常特殊的例子,一个单列非唯一索引上的等值查询,并且表中没有空值,这种情况非常容易处理.(我甚至都没有提及索引的blevel与clustering_factor.)Oracle还有多个不同的方法来处理更加一般的例子.
    考虑如下这些我为了方便而忽视的情况:

    • 多列索引
    • 使用多列索引中的部分列
    • 范围扫描
    • 唯一索引
    • 由非唯一索引代表的唯一约束
    • 索引跳跃扫描
    • 只查询索引的语句
    • 位图索引
    • 空值的影响

    这个列表还可以不停地列下去.并没有一个简单的公式来告诉你Oracle是如何计算它的成本,只存在一个通用准则,通过它你可以了解这个方法的梗概,以及一组可以应用到不同情形下的不同计算公式.
    不管怎样,本文的目的是让你知道有这个通用准则,以及优化器策略中内嵌的两个基本假设.我希望,这篇文章可以帮助你更加深入的理解那些优化器做出来的众所周知的怪事.

    进一步阅读

    • Tim Gorman: www.evdbt.com. “The Search for Intelligent Life in the Cost Based Optimiser.”
    • Wolfgang Breitling: www.centrexcc.com. “Looking under the hood of the CBO.”


    较旧的文章

    阿里巴巴DBA出品