Oracle高水位線(HWM)及效能優化
說到HWM
,我們首先要簡要的談談ORACLE的邏輯儲存管理.我們知道,ORACLE在邏輯儲存上分4個粒度:表空間,段,區和塊.
(1)塊
:是粒度最小的儲存單位,現在標準的塊大小是8K
,ORACLE每一次I/O
操作也是按塊來操作的,也就是說當ORACLE從資料檔案讀資料時,是讀取多少個塊,而不是多少行.
(2)區
:由一系列相鄰的塊而組成,這也是ORACLE空間分配的基本單位,舉個例子來說,當我們建立一個表PM_USER
時,首先ORACLE會分配一區的空間給這個表,隨著不斷的INSERT
資料到PM_USER
,原來的這個區容不下插入的資料時,ORACLE是以區為單位進行擴充套件的,也就是說再分配多少個區給PM_USER
,而不是多少個塊.
(3)段
:是由一系列的區所組成,一般來說,當建立一個物件時(表,索引),就會分配一個段給這個物件.所以從某種意義上來說,段就是某種特定的資料.如CREATE TABLE PM_USER
,這個段就是資料段,而CREATE INDEX ON PM_USER(NAME)
,ORACLE同樣會分配一個段給這個索引,但這是一個索引段了.查詢段的資訊可以通過資料字典: SELECT * FROM USER_SEGMENTS
來獲得,
(4)表空間
:包含段,區及塊.表空間的資料物理上儲存在其所在的資料檔案中.一個資料庫至少要有一個表空間.
OK,我們現在回到HWM
上來,那麼,什麼是高水位標記呢?這就跟ORACLE的段空間管理相關了.
(一)ORACLE用HWM
來界定一個段中使用的塊和未使用的塊.
舉個例子來說,當我們建立一個表:PT_SCHE_DETAIL
時,ORACLE就會為這個物件分配一個段.在這個段中,即使我們未插入任何記錄,也至少有一個區被分配,第一個區的第一個塊就稱為段頭(SEGMENT HEAD
),段頭中就儲存了一些資訊,基中HWM的資訊就儲存在此.此時,因為第一個區的第一塊用於儲存段頭的一些資訊,雖然沒有儲存任何實際的記錄,但也算是被使用,此時HWM是位於第2個塊.當我們不斷插入資料到PM_USER後,第1個塊已經放不下後面新插入的資料,此時,ORACLE將高水位之上的塊用於儲存新增資料,同時,HWM本身也向上移.也就是說,當我們不斷插入資料時,HWM會往不斷上移,這樣,在HWM之下的,就表示使用過的塊,HWM之上的就表示已分配但從未使用過的塊.
(二)HWM在插入資料時,當現有空間不足而進行空間的擴充套件時會向上移,但刪除資料時不會往下移.
這就好比是水庫的水位,當漲水時,水位往上移,當水退出後,最高水位的痕跡還是清淅可見.
考慮讓我們看一個段,如一張表,其中填滿了塊,如圖 1 所示。在正常操作過程中,刪除了一些行,如圖 2 所示。現有就有了許多浪費的空間:(I) 在表的上一個末端和現有的塊之間,以及 (II) 在塊內部,其中還有一些沒有刪除的行。
圖1:分配給該表的塊。用灰色正方形表示行
ORACLE 不會釋放空間以供其他物件使用,有一條簡單的理由:由於空間是為新插入的行保留的,並且要適應現有行的增長。被佔用的最高空間稱為最高使用標記 (HWM),如圖 2 所示。
圖2:行後面的塊已經刪除了;HWM 仍保持不變
(三)HWM的資訊儲存在段頭當中.
HWM
本身的資訊是儲存在段頭.在段空間是手工管理方式時,ORACLE是通過FREELIST
(一個單向連結串列)來管理段內的空間分配.在段空間是自動管理方式時(ASSM),ORACLE是通過BITMAP
來管理段內的空間分配.
(四)ORACLE的全表掃描是讀取高水位標記(HWM
)以下的所有塊.
所以問題就產生了.當使用者發出一個全表掃描時,ORACLE 始終必須從段一直掃描到 HWM
,即使它什麼也沒有發現。該任務延長了全表掃描的時間。
(五)當用直接路徑插入行時 — 例如,通過直接載入插入(用 APPEND
提示插入)或通過 SQL*LOADER
直接路徑 — 資料塊直接置於 HWM
之上。它下面的空間就浪費掉了。
我們來分析這兩個問題,後者只是帶來空間的浪費,但前者不僅是空間的浪費,而且會帶來嚴重的效能問題.我們來看看下面的例子:
(A)我們先來搭建測試的環境,第一步先建立一個段空間為手工管理的表空間:
CREATE TABLESPACE "RAINNY"
LOGGING
DATAFILE 'D:ORACLE_HOMEORADATARAINNYRAINNY.ORA' SIZE 5M
AUTOEXTEND
ON NEXT 10M MAXSIZE UNLIMITED EXTENT MANAGEMENT LOCAL
SEGMENT SPACE MANAGEMENT MANUAL;
(B)建立一個表,注意,此表的第二個欄位我故意設成是CHAR(100)
,以讓此表在插入1千萬條記錄後,空間有足夠大:
CREATE TABLE TEST_TAB(C1 NUMBER(10),C2 CHAR(100)) TABLESPACE RAINNY;
插入記錄
DECLARE
I NUMBER(10);
BEGIN
FOR I IN 1..10000000 LOOP
INSERT INTO TEST_TAB VALUES(I,'TESTSTRING');
END LOOP;
COMMIT;
END;
/
©我們來查詢一下,看在插入一千萬條記錄後所訪問的塊數和查詢所用時間:
SQL> SET TIMING ON
SQL> SET AUTOTRACE TRACEONLY
SQL> SELECT COUNT(*) FROM TEST_TAB;
ELAPSED: 00:01:03.05
EXECUTION PLAN
------------------------------------------------------------
0 SELECT STATEMENT OPTIMIZER=CHOOSE (COST=15056 CARD=1)
1 0 SORT (AGGREGATE)
2 1 TABLE ACCESS (FULL) OF 'TEST_TAB' (COST=15056 CARD=10000
000)
STATISTICS
----------------------------------------------------------
0 RECURSIVE CALLS
0 DB BLOCK GETS
156310 CONSISTENT GETS
154239 PHYSICAL READS
0 REDO SIZE
379 BYTES SENT VIA SQL*NET TO CLIENT
503 BYTES RECEIVED VIA SQL*NET FROM CLIENT
2 SQL*NET ROUNDTRIPS TO/FROM CLIENT
0 SORTS (MEMORY)
0 SORTS (DISK)
1 ROWS PROCESSED
我們來看上面的執行計劃,這句SQL總供耗時是:1分3秒.訪問方式是採用全表掃描方式(FTS),邏輯讀了156310個BLOCK,物理讀了154239個BLOCK.
我們來分析一下這個表:
BEGIN
DBMS_STATS.GATHER_TABLE_STATS(OWNNAME=> 'TEST',
TABNAME=> 'TEST_TAB',
PARTNAME=> NULL);END;
/
發現這個表目前使用的BLOCK
有: 156532,未使用的BLOCK
(EMPTY_BLOCKS
)為:0,總行數為(NUM_ROWS
):1000 0000
(D)接下來我們把此表的記錄用DELETE
方式刪掉,然後再來看看SELECT COUNT(*) FROM TEST_TAB
所花的時間:
DELETE FROM TEST_TAB;
COMMIT;
SQL> SELECT COUNT(*) FROM TEST_TAB;
ELAPSED: 00:01:04.03
EXECUTION PLAN
----------------------------------------------------------
0 SELECT STATEMENT OPTIMIZER=CHOOSE (COST=15056 CARD=1)
1 0 SORT (AGGREGATE)
2 1 TABLE ACCESS (FULL) OF 'TEST_TAB' (COST=15056 CARD=1)
STATISTICS
----------------------------------------------------------
0 RECURSIVE CALLS
0 DB BLOCK GETS
156310 CONSISTENT GETS
155565 PHYSICAL READS
0 REDO SIZE
378 BYTES SENT VIA SQL*NET TO CLIENT
503 BYTES RECEIVED VIA SQL*NET FROM CLIENT
2 SQL*NET ROUNDTRIPS TO/FROM CLIENT
0 SORTS (MEMORY)
0 SORTS (DISK)
1 ROWS PROCESSED
大家來看,在DELETE
表後,此時表中已沒有一條記錄,為什麼SELECT COUNT(*) FROM TEST_TAB
花的時間為1分4秒, 反而比有記錄稍微長點,這是為什麼呢?而且大家看,其邏輯讀了156310個 BLOCK,跟之前有一千萬行記錄時差不多,ORACLE怎麼會這麼笨啊?
我們在DELETE
表後再次分析表,看看有什麼變化:
這時, TEST_TAB
表目前使用的BLOCK
是: 156532,未使用的BLOCK
(EMPTY_BLOCKS
)為:0,總行數為(NUM_ROWS
)已變成:0
為什麼表目前使的BLOCK
數還是156532呢?
問題的根源就在於ORACLE的HWM
.也就是說,在新增記錄時,HWM
會慢慢往上移,但是在刪除記錄後,HWM
卻不會往下移,也就是說,DELETE一千萬條記錄後,此表的HWM根本沒移動,還在原來的那個位置,所以,HWM以下的塊數同樣也是一樣的.ORACLE的全表掃描是讀取ORACLE高水位標記下的所有BLOCK,也就是說,不管HWM下的BLOCK
現在實際有沒有存放資料,ORACLE都會一一讀取,這樣,大家可想而知,在我們DELETE
表後,ORACLE讀了大量的空塊,耗去了大量的時間.
我們再來看DELETE
表後段空間實際使用的狀況:
SQL> EXEC SHOW_SPACE('TEST_TAB','TEST');
TOTAL BLOCKS............................164352 --總共164352塊
TOTAL BYTES.............................1346371584
UNUSED BLOCKS...........................7168 --有7168塊沒有用過,也就是在HWM上面的塊數
UNUSED BYTES............................58720256
LAST USED EXT FILEID....................9
LAST USED EXT BLOCKID...................158856-- BLOCK ID 是針對資料檔案來編號的,表示最後使用的一個EXTENT的第一個BLOCK的編號
LAST USED BLOCK.........................1024 --在最後使用的一個EXTENT 中一共用了1024塊
PL/SQL PROCEDURE SUCCESSFULLY COMPLETED
總共用了164352塊,除了一個SEGMENT HEADER
,實際總共用了164351個塊,有7168塊從來沒有使用過。LAST USED BLOCK
表示在最後一個使用的EXTENT
中使用的BLOCK
, 結合LAST USED EXT BLOCK ID
可以計算HWM
位置 :
LAST USED EXT BLOCK ID + LAST USED BLOCK -1 = HWM
所在的資料檔案的BLOCK編號
代入得出: 158856+1024-1=159879,這個就是HWM所有的BLOCK
編號
HWM所在的塊:TOTAL BLOCKS- UNUSED BLOCKS=164352-7168=157184
,也就是說,HWM在第157184個塊,其BLOCKID
是159879
(E)結下來,我們再做幾個試驗:
第一步:執行ALTER TABLE TEST_TAB DEALLOCATE UNUSED;
我們看看段空間的使用狀況:
SQL> EXEC SHOW_SPACE('TEST_TAB','TEST');
TOTAL BLOCKS............................157184
TOTAL BYTES.............................1287651328
UNUSED BLOCKS...........................0
UNUSED BYTES............................0
LAST USED EXT FILEID....................9
LAST USED EXT BLOCKID...................158856
LAST USED BLOCK.........................1024
此時我們再代入上面的公式,算出HWM
的位置: 157184-0=157184
HWM所在的BLOCK ID
是158856+1024-1=159879
,跟剛剛的沒有變化,也就是說執行ALTER TABLE TEST_TAB DEALLOCATE UNUSED
後,段的高水位標記的位置沒有改變,但是大家看看UNUSED BLOCKS
變為0了,總的塊數減少到157184,這證明,DEALLOCATE UNUSED
為釋放HWM
上面的未使用空間,但是並不會釋放HWM
下面的自由空間,也不會移動HWM
的位置.
第二步:我們再來看看執行ALTER TABLE TEST_TAB MOVE
後段空間的使用狀況:
SQL> EXEC SHOW_SPACE('TEST_TAB','TEST');
TOTAL BLOCKS............................8
TOTAL BYTES.............................65536
UNUSED BLOCKS...........................5
UNUSED BYTES............................40960
LAST USED EXT FILEID....................9
LAST USED EXT BLOCKID...................2632
LAST USED BLOCK.........................3
此時,總共用到的塊數已變為8, 我們再代入上面的公式,算出HWM的位置: 8-5=3 HWM
所在的BLOCK ID
是2632+3-1=2634,
OK,我們發現,此時HWM
的位置已經發生變化,現在HWM
的位置是在第3個BLOCK
,其BLOCK ID
是2634,所有資料檔案的ID是9(這個沒有發生變化,資料檔案還是原來的那個資料檔案,只是釋放了原來的自由空間),最後使用的塊數也變為3,也就是說已經使用了3塊,HWM
就是在最後一個使用的塊上,即第3個塊上.大家可能會覺得奇怪,為什麼釋放空間後,未使用的塊還有5個啊?也就是說HWM
之上還是有5個已分配但從未使用的塊.答案就跟HWM
移動的規律有關.當我們在插入資料時,ORACLE首先在HWM
之下的塊當中定位自由空間(通過自由列表FREELIST
),如果FREELIST
當中沒有自由塊了,ORACLE就開始往上擴充套件,而HWM也跟著往上移,每5塊移動一次.我們來看ORACLE的說明:
The high water mark is:
-Recorded in the segment header block
-Set to the beginning of the segment on the creation
-Incremented in five-block increments as rows are inserted
-Reset by the truncate command
-Never reset by the delete command
-Space above the high-water-mark can be reclaimed at the table level by using the following command:
ALTER TABLE DEALLOCATE UNUSED…
我們再來看看:SELECT COUNT(*) FROM TEST_TAB
所花的時間:
SQL> SELECT COUNT(*) FROM TEST_TAB;
ELAPSED: 00:00:00.00
EXECUTION PLAN
----------------------------------------------------------
0 SELECT STATEMENT OPTIMIZER=CHOOSE
1 0 SORT (AGGREGATE)
2 1 TABLE ACCESS (FULL) OF 'TEST_TAB'
STATISTICS
----------------------------------------------------------
0 RECURSIVE CALLS
0 DB BLOCK GETS
3 CONSISTENT GETS
0 PHYSICAL READS
0 REDO SIZE
378 BYTES SENT VIA SQL*NET TO CLIENT
503 BYTES RECEIVED VIA SQL*NET FROM CLIENT
2 SQL*NET ROUNDTRIPS TO/FROM CLIENT
0 SORTS (MEMORY)
0 SORTS (DISK)
1 ROWS PROCESSED
很快,不到1秒.
我們最後再來對錶作一次分析, 此時這個表目前使用的BLOCK
為: 0,未使用的BLOCK
(EMPTY_BLOCKS)為:0,總行數為(NUM_ROWS
):0
從中我們也可以發現,分析表和SHOW_SPACE
顯示的資料有點不一致.那麼哪個是準的呢?其實這兩個都是準的,只不過計算的方法有點不同.事實上,當你建立了一個物件如表以後,不管你有沒有插入資料,它都會佔用一些塊,ORACLE也會給它分配必要的空間.同樣,用ALTER TABLE MOVE
釋放自由空間後,還是保留了一些空間給這個表.
最後,我們再來執行TRUNCATE
命令,截斷這個表,看看段空間的使用狀況:
TRUNCATE TABLE TEST_TAB;
SQL> EXEC SHOW_SPACE('TEST_TAB','TEST');
TOTAL BLOCKS............................8
TOTAL BYTES.............................65536
UNUSED BLOCKS...........................5
UNUSED BYTES............................40960
LAST USED EXT FILEID....................9
LAST USED EXT BLOCKID...................2632
LAST USED BLOCK.........................3
PL/SQL PROCEDURE SUCCESSFULLY COMPLETED
SQL>
我們發現TRUNCATE
後和MOVE
沒有什麼變化.
為了,最終驗證一下我上面的觀點,我再DROP
一下表,然後新建這個表,看看這時在沒有插入任何資料之前,是否ORACLE
確實有給這個物件分配必要的空間:
DROP TABLE TEST_TAB;
CREATE TABLE TEST_TAB(C1 NUMBER(10),C2 CHAR(100)) TABLESPACE RAINNY;
SQL> EXEC SHOW_SPACE('TEST_TAB','TEST');
TOTAL BLOCKS............................8
TOTAL BYTES.............................65536
UNUSED BLOCKS...........................5
UNUSED BYTES............................40960
LAST USED EXT FILEID....................9
LAST USED EXT BLOCKID...................2112
LAST USED BLOCK.........................3
大家看,即使我沒有插入任何一行記錄,ORACLE還是給它分配了8個塊.當然這個跟建表語句的INITIAL
引數及MINEXTENTS
引數有關:請看TEST_TAB
的儲存引數:
S TORAGE
(
INITIAL 64K
MINEXTENTS 1
MAXEXTENTS UNLIMITED
);
也就是說,在這個物件建立以後,ORACLE至少給它分配一個區,初始大小是64K
,一個標準塊的大小是8K
,剛好是8個BLOCK
.
總結:
在9I中:
(1)如果MINEXTENT
可以使ALTER TABLE TABLENAME DEALLOCATE UNUSED
將HWM
以上所有沒使用的空間釋放
(2)如果MINEXTENT >HWM
則釋放MINEXTENTS
以上的空間。如果要釋放HWM
以上的空間則使用KEEP 0
。
ALTER TABLE TABLESNAME DEALLOCATE UNUSED KEEP 0;
(3) TRUNCATE TABLE DROP STORAGE
(預設值)命令可以將MINEXTENT
之上的空間完全釋放(交還給作業系統),並且重置HWM
。
(4)如果僅是要移動HWM
,而不想讓表長時間鎖住,可以用TRUNCATE TABLE REUSE STORAGE
,僅將HWM重置。
(5)ALTER TABLE MOVE
會將HWM移動,但在MOVE
時需要雙倍的表空間,而且如果表上有索引的話,需要重構索引
(6)DELETE
表不會重置HWM
,也不會釋放自由的空間(也就是說DELETE
空出來的空間只能給物件本身將來的INSERT/UPDATE
使用,不能給其它的物件使用)
在ORACLE 10G
:
可以使用ALTER TABLE TEST_TAB SHRINK SPACE
命令來聯機移動HWM
,
如果要同時壓縮表的索引,可以釋出:ALTER TABLE TEST_TAB SHRINK SPACE CASCADE
注意:在使用此命令時需要先使行可遷移row movement
(具體見例子)。
與使用ALTER TABLE MOVE
不同的是執行此命令後並不需要重構索引。
Oracle 官方說明
Shrinking Database Segments Online
You use online segment shrink to reclaim fragmented free space below the high water mark in an Oracle Database segment. The benefits of segment shrink are these:
* Compaction of data leads to better cache utilization, which in turn leads to better online transaction processing (OLTP) performance.
* The compacted data requires fewer blocks to be scanned in full table scans, which in turns leads to better decision support system (DSS) performance.
Segment shrink is an online, in-place operation. DML operations and queries can be issued during the data movement phase of segment shrink. Concurrent DML operation are blocked for a short time at the end of the shrink operation, when the space is deallocated. Indexes are maintained during the shrink operation and remain usable after the operation is complete. Segment shrink does not require extra disk space to be allocated.
Segment shrink reclaims unused space both above and below the high water mark. In contrast, space deallocation reclaims unused space only above the high water mark. In shrink operations, by default, the database compacts the segment, adjusts the high water mark, and releases the reclaimed space.
Segment shrink requires that rows be moved to new locations. Therefore, you must first enable row movement in the object you want to shrink and disable any rowid-based triggers defined on the object.
Shrink operations can be performed only on segments in locally managed tablespaces with automatic segment space management (ASSM). Within an ASSM tablespace, all segment types are eligible for online segment shrink except these:
* IOT mapping tables
* Tables with rowid based materialized views
* Tables with function-based indexes
操作的過程:
SQL> create table demo as select * from dba_source;
Table created.
Elapsed: 00:00:05.83
SQL> select count(*) from demo;
COUNT(*)
----------
210992
Elapsed: 00:00:01.06
SQL> insert into demo select * from demo;
210992 rows created.
Elapsed: 00:00:59.83
SQL> commit;
Commit complete.
//得到一個40萬條記錄的表,下面來檢視這個表空間分佈情況。
SQL> exec show_space('demo','auto');
PL/SQL procedure successfully completed.
Elapsed: 00:00:00.07
SQL> set serveroutput on
SQL> exec show_space('demo','auto');
Total Blocks............................9216
Total Bytes.............................75497472
Unused Blocks...........................768
Unused Bytes............................6291456
Last Used Ext FileId....................4
Last Used Ext BlockId...................8328
Last Used Block.........................256
一共有9216個資料塊,HWM
在9216-768=8448這個塊.
也可以通過檢視extents
得到HWM=8*16+128*63+256=8192+256=8448
PL/SQL procedure successfully completed.
Elapsed: 00:00:00.01
SQL> delete from demo where rownum<220000;
219999 rows deleted.
Elapsed: 00:00:40.99
SQL> commit;
Commit complete.
Elapsed: 00:00:00.01
SQL> exec show_space('demo','auto');
Total Blocks............................9216
Total Bytes.............................75497472
Unused Blocks...........................768
Unused Bytes............................6291456
Last Used Ext FileId....................4
Last Used Ext BlockId...................8328
Last Used Block.........................256
PL/SQL procedure successfully completed.
//刪除操作後表的HWM沒有變化,還是在第8448塊這個位置。
Elapsed: 00:00:00.00
SQL> alter table demo shrink space;
alter table demo shrink space
*
ERROR at line 1:
ORA-10636: ROW MOVEMENT is not enabled
//先要enable row movement才能shrink
Elapsed: 00:00:00.09
SQL> alter table demo enable row movement;
Table altered.
Elapsed: 00:00:00.10
SQL> alter table demo shrink space;
Table altered.
Elapsed: 00:01:35.51
SQL> exec show_space('demo','auto');
Total Blocks............................3656
Total Bytes.............................29949952
Unused Blocks...........................0
Unused Bytes............................0
Last Used Ext FileId....................4
Last Used Ext BlockId...................3720
Last Used Block.........................72
PL/SQL procedure successfully completed.
Elapsed: 00:00:00.02
//可以看到HWM降到了3656這個塊上面!
相關文章
- Oracle案例10——HWM(高水位線)效能優化Oracle優化
- ORACLE 高水位線(HWM)Oracle
- Oracle Freelist和HWM原理探討及相關效能優化(轉)Oracle優化
- oracle高水位線處理Oracle
- 一、oracle 高水位線詳解Oracle
- ORACLE資料庫降低高水位線方法Oracle資料庫
- oracle 高水位分析處理Oracle
- oracle 效能優化Oracle優化
- ORACLE FREELIST HWM(轉)Oracle
- Oracle資料庫管理——表資料庫高水位及shrink操作Oracle資料庫
- Oracle效能優化-SQL優化(案例一)Oracle優化SQL
- Oracle效能優化-SQL優化(案例二)Oracle優化SQL
- Oracle效能優化-SQL優化(案例三)Oracle優化SQL
- Oracle效能優化-SQL優化(案例四)Oracle優化SQL
- Oracle 效能優化-expdp備份速度優化02Oracle優化
- Oracle 效能優化-expdp備份速度優化03Oracle優化
- Tomcat高階特性及效能調優Tomcat
- ORACLE 如何診斷高水位爭用(enq: HW – contention)OracleENQ
- Oracle 高水位查詢和處理方法彙總Oracle
- [java][效能優化]java高階開發必會的50個效能優化Java優化
- 效能優化之關於畫素管道及優化(二)優化
- Oracle效能優化使用度量和預警Oracle優化
- Oracle SQL效能優化的40條軍規OracleSQL優化
- 測量、基線和效能優化之三:基於測量、基線和變化的效能優化優化
- 聊聊flink水位線
- 測量、基線和效能優化之二:基線和效能優化
- 【前端效能優化】vue效能優化前端優化Vue
- Oracle效能優化方法論的發展之二:基於OWI的效能優化方法論Oracle優化
- 測量、基線和效能優化之三:基於測量、基線和變化的效能優化v優化
- 使用Android Profile做效能分析及優化Android優化
- 線上Redis高併發效能調優實踐Redis
- 使用資源管理器優化Oracle效能AQ優化Oracle
- Oracle資料庫高水位釋放——LOB欄位空間釋放Oracle資料庫
- [效能優化]UITableView效能優化的一點感悟及計算UILabel高度的新方法優化UIView
- oracle優化Oracle優化
- 【OC梳理】效能檢測及優化彙總優化
- Web 效能優化:理解及使用 JavaScript 快取Web優化JavaScript快取
- Sensei for mac Mac 效能優化及清理工具Mac優化
- 高併發&效能優化(一)------總體介紹優化