select返回记录的顺序
(轉(zhuǎn)自:http://blog.chinaunix.net/u2/60332/showart_495257.html)
?
關(guān)鍵詞: select,順序,優(yōu)化,備份,掃描,索引
文章摘要:
?? 當我們執(zhí)行了 select 語句, select 返回的記錄的順序?qū)ξ覀兙幊谭绞接休^大影響,對數(shù)據(jù)庫記錄備份清除以及 sql 性能優(yōu)化都有很大的關(guān)系。因此有必要明確 select 返回記錄的順序。本文按數(shù)據(jù)庫分類討論 oracle/sybase/sql server 返回記錄的順序,從原理探討三種數(shù)據(jù)庫各自的特點,并著重探討了這些差異對數(shù)據(jù)查詢及記錄備份的影響。
縮略語:
IAM : index allocation map
PFS : page free space
1. 簡介
?????? 當我們執(zhí)行了 select 語句, select 返回的記錄的順序?qū)ξ覀兙幊谭绞接休^大影響,對數(shù)據(jù)庫記錄備份清除以及 sql 性能優(yōu)化都有很大的關(guān)系。因此有必要明確 select 返回記錄的順序。
select 返回記錄的順序與數(shù)據(jù)庫類型有很大關(guān)系,因此以下按數(shù)據(jù)庫類型分別討論。本文主要討論了 oracle/sybase/sql server 返回記錄的順序,從原理探討三種數(shù)據(jù)庫各自的特點,并著重探討了這些差異對數(shù)據(jù)查詢及記錄備份的影響。
2. oracle
以下假設(shè)數(shù)據(jù)庫查詢優(yōu)化方式均為基于 rule 的方式, ORACLE 采用兩種訪問表中記錄的方式:
a. 全表掃描 ( Full Table Scan)
全表掃描就是順序地訪問表中每 條記錄. ORACLE采用一次讀入多個數(shù)據(jù)塊(database block)的方式優(yōu)化全表掃描。
b. 通過ROWID訪問表
你可以采用基于 ROWID 的訪問方式情況,提高訪問表的效率, ROWID 包含了表中記錄的物理位置信息。 ORACLE 采用索引 (INDEX) 實現(xiàn)了數(shù)據(jù)和存放數(shù)據(jù)的物理位置 (ROWID) 之間的聯(lián)系。通常索引提供了快速訪問 ROWID 的方法,因此那些基于索引列的查詢就可以得到性能上的提高。通常表現(xiàn)為按索引掃描。 (Index Scan)
2.1全表掃描
如果 select 語句不能使用索引,則 Oracle 按全表掃描方式讀取數(shù)據(jù)塊,對于返回的結(jié)果集, oracle 按 rowid 的大小順序來返回記錄。因此 select * from mytable 與 select * from mytable order by rowid 效果是一樣的
?????? 可以通過 select rowid from table 得到 rowid 偽列,數(shù)據(jù)類型為 ROWID 類型。使用查詢語句返回的是 ROWID 的擴展格式( Extended Rowid )。擴展格式的 ROWID 由 18 個字符組成。這 18 個字符可以按照 OOOOOO.FFF.BBBBBB.SSS 的格式分為 4 組。分別代表數(shù)據(jù)對象編號( Data Object Number ) , 數(shù)據(jù)文件編號( Datafile Number ),數(shù)據(jù)塊編號( Data Block Number ) , 記錄或記錄片斷的塊內(nèi)行號。
?????? 必須說明的是,并不是后插入記錄的 rowid 就越大,有可能后插入的記錄 rowid 還要小。下面給出兩個論點加以證明:
1 .后插入的記錄塊內(nèi)行號可能大,也可能小
根據(jù)我們的試驗,假設(shè)現(xiàn)在表中有三條記錄假設(shè)文件號相同,按塊號,行號排列如下:
108 0
108 1
108 2
刪除中間一條記錄后,得到
108 0
108 2
再增加一條記錄,可能會得到
108 0
108 1? <--- 新增加的記錄
108 2
也可能是
108 0
108 2
108 3? <--- 新增加的記錄
兩種情況均有可能出現(xiàn),取決于 oracle 塊內(nèi)的分配算法。關(guān)于該情況的更深入的分析可以參見文獻 2 。
2 .后插入的記錄的塊號有可能大,有可能小
?????? 插入記錄的塊號并不是線性增加的,而是受 FreeList 控制。有關(guān) FreeList 的理論和算法可以參見文獻 1 。
??????
因此對于全表掃描可以得出以下結(jié)論:
1.???????? 在 oracle 中 select * from mytable 不能保證返回的記錄順序是按插入的先后順序,而是按 rowid 順序。
rowid 的順序與記錄行存儲的“物理序”一致。在沒有索引情況下, select 作全表掃描,是按“物理序”,此時 select 返回記錄按“物理序”最快。
2.???????? 對于已經(jīng)插入的記錄其 ROWID 不會發(fā)生變化。
如果全表掃描方式下,直接使用 rownum 作為選擇條件,根據(jù)結(jié)論 1 ,兩次得到的記錄可能是不一樣的。如果 sql 有時間條件或其他條件作為 sql 語句輔助的篩選(排出當前插入的值),那么再用 rownum 作為選擇條件,則返回的記錄及記錄的順序均是一樣的。
結(jié)論 2 的特性可用于某些日志表的清除 - 備份機制中。對于某些日志表為了提高 insert 性能,可能沒有索引,并且在存儲過程中對這些日志表進行清除和備份。利用 insert into select 先將部分記錄選入到備份表中,再用 delete 語句刪除日志表中的記錄。通過 rownum 來控制操作的行數(shù),避免回滾段問題,通過時間條件來實施結(jié)論 2 ,保證記錄一致。
2.2按索引掃描
對于一段范圍的按索引選擇,在 oracle 內(nèi)部表現(xiàn)為索引葉節(jié)點的掃描,索引葉節(jié)點通常已經(jīng)排序并且葉節(jié)點之間存在指針,便于掃描。由于此時 select 按索引掃描表,因此返回的記錄就按“索引序”排列。
利用上述特征,對于按索引掃描可以有以下的應(yīng)用方式:
1 .通過索引可以使返回記錄事先排序。
在 oracle 中使用索引就可以使返回的記錄得到排序,而無需再使用 order by 。對于不同的排序方式可以用不同的索引完成,通過 hint/*+*/ 指示可以控制索引按不同的掃描方式工作,從而達到不同的效果。如 /*+INDEX(TABLE INDEX_NAME)*/ 或 /*+INDEX_DESC(TABLE INDEX_NAME)*/ 指示按索引升序掃描或按索引降序掃描,從而實現(xiàn)返回的記錄按字段的升序排列或按字段的降序排列。
例如對于表 T(a int,b int) 在 a 上有索引 index_a , b 上有索引 b
則 select * from t 得到的記錄
?
| A | B |
| 19 | 43 |
| 21 | 1 |
| 3 | 10 |
| 5 | 8 |
| 11 | 2 |
select /*+INDEX(T INDEX_A)*/* from t where a>0 或者
select * from t where a>0 order by a
| A | B |
| 3 | 10 |
| 5 | 8 |
| 11 | 2 |
| 19 | 43 |
| 21 | 1 |
從執(zhí)行計劃來看,按索引掃描和按索引 ROWID 方式訪問。
select /*+INDEX_DESC(T INDEX_B)*/* from t where b>0 或者
select * from t where b>0 order by b
| A | B |
| 21 | 1 |
| 11 | 2 |
| 5 | 8 |
| 3 | 10 |
| 19 | 43 |
從執(zhí)行計劃來看,按索引掃描和按索引 ROWID 方式訪問。
?
2 .通過以時間、流水號等字段為索引字段,可以使記錄實現(xiàn)按插入的順序返回
?????? 同樣利用上述特性,來說明 2.1 中的備份問題。當日志表有索引時,選擇限定掃描范圍的索引字段,使之保證后插入的記錄是在結(jié)果集后面的,如時間或流水號等,該順序就保證了按 rownum 控制行數(shù)時 insert 和 delete 操作的記錄是完全一致的,同時基于索引的掃描保證了 sql 的性能。
3.sybase
不管你的 select 語句中是否在 where 后面使用了索引, sybase 均可能基于代價對索引的使用進行調(diào)整。即使沒有 where 語句也有可能使用索引,即使有 where 語句也有可能不用索引。當然,如果表本身就沒有創(chuàng)建任何索引就肯定不會使用到索引。
3.1沒有索引的表
?????? 沒有索引的表在稱為堆表。堆表在 sysindexes 表中有一條對應(yīng)的記錄,其 indid=0 。 first 字段表示堆表的首頁, root 表示堆表的尾頁。堆表中所有的數(shù)據(jù)頁形成從 sysindex.first <-> sysindex.root 的雙向鏈表。
?????? 對于插入記錄,插入到堆表中的所有數(shù)據(jù)會加到該表的尾部。 sybase 利用 sysindex 表的 indid ( =0 )和 root 值, 找出該表的最后一個數(shù)據(jù)頁。如果在該頁上有空間,在數(shù)據(jù)的尾部插入新的記錄行。如果最后一頁上沒有可獲得的空間時,如果在該擴展單元的下一頁有可獲得的空 間,這是用它;如果最后一頁已經(jīng)是擴展單元的最后一頁,則開始使用一個新的擴展單元,對于新加入的頁總是會鏈到鏈表的尾部,同時更新 sysindex.root 的值。
?????? 對于記錄刪除,當刪除一條記錄時,頁內(nèi)緊隨被刪除記錄后的記錄向該頁前部移動,所有未使用的空間相鄰地保留在頁的底部。當一頁中所有行均被刪除,這一頁就會脫離該堆表的數(shù)據(jù)鏈。
??? 對于更新,堆表按下面的原則:
·???????? 如果行的長度沒有變化,就在原來的行上直接更新,并且沒有頁內(nèi)數(shù)據(jù)的移動。
·???????? 如果行的長度變化,并且頁的空閑空間足夠。行還是在頁上的相同位置,但是其它行將上移或下移以保持頁內(nèi)行的連續(xù)。
·???????? 如果該頁不能容納行。在Allpages-locked堆表中,行會被刪除,并且“新”行插入到最后頁。Data-only-lockedthe 堆表中,行插入到另外的頁中,在原來的位置采用轉(zhuǎn)向指針指到該頁面,這樣保證行的ID位置不變。
對于掃描,按 sysindex.first <-> sysindex.root 鏈表方式讀取數(shù)據(jù)頁。
對于堆表,根據(jù)上述插入、刪除、更新、掃描特性,可以得到下面的結(jié)論:
1. 對于不帶任何索引的堆表,如果確保不使用 update ,或確保 update 不產(chǎn)生插入操作,就可以放心的使用 select 完成自然排序,此時記錄按插入的先后順序返回。
?
3.2有索引的表
對于sybase執(zhí)行計劃沒有帶索引的表,select返回記錄的順序和堆表掃描返回的順序相同。
對于 sybase 執(zhí)行計劃帶索引的表, select? 按索引字段的順序返回記錄。 sybase 將索引組織為 B 樹。索引內(nèi)的每一頁包含一個頁首,頁首后面跟著索引行。每個索引行都包含一個鍵值以及一個指向較低級頁或數(shù)據(jù)行的指針。索引的每個頁稱為索引節(jié)點。 B 樹的頂端節(jié)點稱為根節(jié)點。索引的底層節(jié)點稱為葉節(jié)點。每級索引中的頁鏈接在雙向鏈接列表中。
?????? 對于有索引的表,得到以下結(jié)論:
1 .以通過控制索引來控制查詢方式,從而控制返回順序。
?????? 如我們可以通過 (index index_name) 來指定對某個索引的使用,從而達到按索引 index_name 排序。也可以使用 (index 0) 指示強制不使用索引,從而使返回的記錄順序按堆表方式。
2 .如何沒有強制指定索引,不管你的 select 語句中是否在 where 后面使用了索引, sybase 均可能基于代價對索引的使用進行調(diào)整。由于 sybase 基于代價執(zhí)行計劃會對索引的使用進行調(diào)整,因此不能像 oracle 那樣利用非聚簇索引完成返回記錄的自然排序,這時最好加上 order by 以保證排序的準確。
3 .如果需要排序的字段是聚簇索引,那么就可以放心使用該索引完成排序。這時,不論執(zhí)行計劃怎樣, sybase 均按聚簇索引字段順序返回記錄。對于聚簇索引表,在插入數(shù)據(jù)時,會引起頁內(nèi)部分記錄(值大的記錄)的移動,通過移動 sybase 保證了數(shù)據(jù)的物理順序與聚簇索引順序一致。
4.Ms Sql Server
?????? 不管你的 select 語句中是否在 where 后面使用了索引, Sql Server 均可能基于代價對索引的使用進行調(diào)整。即使沒有 where 語句也有可能使用索引,即使有 where 語句也有可能不用索引。當然,如果表本身就沒有創(chuàng)建任何索引就肯定不會使用到索引。
4.1沒有索引的表
沒有索引的表在稱為堆表或堆集。堆集使用 IAM 管理擴展盤區(qū),多個 IAM 形成 IAM 鏈。 堆集在 sysindexes 內(nèi)有一行,其 indid = 0。sysindexes.FirstIAM 列指向 IAM 頁鏈的 IAM 首頁,IAM 頁鏈管理分配給堆集的空間。SQL Server 2000 使用 IAM 頁在堆集中瀏覽。堆集內(nèi)的數(shù)據(jù)頁和行沒有任何特定的順序,也不鏈接在一起。數(shù)據(jù)頁之間唯一的邏輯連接是記錄在 IAM 頁內(nèi)的連接。
對于插入操作,當SQL Server 2000 需要插入新行而當前頁沒有可用空間時,它使用 IAM 和 PFS 頁查找具有足夠空間容納該行的頁。SQL Server 使用 IAM 頁查找分配給對象的擴展盤區(qū)。對于每個擴展盤區(qū),SQL Server 搜索 PFS 頁以查看是否有一頁具有足夠的空間容納這一行。
SQL Server 只有當無法在現(xiàn)有的擴展盤區(qū)內(nèi)快速找到一頁有足夠空間容納正插入的行時,才給對象分配新的擴展盤區(qū)。SQL Server 使用按比例分配算法,從文件組內(nèi)的可用擴展盤區(qū)中分配擴展盤區(qū)。如果一個文件組有兩個文件,其中一個的可用空間是另一個的兩倍,那么每從后者分配一頁,就 從前者分配兩頁。這意味著文件組內(nèi)的每個文件應(yīng)該有近似的空間使用百分比。
對于刪除操作,在堆表中,即使刪除了記錄,該記錄所在頁不會作頁內(nèi)移動。
對于數(shù)據(jù)更新,SQL Server可以采用多種方式來進行。更新可能是現(xiàn)場發(fā)生的,也可能是以先刪除然后插入的方式進行的,還可以是通過查詢處理器或存儲引擎來管理更新。但是 在堆表中,總是采用現(xiàn)場更新方式,對于更新的內(nèi)容原來的頁不能容納的情況,sql server 2000采用轉(zhuǎn)向指針處理,保證了更新后該記錄位置的不變。
通過掃描 IAM 頁可以對堆集進行表掃描或串行讀,以找到容納這個堆集的頁的擴展盤區(qū)。因為 IAM 按擴展盤區(qū)在數(shù)據(jù)文件內(nèi)存在的順序表示它們,所以這意味著串行堆集掃描一律沿每個文件進行。
根據(jù)上述堆表的插入、更新、刪除、掃描原則,可以得到以下的結(jié)論:
1 .使用 IAM 頁設(shè)置掃描順序意味著堆集中的行一般不按照插入的順序返回。
2 .對于已經(jīng)存在的記錄,記錄的位置(數(shù)據(jù)庫號,文件號,頁號,行號)不會變化。
?????? 結(jié)論 2 可應(yīng)用到備份 - 清除機制中。如果日志表是沒有索引的堆表,就可以通過時間、流水號等字段排除當前插入的記錄,使 select 和 delete 兩次操作返回的結(jié)果集及順序完全一致,再通過 set rowcount 來控制每次操作的記錄條數(shù),使得備份 - 清除操作能夠安全進行。
4.2有索引的表
對于Sql Server 執(zhí)行計劃沒有帶索引的表,select返回記錄的順序和堆表掃描返回的順序相同。
對于 Sql Server 執(zhí)行計劃帶索引的表, select?? 按索引字段的順序返回記錄。 SQL Server 將索引組織為 B 樹。索引內(nèi)的每一頁包含一個頁首,頁首后面跟著索引行。每個索引行都包含一個鍵值以及一個指向較低級頁或數(shù)據(jù)行的指針。索引的每個頁稱為索引節(jié)點。 B 樹的頂端節(jié)點稱為根節(jié)點。索引的底層節(jié)點稱為葉節(jié)點。每級索引中的頁鏈接在雙向鏈接列表中。
對于有索引的表,得到以下結(jié)論:
1 .可以通過控制索引來控制查詢方式,從而控制返回順序。
?????? 如我們可以通過 with(index(index_name)) 來指定對某個索引的使用,從而達到按索引 index_name 排序。
2 .如何沒有強制指定索引,不管你的 select 語句中是否在 where 后面使用了索引, Sql Server 均可能基于代價對索引的使用進行調(diào)整,即使沒有 where 語句也有可能使用索引,即使有 where 語句也有可能不用索引。不管你的 delete 語句中是否在 where 后面使用了索引, Sql Server 均可能基于代價對索引的使用進行調(diào)整,即使沒有 where 語句也有可能使用索引,即使有 where 語句也有可能不用索引。帶相同 where 語句的 select 和 delete 執(zhí)行計劃很可能不一樣。
?????? 因此 select 和 delete 得到的記錄順序很可能不一致,如果要選取前 n 條記錄 , 那么得到的記錄集盡管條數(shù)一致但內(nèi)容不一致。盡管我們可以通過 with(index(index_name)) 來強制 select 對索引的使用,但 delete 卻不能夠強制指定索引,因為 delete 涉及對索引本身的刪除。
?????? 這種情況下,如果數(shù)據(jù)庫的性能夠好,要備份的數(shù)據(jù)不多,就不要使用 set rowcount 來控制條數(shù)。但如果確需要控制一次刪除的條數(shù),可以直接在 where 條件中控制更小的范圍,如時間范圍控制到小時,一天的數(shù)據(jù)通過 24 小時的循環(huán)來備份。
?????? 要么采用 DTS 作備份。
3 .如果需要排序的字段是聚簇索引,那么就可以放心使用該索引完成排序。這時,不論執(zhí)行計劃怎樣, sql server 均按聚簇索引字段順序返回記錄。
?
參考文獻和資料:
1.《Oracle Freelist和HWM原理探討及相關(guān)性能優(yōu)化》,游波
2. 《 關(guān)于block中數(shù)據(jù)的存儲和重組的探究 》,http://www.itpub.net
3.《 怎樣按物理順序提取記錄? 》,http://www.itpub.net
4.《 如何找出一個表的最后一行?物理插入順序 》,http://www.itpub.net
5 .《 Oracle 9i for windows nt/2000 數(shù)據(jù)系統(tǒng)培訓教程 》,清華大學出版社
6. 《 Microsoft SQL Server 2000 技術(shù)內(nèi)幕 》,北京大學出版社
7. 《 Heaps of data: tables without clustered indexes 》
上述部分文章在我的blog網(wǎng)站http://blog.csdn.net/youbo2004 上可找到。
總結(jié)
以上是生活随笔為你收集整理的select返回记录的顺序的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: imp导入时触发器的状态
- 下一篇: 滴滴启动造车项目 意料之外情理之中