1.物理讀(physical read)
當數據塊第一次讀取到,就會緩存到buffer cache 中,而第二次讀取和修改該數據塊時就在內存buffer cache
1.1 第一次讀取
SQL> set autotrace traceonly;
SQL> create table test_read as select * from dba_objects;
Table created.
SQL> select * from test_read;
50349 rows selected.
Execution Plan
----------------------------------------------------------
Plan hash value: 4187308185
-------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 53569 | 9259K| 157 (3)| 00:00:02 |
| 1 | TABLE ACCESS FULL| TEST_READ | 53569 | 9259K| 157 (3)| 00:00:02 |
-------------------------------------------------------------------------------
Note
-----
- dynamic sampling used for this statement
Statistics
----------------------------------------------------------
288 recursive calls
0 db block gets
4120 consistent gets
691 physical reads
0 redo size
5404806 bytes sent via SQL*Net to client
37301 bytes received via SQL*Net from client
3358 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
50349 rows processed
1.2 第二次讀取
SQL> select * from test_read;
50349 rows selected.
Execution Plan
----------------------------------------------------------
Plan hash value: 4187308185
-------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 53569 | 9259K| 157 (3)| 00:00:02 |
| 1 | TABLE ACCESS FULL| TEST_READ | 53569 | 9259K| 157 (3)| 00:00:02 |
-------------------------------------------------------------------------------
Note
-----
- dynamic sampling used for this statement
Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
4019 consistent gets
0 physical reads
0 redo size
5404806 bytes sent via SQL*Net to client
37301 bytes received via SQL*Net from client
3358 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
50349 rows processed
SQL>
1.3 數據塊被重新讀入buffer cache
如果有新的數據需要被讀入Buffer Cache中,而Buffer Cache又沒有足夠的空閑空間,Oracle就根據LRU算法將LRU鏈表中LRU端的數據置換出去。
當這些數據被再次訪問到時,需要重新從磁盤讀入。
SQL> alter session set events 'immediate trace name flush_cache';
Session altered.
SQL> select * from test_read;
50349 rows selected.
Execution Plan
----------------------------------------------------------
Plan hash value: 4187308185
-------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 53569 | 9259K| 157 (3)| 00:00:02 |
| 1 | TABLE ACCESS FULL| TEST_READ | 53569 | 9259K| 157 (3)| 00:00:02 |
-------------------------------------------------------------------------------
Note
-----
- dynamic sampling used for this statement
Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
4019 consistent gets
692 physical reads
0 redo size
5404806 bytes sent via SQL*Net to client
37301 bytes received via SQL*Net from client
3358 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
50349 rows processed
SQL>
2.邏輯讀(buffer read)
邏輯讀指的就是從(或者視圖從)Buffer Cache中讀取數據塊。按照訪問數據塊的模式不同,可以分為即時讀(Current Read)和一致性讀(Consistent Read)。
注意:邏輯IO只有邏輯讀,沒有邏輯寫。
(1)、即時讀(db block gets)
即時讀即讀取數據塊當前的最新數據。任何時候在Buffer Cache中都只有一份當前數據塊。即時讀通常發生在對數據進行修改、刪除操作時。
這時,進程會給數據加上行級鎖,并且標識數據為“臟”數據。
SQL> select * from test_read for update;
50349 rows selected.
Execution Plan
----------------------------------------------------------
Plan hash value: 428907831
--------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 53569 | 9259K| 157 (3)| 00:00:02 |
| 1 | FOR UPDATE | | | | | |
| 2 | TABLE ACCESS FULL| TEST_READ | 53569 | 9259K| 157 (3)| 00:00:02 |
--------------------------------------------------------------------------------
Note
-----
- dynamic sampling used for this statement
Statistics
----------------------------------------------------------
215 recursive calls
51449 db block gets
4894 consistent gets
6 physical reads
10505524 redo size
4507795 bytes sent via SQL*Net to client
37301 bytes received via SQL*Net from client
3358 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
50349 rows processed
SQL>
(2)、一致性讀(consistent gets)
Oracle是一個多用戶系統。當一個會話開始讀取數據還未結束讀取之前,可能會有其他會話修改它將要讀取的數據。
如果會話讀取到修改后的數據,就會造成數據的不一致。一致性讀就是為了保證數據的一致性。在Buffer Cache中的數據塊上都會有最后一次修改數據塊時的SCN。
如果一個事務需要修改數據塊中數據,會先在回滾段中保存一份修改前數據和SCN的數據塊,然后再更新Buffer Cache中的數據塊的數據及其SCN,并標識其為“臟”數據。
當其他進程讀取數據塊時,會先比較數據塊上的SCN和自己的SCN。如果數據塊上的SCN小于等于進程本身的SCN,則直接讀取數據塊上的數據;
如果數據塊上的SCN大于進程本身的SCN,則會從回滾段中找出修改前的數據塊讀取數據。通常,普通查詢都是一致性讀。
下面這個例子幫助大家理解一下一致性讀:
a、第一個會話
SQL> update test_read set status='Y' where object_id=20;
b、第二個會話
SQL> set autotrace traceonly;
SQL> select * from test_read;
50349 rows selected.
Execution Plan
----------------------------------------------------------
Plan hash value: 4187308185
-------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 53569 | 9259K| 157 (3)| 00:00:02 |
| 1 | TABLE ACCESS FULL| TEST_READ | 53569 | 9259K| 157 (3)| 00:00:02 |
-------------------------------------------------------------------------------
Note
-----
- dynamic sampling used for this statement
Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
4020 consistent gets
0 physical reads
0 redo size
5404802 bytes sent via SQL*Net to client
37301 bytes received via SQL*Net from client
3358 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
50349 rows processed
SQL>
結果:明顯看到目前是:4020 consistent gets
而以前是 :4019 consistent gets
說明這多出的一個是要從回滾段中獲取的
3、簡單例子分析一下邏輯讀中的一致性讀和即時讀- Case1:
- HELLODBA.COM>set time on
- 10:22:09 HELLODBA.COM>update t_test1 set SECONDARY='A' where object_id = -1;
- 1 row updated.
- 10:22:22 HELLODBA.COM>commit;
- Commit complete.
- Session 1:
- 10:22:25 HELLODBA.COM>update t_test1 set SECONDARY='B' where object_id = -1 and SECONDARY='B' and (select count(*) from t_test2 t1, t_test2 t2) > 0;
- 0 rows updated.
- 10:23:15 HELLODBA.COM>
- Session 2:
- 10:22:37 HELLODBA.COM>update t_test1 set SECONDARY='B' where object_id = -1;
- 1 row updated.
- 10:23:02 HELLODBA.COM>commit;
- Commit complete.
- 10:23:04 HELLODBA.COM>
- Case2:
- 10:25:38 HELLODBA.COM>update t_test1 set SECONDARY='A' where object_id = -1;
- 1 row updated.
- 10:25:48 HELLODBA.COM>commit;
- Commit complete.
- Session 1:
- 10:26:05 HELLODBA.COM>update t_test1 set SECONDARY='B' where object_id = -1 and SECONDARY='A' and (select count(*) from t_test2 t1, t_test2 t2) > 0;
- 0 rows updated.
- 10:27:21 HELLODBA.COM>
- Session 2:
- 10:26:16 HELLODBA.COM>update t_test1 set SECONDARY='B' where object_id = -1;
- 1 row updated.
- 10:26:41 HELLODBA.COM>commit;
- Commit complete.
- 10:26:42 HELLODBA.COM>
如果你觀察得足夠仔細,你可以從上面2個例子看到一個有趣的現象:無論session 1是否命中到數據,it最終都沒有修改數據。其根本原因就是當前模式讀與一致性讀的區別。
我們知道,為了減少并發沖突,Oracle引入了MVCC(多版本并發控制,也叫MCC)方法。在這種機制中,并發事務不會因為一致性的原因而相互阻塞,除非他們要修改同一條記錄。他們會將日志中所有SCN大于本身事務SCN的日志做回滾,以保證本事務讀取到的數據塊與事務SCN的一致。在Oracle中,這樣的讀取行為就稱為一致性讀。
然而,一致性讀所讀取到數據塊僅僅是某個時間點的一個快照,也就是說這樣的數據是只讀的。如果要修改數據,那么oracle需要讀取到當前的數據塊,也就是當前模式讀。
在一個UPDATE過程中,oracle會先一致性讀取與事務SCN一致的數據快照,并用where條件進行過濾。讓后根據讀取到數據塊的ID,再從當前數據中讀取到相應的數據塊進行修改。但是,如在事務啟動后到數據塊被讀取之間的這段時間內,相應的數據塊發生了改變,那么可能就會有我們意想不到的事情發生。
往回看我們的第一個例子。我們在session 1中,在10:22:25啟動了update事務。但是,由于該事務中存在一個大的子查詢,它會在幾十秒后才會讀取到需要被修改的數據。在Session 2中,我們在10:22:37開始update這些數據并在10:23:02提交了事務。而這個時間是早于數據在session 1中被讀取到的時間的。當session 2中的數據改變被提交后,session 1中的事務讀取到了該數據塊。因為session 2中的事務SCN大于session 1中的事務SCN,因此會讀取UNDO中的數據進行回滾,也就是說它讀取到數據SECONDARY是'A',再通過條件(SECONDARY='B')過濾后,沒有數據被命中,因此也沒有數據被修改。
在第二個例子中,session 1的事務在一致性讀取到數據塊之前也發生了類似的事情。當它回滾了數據后,它一致性讀取到了滿足過濾條件(SECONDARY='A')的數據塊。此時,它需要通過該數據塊ID再到當前數據中讀取該數據塊。但是因為當前數據塊的內容已經被session 2中的事務所修改,它還是沒有能修改到數據。
我想,通過這兩個例子,讀者應該更容易理解到當前模式讀與一致性讀之間的區別。