深度分析數(shù)據(jù)庫(kù)的熱點(diǎn)塊問(wèn)題(latch: cache buffers chains)
點(diǎn)擊上方“IT那活兒”公眾號(hào),關(guān)注后了解更多內(nèi)容,不管IT什么活兒,干就完了!?。?/strong>
數(shù)據(jù)庫(kù)的熱點(diǎn)塊,從簡(jiǎn)單了講,就是極短的時(shí)間內(nèi)對(duì)少量數(shù)據(jù)塊進(jìn)行了過(guò)于頻繁的訪(fǎng)問(wèn)。定義看起來(lái)總是很簡(jiǎn)單的,但實(shí)際在數(shù)據(jù)庫(kù)中,我們要去觀察或者確定熱點(diǎn)塊的問(wèn)題,卻不是那么簡(jiǎn)單了。要深刻地理解數(shù)據(jù)庫(kù)是怎么通過(guò)一些數(shù)據(jù)特征來(lái)表示熱點(diǎn)塊的,我們需要了解一些數(shù)據(jù)庫(kù)在這方面處理機(jī)制的特性。
數(shù)據(jù)緩沖區(qū)的結(jié)構(gòu)
我們都知道,當(dāng)查詢(xún)開(kāi)始的時(shí)候,進(jìn)程首先去數(shù)據(jù)緩沖區(qū)中查找是否存在查詢(xún)所需要的數(shù)據(jù)塊,如果沒(méi)有,就去磁盤(pán)上把數(shù)據(jù)塊讀到內(nèi)存中來(lái)。在這個(gè)過(guò)程中,涉及到數(shù)據(jù)緩沖區(qū)中LRU鏈的管理。現(xiàn)在我們的重點(diǎn)是,到底進(jìn)程是如何地去快速定位到自己所想要的block的,或者如何快速確定想要的block不在內(nèi)存中而去進(jìn)行物理讀的。我們仔細(xì)想一想,隨著硬件的發(fā)展,內(nèi)存越來(lái)越大,cache buffer也越來(lái)越大,我們?nèi)绾尾拍茉诖罅康膬?nèi)存中迅速定位到自己想要的block?總不能去所有buffer中遍歷吧!在此數(shù)據(jù)庫(kù)引出了hash的概念(oracle中快速定位信息總是通過(guò)hash算法的,比如快速定位sql是否在shared pool size中存在就是通過(guò)hash value來(lái)定位的,也就是說(shuō)shared pool size中對(duì)象也是通過(guò)hash table來(lái)管理的),了解一點(diǎn)數(shù)據(jù)結(jié)構(gòu)的基本知識(shí)就知道,hash 的一大重要功能就是快速地查找。舉個(gè)最簡(jiǎn)單的例子,假設(shè)我們有一個(gè)hash table 就是一個(gè)二維數(shù)組a[200][100],現(xiàn)在有1000個(gè)無(wú)序數(shù)字,我們要從這1000個(gè)數(shù)字里面查找某個(gè)值是否存在,或者說(shuō)當(dāng)我們接收到某個(gè)數(shù)字的時(shí)候必須判斷是否已經(jīng)存在,當(dāng)然,我們可以遍歷這1000個(gè)數(shù)字,但這樣的效率就很低。但現(xiàn)在我們考慮這樣一種方法,那就是把1000個(gè)數(shù)字除以200,根據(jù)其余數(shù),放在a[200][100]里面(假設(shè)相同余數(shù)的最大數(shù)量不超過(guò)100),余數(shù)就是數(shù)組的下標(biāo)。這樣,平均來(lái)說(shuō)一個(gè)數(shù)組a[i]里面可能有5個(gè)左右的數(shù)字。當(dāng)我們要去判別一個(gè)數(shù)字是否存在的時(shí)候,對(duì)這個(gè)數(shù)字除以200(這就是一個(gè)最簡(jiǎn)單的hash算法),根據(jù)余數(shù)i作為下標(biāo)去數(shù)組a[i]中查找,大約進(jìn)行5次查找就能判別是否已經(jīng)存在,這樣通過(guò)開(kāi)辟內(nèi)存空間a[200][100]來(lái)?yè)Q取了時(shí)間(當(dāng)然hash 算法的選取和hash table的大小是一個(gè)很關(guān)鍵的問(wèn)題)。明白了基本的hash原理之后,我們?cè)賮?lái)看oracle的block的管理。數(shù)據(jù)庫(kù)為這些block也開(kāi)辟了hash table,假設(shè)是a,則在一維上的數(shù)量是由參數(shù)_db_block_hash_buckets 來(lái)決定的,也就是存在hash table a[_db_block_hash_buckets ],從oracle8i開(kāi)始,_db_block_hash_buckets =db_block_buffers*2。而一個(gè)block被放到哪個(gè)buckets里面,則是由block的文件編號(hào)、塊號(hào)(x$bh.dbarfl、x$bh.dbablk對(duì)應(yīng)了block的文件屬于表空間中的相關(guān)編號(hào)和block在文件中的編號(hào),x$bh是所有cache buffer的header信息,通過(guò)表格的形式可以查詢(xún))做hash 算法決定放到哪個(gè)bucket的,而bucket里面就存放了這些buffers的地址。這樣當(dāng)我們要訪(fǎng)問(wèn)數(shù)據(jù)的時(shí)候,可以獲得segment的extent(可以通過(guò)dba_extents查到看,詳細(xì)的信息來(lái)源這里不做探討),自然知道要訪(fǎng)問(wèn)的文件編號(hào)和block編號(hào),根據(jù)文件和block編號(hào)可以通過(guò)hash算法計(jì)算出hash bucket,然后就可以去hash bucket里面去找block對(duì)應(yīng)的buffer。除此之外,為了維護(hù)對(duì)這些block的訪(fǎng)問(wèn)和更改,oracle還提供了一種latch來(lái)保護(hù)這些block。因?yàn)橐苊獠煌倪M(jìn)程隨意地徑直并發(fā)修改和訪(fǎng)問(wèn)這些block,這樣很可能會(huì)破壞block的結(jié)構(gòu)的。latch是數(shù)據(jù)庫(kù)內(nèi)部提供的一種維護(hù)內(nèi)部結(jié)構(gòu)的一種低級(jí)鎖,latch的生存周期極短(微秒以下級(jí)別),進(jìn)程加latch后快速的進(jìn)行某個(gè)訪(fǎng)問(wèn)或者修改動(dòng)作然后釋放latch(關(guān)于latch不再過(guò)多的闡述,那可能又是需要另一篇文章才能闡述清楚)。這種latch數(shù)量是通過(guò)參數(shù)_db_block_hash_latches 來(lái)定義的,一個(gè)latch對(duì)應(yīng)的保護(hù)了多個(gè)buckets。從8i開(kāi)始,這個(gè)參數(shù)的default規(guī)則為:通過(guò)這個(gè)規(guī)則我們可以看出,一個(gè)latch大約可以維護(hù)128個(gè)左右的buffers。由于latch使得對(duì)block的操作的串行化(9i中有改進(jìn),讀與讀可以并行,但讀與寫(xiě)、寫(xiě)與寫(xiě)依然要串行),很顯然我們可以想到一個(gè)道理,如果大量進(jìn)程對(duì)相同的block進(jìn)程進(jìn)行操作,必然在這些latch上造成競(jìng)爭(zhēng),也就是說(shuō)必然形成latch的等待。這在宏觀上就表現(xiàn)為系統(tǒng)級(jí)的等待。明白了這些原理,為我們下面的在數(shù)據(jù)庫(kù)中的診斷奠定了基礎(chǔ)。
如果我們經(jīng)常關(guān)注awr報(bào)告,會(huì)發(fā)現(xiàn)有時(shí)候出現(xiàn)cache buffer chains的等待。這個(gè)cache buffer chains就是_db_block_hash_latches所定義的latch的總稱(chēng),通過(guò)查詢(xún)v$latch也可得到:select">sys@OCN>select latch#,name,gets,misses,sleeps from v$latch where name like cache buffer%;
LATCH# NAME GETS MISSES SLEEPS
---------- ------------------------------ ---------- ---------- ----------
93 cache buffers lru chain 54360446 21025 238
98 cache buffers chains 6760354603 1680007 27085
99 cache buffer handles 554532 6 0
在這個(gè)查詢(xún)結(jié)果里我們可以看到記錄了數(shù)據(jù)庫(kù)啟動(dòng)以來(lái)的所有cahce buffer chains的latch的狀況,gets表示總共有這么多次請(qǐng)求,misses表示請(qǐng)求失敗的次數(shù)(加鎖不成功),而sleeps 表示請(qǐng)求失敗休眠的次數(shù),通過(guò)sleeps我們可以大體知道數(shù)據(jù)庫(kù)中l(wèi)atch的競(jìng)爭(zhēng)是否嚴(yán)重,這也間接的表征了熱點(diǎn)塊的問(wèn)題是否嚴(yán)重。由于v$latch是一個(gè)聚合信息,我們并不能獲得哪些塊可能存在頻繁訪(fǎng)問(wèn)。那我們要來(lái)看另一個(gè)view信息,那就是v$latch_children,v$latch_children.addr記錄的就是這個(gè)latch的地址。select">sys@OCN>select addr,LATCH#,CHILD#,gets,misses,sleeps from v$latch_children
2 where name = cache buffers chains and rownum < 21;
ADDR LATCH# CHILD# GETS MISSES SLEEPS
-------- ---------- ---------- ---------- ---------- ----------
91B23B74 98 1024 10365583 3957 33
91B23374 98 1023 5458174 964 25
91B22B74 98 1022 4855668 868 15
91B22374 98 1021 5767706 923 22
91B21B74 98 1020 5607116 934 31
91B21374 98 1019 9389325 1111 25
91B20B74 98 1018 5060207 994 31
91B20374 98 1017 18204581 1145 18
91B1FB74 98 1016 7157081 920 23
到此我們可以根據(jù)v$latch_child.addr關(guān)聯(lián)到對(duì)應(yīng)的x$bh.hladdr(這是buffer header中記錄的當(dāng)前buffer所處的latch地址),通過(guò)x$bh可以獲得塊的文件編號(hào)和block編號(hào)。select">sys@OCN>select dbarfil,dbablk
from x$bh
where hladdr in
(select addr
from (select addr
from v$latch_children
order by sleeps desc)
where rownum < 11);
DBARFIL DBABLK
---------- ----------
4 6498
40 14915
15 65564
28 34909
由此我們就打通了cache buffers chains和具體block之間的關(guān)系,那再繼續(xù)下來(lái),知道了block,我們需要知道究竟是哪些segment。這個(gè)可以通過(guò)dba_extents來(lái)獲得。select distinct a.owner,a.segment_name from
dba_extents a,
(select dbarfil,dbablk
from x$bh
where hladdr in
(select addr
from (select addr
from v$latch_children
order by sleeps desc)
where rownum < 11)) b
where a.RELATIVE_FNO = b.dbarfil
and a.BLOCK_ID <= b.dbablk and a.block_id + a.blocks > b.dbablk;
OWNER SEGMENT_NAME SEGMENT_TYPE
------------------------------ ------------------------------ ------------------
ALIBABA BIZ_SEARCHER TABLE
ALIBABA CMNTY_USER_MESSAGE TABLE
ALIBABA CMNTY_VISITOR_INFO_PK INDEX
select object_name
from dba_objects
where data_object_id in
(select obj
from x$bh
where hladdr in
(select addr
from (select addr
from v$latch_children
order by sleeps desc)
where rownum < 11)) ;
OBJECT_NAME
------------------------------------
I_CCOL2
RESOURCE_PLAN$
DUAL
FGA_LOG$
AV_TRANSACTION
到這里我們基本能找到熱點(diǎn)塊對(duì)對(duì)應(yīng)的對(duì)象。但實(shí)際上還有另外一個(gè)途徑來(lái)獲取這些信息,那就是和x$bh.tch 相關(guān)的一種方法。對(duì)于8i開(kāi)始o(jì)racle提供了接觸點(diǎn)(touch count)來(lái)作為block是冷熱的標(biāo)志,在一定條件滿(mǎn)足的情況下block被進(jìn)程訪(fǎng)問(wèn)一次touch count 增加一,到某個(gè)標(biāo)準(zhǔn)之后被移動(dòng)到LRU熱端(關(guān)于touch count 在這里不做詳細(xì)介紹,那又將是一大篇文章)。那在短時(shí)間內(nèi)從某種意義上講,touch count 大的block可能暗示著在當(dāng)前某個(gè)周期內(nèi)被訪(fǎng)問(wèn)次數(shù)比較多。select distinct a.owner,a.segment_name,a.segment_type from
dba_extents a,
(select dbarfil,dbablk
from (select dbarfil,dbablk
from x$bh order by tch desc) where rownum < 11) b
where a.RELATIVE_FNO = b.dbarfil
and a.BLOCK_ID <= b.dbablk and a.block_id + a.blocks > b.dbablk;
OWNER SEGMENT_NAME SEGMENT_TYPE
------------------------------ ------------------------------ ------------------
ALIBABA CMNTY_USER_MESSAGE TABLE
ALIBABA MEMBER_PK INDEX
ALIBABA OFFER_DRAFT_GMDFY_IND INDEX
select object_name
from dba_objects
where data_object_id in
(select obj
from (select obj
from x$bh order by tch desc) where rownum < 11) ;
OBJECT_NAME
---------------------------------------------------
DUAL
MEMBER_PK
SAMPLE_GROUP_PK
CMNTY_USER_MESSAGE_TD_BSM_IND
OFFER_DRAFT_MID_GMDFY_IND
OFFER_MID_GPOST_IND
到這里,我們尋找熱點(diǎn)塊和熱點(diǎn)對(duì)象的工作算是完成了,但我們還并沒(méi)有解決問(wèn)題。
熱點(diǎn)塊和熱點(diǎn)對(duì)象我們都找到了,但是我們?cè)撛趺磥?lái)解決這個(gè)問(wèn)題呢?一般來(lái)說(shuō),熱點(diǎn)塊會(huì)導(dǎo)致cache buffers chains競(jìng)爭(zhēng)等待,但并不是說(shuō)cache buffer chains一定是因?yàn)闊狳c(diǎn)塊而起,在特別情況下有可能是因?yàn)閘atch數(shù)量的問(wèn)題導(dǎo)致的,也就是一個(gè)latch管理的buffers數(shù)量太多而導(dǎo)致競(jìng)爭(zhēng)激烈。但是latch數(shù)量我們一般是不會(huì)輕易去設(shè)置的,這是oracle的隱藏參數(shù)。實(shí)際上最有效的辦法,是從優(yōu)化sql入手,不良的sql往往帶來(lái)大量的不必要的訪(fǎng)問(wèn),這是造成熱點(diǎn)塊的根源。比如本該通過(guò)全表掃描的查詢(xún)卻走了索引的range scan,這樣將帶來(lái)大量的對(duì)塊的重復(fù)訪(fǎng)問(wèn)。從而形成熱點(diǎn)問(wèn)題。再或者比如不當(dāng)?shù)刈吡薾ested loops的表連接,也可能對(duì)非驅(qū)動(dòng)表造成大量的重復(fù)訪(fǎng)問(wèn)。那么在這個(gè)時(shí)候,我們的目標(biāo)就是找出這些sql來(lái)并嘗試優(yōu)化。在awr報(bào)告中,根據(jù)報(bào)告中sql列表,我們?nèi)绻峭ㄟ^(guò)dba_extents確定的熱點(diǎn)對(duì)象而不是通過(guò)dba_objects確定的,則可以通過(guò)查找出的熱點(diǎn)segment轉(zhuǎn)換為對(duì)應(yīng)的表,對(duì)于非分區(qū)的索引,index_name就是segment_name,通過(guò)dba_indexes很容易的找到對(duì)應(yīng)的table_name,對(duì)于分區(qū)表和分區(qū)索引也能通過(guò)和dba_tab_partition和dba_ind_partitions找到segment和table的對(duì)應(yīng)關(guān)系。通過(guò)這些table到awr報(bào)告中去找相關(guān)的sql。select sql_text
from stats$sqltext a,
(select distinct a.owner,a.segment_name,a.segment_type from
dba_extents a,
(select dbarfil,dbablk
from (select dbarfil,dbablk
from x$bh order by tch desc) where rownum < 11) b
where a.RELATIVE_FNO = b.dbarfil
and a.BLOCK_ID <= b.dbablk and a.block_id + a.blocks > b.dbablk) b
where a.sql_text like %||b.segment_name||% and b.segment_type = TABLE
order by a.hash_value,a.address,a.piece;
SQL_TEXT
----------------------------------------------------------------
SELECT SEQ_SMS_TRANSACTION.nextval FROM DUAL
SELECT SEQ_BIZ_EXPRESS.nextval FROM DUAL
SELECT SEQ_SAMPLE.nextval FROM DUAL
SELECT bizgroup.seq_grp_user.NextVal FROM DUAL當(dāng)然這里是從awr搜集的stats$sqltext中去找的(你可以在awr的文本報(bào)告中去找),實(shí)際上,我們可以直接在當(dāng)前數(shù)據(jù)庫(kù)中的v$sqlarea或者v$sqltext里面去找到這些sql,然后來(lái)嘗試優(yōu)化。select sql_text
from v$sqltext a,
(select distinct a.owner,a.segment_name,a.segment_type from
dba_extents a,
(select dbarfil,dbablk
from (select dbarfil,dbablk
from x$bh order by tch desc) where rownum < 11) b
where a.RELATIVE_FNO = b.dbarfil
and a.BLOCK_ID <= b.dbablk and a.block_id + a.blocks > b.dbablk) b
where a.sql_text like %||b.segment_name||% and b.segment_type = TABLE
order by a.hash_value,a.address,a.piece;
SQL_TEXT
----------------------------------------------------------------
SELECT NULL FROM DUAL FOR UPDATE NOWAIT
SELECT SEQ_SMS_TRANSACTION.nextval FROM DUAL
SELECT SEQ_BIZ_EXPRESS.nextval FROM DUAL
SELECT SEQ_IM_GROUP.nextval FROM DUAL
SELECT SEQ_SAMPLE.nextval FROM DUAL
=DD-MON-RR HH.MI.SSXFF AM TZR NLS_DUAL_CURRENCY=$ NLS_COMP=
SELECT SEQ_BIZ_SEARCHER.nextval FROM DUAL除了優(yōu)化sql外,當(dāng)然對(duì)于熱點(diǎn)的表或者索引來(lái)說(shuō),如果小的話(huà),我們可以考慮cache在內(nèi)存中,這樣可能降低物理讀提高sql運(yùn)行速度(這并不會(huì)減少cache buffer chains的訪(fǎng)問(wèn)次數(shù)),對(duì)于序列,我們可以對(duì)序列多設(shè)置一些cache。如果是并行服務(wù)器環(huán)境中的索引對(duì)象,并且這個(gè)索引是系列遞增類(lèi)型,我們可以考慮反向索引(關(guān)于反向索引這里就不過(guò)多地做介紹了)。在數(shù)據(jù)庫(kù)中還可能存在一些其他方面的熱點(diǎn)塊癥狀,通過(guò)v$waitstat的等待可以看出一些端倪,v$waitstat是根據(jù)數(shù)據(jù)緩沖區(qū)中各種block的類(lèi)型(x$bh.class)而分類(lèi)統(tǒng)計(jì)的等待狀況。select">sys@OCN>select * from v$waitstat;
CLASS COUNT TIME
------------------ ---------- ----------
data block 1726977 452542
sort block 0 0
save undo block 0 0比如在ASSM表空間出現(xiàn)之前,由于freelist的存在,如果表經(jīng)常被并發(fā)的進(jìn)程DML,則可能存在大量的data block的等待,或者有free list的等待。那么這個(gè)時(shí)候我們發(fā)現(xiàn)這樣的segment之后需要考慮增加freelist數(shù)量。再比如經(jīng)常發(fā)生長(zhǎng)時(shí)間的DML的表被頻繁地訪(fǎng)問(wèn),這樣將會(huì)造成過(guò)多的對(duì)回滾段中塊的訪(fǎng)問(wèn),這時(shí)可能undo block 的等待會(huì)比較多。那么我們可能需要控制DML的時(shí)間長(zhǎng)度或者想辦法從應(yīng)用程序入手來(lái)解決問(wèn)題。如果是undo header的等待比較多,沒(méi)使用undo tablespace 之前,可能需要考慮增加回滾段的數(shù)量。本文從熱點(diǎn)塊的原理入手,詳細(xì)地由oracle的內(nèi)部結(jié)構(gòu)特征開(kāi)始介紹了熱點(diǎn)塊的產(chǎn)生和表現(xiàn)特征。進(jìn)而闡述了診斷熱點(diǎn)對(duì)象和找出造成熱點(diǎn)對(duì)象的sql的方法。并從解決熱點(diǎn)問(wèn)題方面提供了解決方向。知識(shí)點(diǎn):
- 在cache buffer中搜索block的機(jī)制,使用hash技術(shù)來(lái)搜索,數(shù)據(jù)庫(kù)為buffer中的block也開(kāi)辟了hash table,hash table a[_db_block_hash_buckets ],_db_block_hash_buckets =db_block_buffers*2。而一個(gè)block被放到哪個(gè)buckets里面,則是由block的文件編號(hào)、塊號(hào)(x$bh.dbarfl、x$bh.dbablk對(duì)應(yīng)了block的文件屬于表空間中的相關(guān)編號(hào)和block在文件中的編號(hào),x$bh是所有cache buffer的header信息,通過(guò)表格的形式可以查詢(xún))做hash 算法決定放到哪個(gè)bucket的,而bucket里面就存放了這些buffers的地址。
- Latch競(jìng)爭(zhēng)的原因:如果大量進(jìn)程對(duì)相同的block進(jìn)程進(jìn)行操作,必然在這些latch上造成競(jìng)爭(zhēng),也就是說(shuō)必然形成latch的等待。
- 如何確定熱點(diǎn)對(duì)象:v$latch_children.addr記錄的就是latch的地址。根據(jù)v$latch_child.addr關(guān)聯(lián)到對(duì)應(yīng)的x$bh.hladdr(這是buffer header中記錄的當(dāng)前buffer所處的latch地址),通過(guò)x$bh可以獲得塊的文件編號(hào)和block編號(hào)。
- 熱點(diǎn)問(wèn)題的解決:一般來(lái)說(shuō),熱點(diǎn)塊會(huì)導(dǎo)致cache buffers chains競(jìng)爭(zhēng)等待,但并不是說(shuō)cache buffer chains一定是因?yàn)闊狳c(diǎn)塊而起,在特別情況下有可能是因?yàn)閘atch數(shù)量的問(wèn)題導(dǎo)致的,也就是一個(gè)latch管理的buffers數(shù)量太多而導(dǎo)致競(jìng)爭(zhēng)激烈。但是latch數(shù)量我們一般是不會(huì)輕易去設(shè)置的,這是oracle的隱藏參數(shù)。實(shí)際上最有效的辦法,是從優(yōu)化sql入手,不良的sql往往帶來(lái)大量的不必要的訪(fǎng)問(wèn),這是造成熱點(diǎn)塊的根源。比如本該通過(guò)全表掃描的查詢(xún)卻走了索引的range scan,這樣將帶來(lái)大量的對(duì)塊的重復(fù)訪(fǎng)問(wèn)。從而形成熱點(diǎn)問(wèn)題。再或者比如不當(dāng)?shù)刈吡薾ested loops的表連接,也可能對(duì)非驅(qū)動(dòng)表造成大量的重復(fù)訪(fǎng)問(wèn)。那么在這個(gè)時(shí)候,我們的目標(biāo)就是找出這些sql來(lái)并嘗試優(yōu)化。對(duì)于的小表可以CACHE到內(nèi)存里。
本文作者:湯 杰(上海新炬王翦團(tuán)隊(duì))
本文來(lái)源:“IT那活兒”公眾號(hào)
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/129171.html