文章詳情頁(yè)
Microsoft SQL Server 查詢處理器的內(nèi)部機(jī)制與結(jié)構(gòu)(2)
瀏覽:98日期:2023-10-26 19:51:20
圖 5. 準(zhǔn)備/執(zhí)行模型調(diào)用存儲(chǔ)過(guò)程存儲(chǔ)過(guò)程一般是從 ODBC 和 OLE-DB,通過(guò)發(fā)送 SQL 語(yǔ)句給使用 ODBC 標(biāo)準(zhǔn) CALL 語(yǔ)法調(diào)用過(guò)程的 SQL Server 來(lái)調(diào)用。其應(yīng)類(lèi)似于以下語(yǔ)句:SQLExecDirect(hstm, "{call addorder(?)}", SQL_NTS)對(duì)于默認(rèn)結(jié)果集,這是一個(gè)簡(jiǎn)單的流,因?yàn)檫@正是 RPC 消息原本要處理的對(duì)象。客戶機(jī)向服務(wù)器發(fā)送 RPC 消息,并獲取來(lái)自存儲(chǔ)過(guò)程的處理結(jié)果。如果是游標(biāo),則情況稍微復(fù)雜一些,客戶機(jī)需要調(diào)用 Sp_cursoropen,就像其他游標(biāo)一樣。 Sp_cursoropen 含有內(nèi)部邏輯,檢測(cè)該存儲(chǔ)過(guò)程是否只包含一條 SELECT 語(yǔ)句。如果是,則對(duì)該 SELECT 語(yǔ)句打開(kāi)一個(gè)游標(biāo)。如果該存儲(chǔ)過(guò)程中不是一條 SELECT 語(yǔ)句,則客戶機(jī)會(huì)得到一個(gè)指示,說(shuō)明“我們?yōu)槟蜷_(kāi)結(jié)果集,但是我們將以流水的方式返回?cái)?shù)據(jù)流,您可以把這個(gè)數(shù)據(jù)流提供給用戶”。存儲(chǔ)過(guò)程執(zhí)行流程如圖 6 所示。圖 6. 調(diào)用存儲(chǔ)過(guò)程SQL Manager 前面已經(jīng)提到過(guò)的 SQL Manager 驅(qū)動(dòng)很多服務(wù)器處理過(guò)程,它實(shí)際上是服務(wù)器的心臟。SQL Manager 處理所有調(diào)用存儲(chǔ)過(guò)程的請(qǐng)求,管理過(guò)程緩存,擁有虛擬系統(tǒng)存儲(chǔ)過(guò)程,在稍后要介紹的特定查詢的自動(dòng)參數(shù)化過(guò)程中也要涉及。如果您有與本文類(lèi)似的描述 SQL 6.5 或更老版本的文章,則不會(huì)讀到有關(guān) SQL 管理器的討論,然而,您會(huì)讀到一些完成 SQL 管理器工作的一些不同的組件。但是在 SQL Server 7.0 中,這些組件被統(tǒng)一為 SQL 管理器,通過(guò)系統(tǒng)驅(qū)動(dòng)查詢語(yǔ)句的處理。在一般情況下,當(dāng)要求 SQL 管理器為您做某些工作時(shí),通過(guò) RPC 消息調(diào)用 SQL 管理器。但是,當(dāng)通過(guò) SQL 消息發(fā)送 SQL 語(yǔ)句并進(jìn)入引擎編譯時(shí),也會(huì)用到 SQL 管理器。當(dāng)存儲(chǔ)過(guò)程或批處理程序包含 EXEC 語(yǔ)句時(shí),也會(huì)調(diào)用 SQL 管理器,因?yàn)?EXEC 實(shí)際上就是調(diào)用 SQL 管理器。如果該 SQL 語(yǔ)句傳送了下面就要討論的一個(gè)自動(dòng)參數(shù)化模板,則需要調(diào)用 SQL 管理器對(duì)該查詢進(jìn)行參數(shù)化處理。當(dāng)特定查詢語(yǔ)句需要裝入緩存時(shí),也要調(diào)用 SQL 管理器。編譯與執(zhí)行現(xiàn)在討論在 SQL Server 中編譯和執(zhí)行的一般流程。需要注意的是編譯和執(zhí)行在 SQL Server 內(nèi)部是兩個(gè)不同的階段。 SQL Server 編譯查詢語(yǔ)句和執(zhí)行該語(yǔ)句之間的間隔時(shí)間可能非常短,只有幾個(gè)毫秒,也可能是幾秒鐘、幾分鐘、幾小時(shí)甚至幾天。在編譯過(guò)程中(這個(gè)過(guò)程包括優(yōu)化),我們必須區(qū)分什么樣的知識(shí)可以用于編譯。并不是所有對(duì)編譯有用的知識(shí)對(duì)執(zhí)行也起作用。您必須把編譯和執(zhí)行理解為兩個(gè)不同的活動(dòng),即使您發(fā)送并立即執(zhí)行的是特定 SQL 查詢語(yǔ)句。當(dāng) SQL Server 可以開(kāi)始處理查詢語(yǔ)句時(shí),SQL Manager 要在緩存內(nèi)進(jìn)行查找,如果沒(méi)有找到該語(yǔ)句,則必須編譯該語(yǔ)句。編譯處理要完成以下幾件工作。首先,要進(jìn)行分析和正常化。分析就是剖析該 SQL 語(yǔ)句,將其轉(zhuǎn)換成更適合計(jì)算機(jī)處理的數(shù)據(jù)結(jié)構(gòu)。分析還要驗(yàn)證語(yǔ)法的正確性。分析不進(jìn)行表名和列名合法性等檢查,這些工作在正常化階段完成。正常化主要是解析 SQL 語(yǔ)句中引用的對(duì)象,轉(zhuǎn)換成實(shí)際的數(shù)據(jù)庫(kù)對(duì)象,檢查請(qǐng)求的語(yǔ)義是否有意義。例如,試圖執(zhí)行一個(gè)表,這在語(yǔ)義上就是錯(cuò)誤的。下一步是編譯 Transact-SQL 代碼。Transact-SQL 和 SQL 本身都讓人有點(diǎn)兒困惑,Microsoft 的開(kāi)發(fā)人員也像別人一樣經(jīng)常互換兩個(gè)詞。但是,這兩者之間還是有重要差別的。SQL 包括所有 DML 語(yǔ)句:INSERT、UPDATE、DELETE 和 SELECT。SQL Server 還有一種包括這些 DML 語(yǔ)句的語(yǔ)言,稱(chēng)為 Transact-SQL,也就是 TSQL。TSQL 提供過(guò)程結(jié)構(gòu):IF 語(yǔ)句、WHILE 語(yǔ)句、局部變量聲明等。服務(wù)器內(nèi)部對(duì) SQL 和 TSQL 的處理方法完全不同。TSQL 的過(guò)程邏輯要由知道如何進(jìn)行過(guò)程化處理的引擎來(lái)編譯。SQL 語(yǔ)句本身由典型的查詢優(yōu)化器來(lái)處理。優(yōu)化器必須把基于集合的 SQL 語(yǔ)句的非過(guò)程化的請(qǐng)求,翻譯成可以被高效執(zhí)行并返回所需結(jié)果的過(guò)程。除非特別說(shuō)明,我們?cè)谝韵掠懻摼幾g時(shí),均指 TSQL 的編譯和 SQL 語(yǔ)句的優(yōu)化。上面已經(jīng)提到,編譯和執(zhí)行是兩個(gè)不同的查詢處理階段,因此,優(yōu)化器完成的工作之一是基于相當(dāng)穩(wěn)定的狀態(tài)進(jìn)行優(yōu)化。您可以注意到,SQL Server 可能會(huì)根據(jù)語(yǔ)句所滿足的條件重新編譯,所以狀態(tài)并不是永遠(yuǎn)穩(wěn)定的,但也不是處于不停的變化之中。如果優(yōu)化器使用的信息變化太劇烈、太經(jīng)常 — 并發(fā)處理器的數(shù)量和鎖的數(shù)量不穩(wěn)定 — 則必須不斷重新進(jìn)行編譯,而一般來(lái)說(shuō)編譯是比較耗時(shí)的。例如,SQL 語(yǔ)句的運(yùn)行時(shí)間為百分之一秒,而編譯可能需要占用半秒。最理想的情況是,SQL Server 能夠只編譯語(yǔ)句一次,而執(zhí)行成千上萬(wàn)次,不必每次執(zhí)行該語(yǔ)句時(shí)都重新編譯它。編譯階段的最終產(chǎn)品是查詢計(jì)劃,放在過(guò)程緩存中。便宜的特定 SQL 計(jì)劃并不放在緩存中,不過(guò)這只是個(gè)小問(wèn)題。我們不希望緩存被不太可能重復(fù)執(zhí)行的內(nèi)容占滿,一般來(lái)說(shuō),特定 SQL 語(yǔ)句的計(jì)劃是最不可能反復(fù)使用的了。如果語(yǔ)句編譯已經(jīng)很便宜(小于百分之一秒),則沒(méi)有必要再把計(jì)劃放入緩存,用不太可能重新使用的計(jì)劃占用緩存。把計(jì)劃放入緩存之后,SQL Manager 按照?qǐng)?zhí)行要求邏輯進(jìn)行檢查,確定是否有更改的內(nèi)容,是否需要重新編譯。即使編譯到執(zhí)行之間時(shí)間間隔只有幾毫秒,也可能有人會(huì)執(zhí)行一條數(shù)據(jù)定義語(yǔ)句 (DDL),為關(guān)鍵的表加了索引。這種可能性不大,但是確實(shí)存在,因此 SQL Server 必須考慮這一點(diǎn)。有幾種情況 SQL Server 必須重新編譯存儲(chǔ)規(guī)劃。元數(shù)據(jù)的修改,例如增加或刪除索引,是重新編譯的最主要的原因。服務(wù)器必須確信所使用的計(jì)劃反映了索引的當(dāng)前狀態(tài)。重新編譯的另一種原因是統(tǒng)計(jì)情況發(fā)生變化。SQL Server 還維護(hù)不少數(shù)據(jù)使用頻率的統(tǒng)計(jì)信息。如果數(shù)據(jù)使用頻率分布情況變化很大,則可能需要另一個(gè)查詢計(jì)劃以便更有效地執(zhí)行。SQL Server 跟蹤表數(shù)據(jù)插入和刪除的統(tǒng)計(jì)數(shù)據(jù),如果數(shù)據(jù)修改的數(shù)量超過(guò)根據(jù)表的容量變化的某一閾值,則需要根據(jù)新的分布數(shù)據(jù)重新編譯計(jì)劃。圖 7 給出了編譯和執(zhí)行過(guò)程的流程。圖 7. 編譯與執(zhí)行注意,實(shí)際參數(shù)的改變并不會(huì)導(dǎo)致重新編譯,環(huán)境的改變,例如可用內(nèi)存的增加或所需數(shù)據(jù)的增加,也不會(huì)導(dǎo)致重新編譯。執(zhí)行是比較簡(jiǎn)單的,如果需要執(zhí)行的查詢很簡(jiǎn)單,如“插入一行”,或從帶有唯一索引的表中查詢數(shù)據(jù),則執(zhí)行處理會(huì)非常簡(jiǎn)單。但是,很多查詢都要求大量的內(nèi)存以提高運(yùn)行效率,或至少?gòu)乃黾拥膬?nèi)存得到好處。在 SQL Server 6.5 中,每個(gè)查詢能夠使用的內(nèi)存限制在 0.5MB 或 1MB 以下。有一個(gè)控制查詢內(nèi)存使用的參數(shù),稱(chēng)為排序頁(yè)。顧名思義,它主要是限制可能占用大量?jī)?nèi)存的排序操作。不管要處理的排序有多大,在 SQL Server 6.5 中,內(nèi)存的使用不能超過(guò) 1MB。即使您使用的機(jī)器上配置了 2GB 內(nèi)存,需要對(duì)數(shù)百萬(wàn)行數(shù)據(jù)排序,也不能突破限制。顯然,復(fù)雜的查詢不能高效執(zhí)行,因此 SQL Server 開(kāi)發(fā)人員增加了 SQL Server 7.0 的能力,使得單個(gè)查詢可以使用大量的內(nèi)存。另一個(gè)問(wèn)題隨之而來(lái)。一旦您開(kāi)始允許查詢使用大量?jī)?nèi)存,就必須確定如何把內(nèi)存分配給可能需要內(nèi)存的很多查詢。 SQL Server 按照以下方法解決這個(gè)問(wèn)題。當(dāng)查詢計(jì)劃優(yōu)化之后,優(yōu)化器要確定有關(guān)給該查詢使用的內(nèi)存的兩部分信息。第一,該查詢有效執(zhí)行所需要的最小內(nèi)存,該參數(shù)與查詢計(jì)劃一起存放。優(yōu)化器還要確定該查詢可以獲益的最大的內(nèi)存量。例如,如果要排序的整個(gè)表只有 100MB,分配 2GB 內(nèi)存就沒(méi)什么幫助了。您需要的只是 100MB,這個(gè)最大有用內(nèi)存參數(shù)隨查詢計(jì)劃一起存放。當(dāng) SQL Server 開(kāi)始執(zhí)行計(jì)劃時(shí),該計(jì)劃被傳遞給一個(gè)所謂內(nèi)存授權(quán)調(diào)度程序的例程中。這個(gè)授權(quán)調(diào)度程序要完成幾項(xiàng)有趣的工作。首先,如果授權(quán)調(diào)度程序要處理的查詢?cè)谟?jì)劃中沒(méi)有排序或雜湊操作,則 SQL Server 知道該查詢不會(huì)需要很多內(nèi)存。在這種情況下,不需要內(nèi)存授權(quán)調(diào)度程序進(jìn)行判斷。該計(jì)劃會(huì)立即執(zhí)行,因此典型的事務(wù)處理請(qǐng)求會(huì)完全旁路這種判斷機(jī)制。內(nèi)存授權(quán)調(diào)度程序還設(shè)有多個(gè)隊(duì)列處理不同容量的請(qǐng)求。內(nèi)存調(diào)度程序優(yōu)先處理較小的請(qǐng)求。例如,如果有一個(gè)查詢要求“提取前 10 個(gè)”,并且只需要對(duì) 20 行排序,則雖然需要經(jīng)過(guò)內(nèi)存授權(quán)調(diào)度程序,但是要釋放該查詢并且很快調(diào)度。服務(wù)器需要并行或并發(fā)執(zhí)行許多這種查詢。如果有很大的查詢,您希望一次只運(yùn)行幾個(gè)查詢,讓它們占有所需的更多內(nèi)存。SQL Server 確定一個(gè)由 4 X(系統(tǒng)中的 CPU 個(gè)數(shù))得到的數(shù)。如果可能,SQL Server 會(huì)同時(shí)運(yùn)行那個(gè)數(shù)量的查詢,為它們分配高效運(yùn)行所需要的最小內(nèi)存。如果還剩有內(nèi)存,則一部分查詢會(huì)允許占用最大高效內(nèi)存。SQL Server 試圖既為查詢分配盡可能多的內(nèi)存,又讓盡可能多的查詢同時(shí)運(yùn)行在系統(tǒng)中。能夠使用最大高效內(nèi)存對(duì)某些操作很重要,例如夜間運(yùn)行的批處理過(guò)程。您可能會(huì)生成很大的報(bào)表,或重新建立索引。這些查詢可能使用大量?jī)?nèi)存,這種機(jī)制可以動(dòng)態(tài)調(diào)整對(duì)內(nèi)存的需求。因此,如果如果在隊(duì)列中等待處理的查詢不多,則內(nèi)存授權(quán)調(diào)度程序會(huì)經(jīng)常分配給查詢最大需要的內(nèi)存。如果白天的機(jī)器負(fù)載很重,則就不能同時(shí)運(yùn)行太多的查詢。這些查詢會(huì)得到有效運(yùn)行所需最小的內(nèi)存,讓內(nèi)存為更多的查詢共享。一旦調(diào)度程序說(shuō)現(xiàn)在可以為請(qǐng)求分配內(nèi)存,則計(jì)劃即被“打開(kāi)”,開(kāi)始實(shí)際運(yùn)行。計(jì)劃會(huì)一直運(yùn)行直到完成。如果查詢使用了默認(rèn)結(jié)果集模型,則計(jì)劃會(huì)一直運(yùn)行到檢索到所有結(jié)果為止,然后把結(jié)果返回給客戶機(jī)。如果使用的是游標(biāo)模型,則處理過(guò)程略有不同。每個(gè)客戶機(jī)請(qǐng)求只提取一塊數(shù)據(jù),并不是所有數(shù)據(jù)。當(dāng)每個(gè)結(jié)果塊返回給客戶機(jī)之后,SQL Server 必須等待客戶機(jī)的下一個(gè)請(qǐng)求。在等待時(shí),整個(gè)計(jì)劃就會(huì)睡眠。這意味著要釋放一些鎖,要釋放一些資源,并保留一些斷點(diǎn)信息。這些斷點(diǎn)信息使得 SQL Server 能夠返回到睡眠之前的狀態(tài),使得執(zhí)行可以繼續(xù)。過(guò)程緩存我們?cè)谇懊嬉呀?jīng)多次提到 SQL Server 的過(guò)程緩存。需要注意的是,SQL Server 7.0 的過(guò)程緩存與以前的版本有很大不同。在早期的版本中,有兩個(gè)有效配置值用于控制過(guò)程緩存的容量:一個(gè)是定義 SQL Server 總可用內(nèi)存的固定容量,另一個(gè)是供存儲(chǔ)查詢計(jì)劃使用的內(nèi)存百分比(扣除滿足固定需要的內(nèi)存)。在老版本中,特定 SQL 語(yǔ)句從不存入緩存,只有存儲(chǔ)過(guò)程計(jì)劃才存入其中。在 SQL Server 7.0 中,內(nèi)存的總?cè)萘渴莿?dòng)態(tài)的,用于查詢計(jì)劃的空間也是經(jīng)常變化的。 在處理查詢時(shí),SQL Server 7.0 首先會(huì)問(wèn)的是:這個(gè)查詢既是特定的又是易于編譯的嗎?如果是,SQL Server 就根本不會(huì)將其寫(xiě)入緩存中。將來(lái)重新編譯這些計(jì)劃比把復(fù)雜的計(jì)劃或數(shù)據(jù)頁(yè)推出內(nèi)存更合算。如果查詢不是特定的或不易于編譯,則 SQL Server 會(huì)從緩存區(qū)中分配一些緩存內(nèi)存存儲(chǔ)該計(jì)劃,因?yàn)樵摼彺鎱^(qū)是 SQL Server 7.0 用來(lái)滿足 99% 內(nèi)存需求的唯一來(lái)源。在少數(shù)特殊情況下,SQL Server 會(huì)直接從操作系統(tǒng)中分配大塊內(nèi)存,但是這種情況極為罕見(jiàn)。SQL Server 的管理是集中式的。寫(xiě)入緩存的除計(jì)劃外,還有反映通過(guò)編譯該查詢實(shí)際創(chuàng)建該計(jì)劃的成本的成本因子。如果這是一個(gè)特定計(jì)劃,則 SQL Server 將它的成本設(shè)置為 0,表示可以立即將它撤出過(guò)程緩存。對(duì)于特定 SQL,雖然有可能被重復(fù)使用,但可能性很小,如果系統(tǒng)內(nèi)存緊張,總是愿意首先撤出特定語(yǔ)句的計(jì)劃。這樣,特定查詢的計(jì)劃是最適合清出緩存的對(duì)象。如果查詢不是特定的,則 SQL Server 會(huì)把該成本設(shè)置為實(shí)際編譯查詢的成本。這些成本是以磁盤(pán) I/O 為單位的。如果從磁盤(pán)中讀出一個(gè)數(shù)據(jù)頁(yè),則有一個(gè) I/O 成本。在編譯計(jì)劃時(shí),信息從磁盤(pán)中讀出,包括統(tǒng)計(jì)數(shù)據(jù)和查詢本身的文本。SQL 要進(jìn)行附加的處理,而且這處理工作被正常化為 I/O 成本。現(xiàn)在,建立過(guò)程的成本可用執(zhí)行 I/O 的成本表示。該成本非常恰當(dāng)反映了,與打算用磁盤(pán)緩存的數(shù)據(jù)量相比,管理實(shí)際打算分配給存儲(chǔ)過(guò)程和任何種類(lèi)查詢計(jì)劃的緩存量的能力。該成本被計(jì)算出來(lái)之后,該計(jì)劃就會(huì)被寫(xiě)入緩存。圖 8 顯示計(jì)算計(jì)劃成本并將其寫(xiě)入緩存的流程。圖 8. 將計(jì)劃寫(xiě)入緩存如果另一個(gè)查詢可以重新使用該計(jì)劃,則 SQL Server 要再次判定計(jì)劃的類(lèi)型。如果是一個(gè)特定計(jì)劃,SQL Server 會(huì)把成本加 1。這樣,如果特定計(jì)劃確實(shí)要被重新使用,則它會(huì)在緩存中稍作停留,停留時(shí)間越長(zhǎng),成本就增加越多。如果該計(jì)劃經(jīng)常被重新使用,則成本會(huì)一次增加一個(gè)單位地不斷增長(zhǎng),直到增長(zhǎng)到其實(shí)際編譯成本。該成本和設(shè)置的成本一樣高。不過(guò)該計(jì)劃經(jīng)常被重復(fù)使用;如果同一用戶或其他用戶不斷重新提交完全一樣的 SQL 文本,該計(jì)劃就會(huì)留在緩存中。 如果查詢不是特定的,也就是說(shuō)是一個(gè)存儲(chǔ)過(guò)程、帶參數(shù)的查詢或自動(dòng)參數(shù)化的查詢,則每次該計(jì)劃被重新使用時(shí),成本都會(huì)設(shè)置回原來(lái)的值。只要計(jì)劃被重新使用,就會(huì)留在緩存中。即使有一段時(shí)間沒(méi)有被使用,取決于最初的編譯代價(jià)的高低,計(jì)劃停留在緩存中的時(shí)間也有長(zhǎng)短。圖 9 顯示從緩存中檢索計(jì)劃并調(diào)整成本的流程。圖 9. 從緩存中檢索計(jì)劃遲緩寫(xiě)入器(Lazywriter) 是使計(jì)劃過(guò)時(shí)的機(jī)制,負(fù)責(zé)在必要的時(shí)候從緩存中刪除計(jì)劃。遲緩寫(xiě)入器實(shí)際上是存儲(chǔ)引擎的一部分,但是因?yàn)檫t緩寫(xiě)入器對(duì)于查詢處理機(jī)制是如此重要,我們還是在這里進(jìn)行討論。遲緩寫(xiě)入器管理查詢計(jì)劃內(nèi)存使用的機(jī)制與管理頁(yè)面的機(jī)制一樣,因?yàn)?SQL Server 7.0 計(jì)劃存儲(chǔ)在普通緩沖存儲(chǔ)器中。遲緩寫(xiě)入器要檢查系統(tǒng)中所有的緩沖器標(biāo)題。如果系統(tǒng)的內(nèi)存不緊張,檢查的次數(shù)就很少;如果開(kāi)始緊張,則遲緩寫(xiě)入器就會(huì)經(jīng)常運(yùn)行。當(dāng)遲緩寫(xiě)入器運(yùn)行時(shí),它要檢查緩沖區(qū)標(biāo)題,并檢查緩存區(qū)中該頁(yè)面的當(dāng)前成本。如果成本為 0,則意味著自從上次遲緩寫(xiě)入器檢查以來(lái),該頁(yè)面沒(méi)有被使用過(guò),于是遲緩寫(xiě)入器就會(huì)釋放該頁(yè)面,以便為系統(tǒng)增加可用內(nèi)存,用于頁(yè)面 I/O 或其他計(jì)劃。此外,如果該緩沖區(qū)包含過(guò)程計(jì)劃,則遲緩寫(xiě)入器會(huì)調(diào)用 SQL 管理器,以完成一些清理工作。最后,該緩沖區(qū)會(huì)被放到可用內(nèi)存表中供重新使用。如果與緩沖區(qū)關(guān)聯(lián)的成本大于 0,則遲緩寫(xiě)入器會(huì)把成本減 1,并繼續(xù)檢查其他緩沖區(qū)。這成本實(shí)際上反映的,某計(jì)劃若是沒(méi)被使用,它在緩存中還能存在多少個(gè)遲緩寫(xiě)入器的檢查周期。這種算法,除了如果對(duì)象是存儲(chǔ)過(guò)程則調(diào)用 SQL Manager 這一步之外,對(duì)緩存中的計(jì)劃和緩存的數(shù)據(jù)或索引沒(méi)有什么區(qū)別。遲緩寫(xiě)入器并不知道對(duì)象是否存儲(chǔ)過(guò)程,這種算法很好地平衡了磁盤(pán) I/O 對(duì)緩存的使用和存儲(chǔ)過(guò)程計(jì)劃對(duì)緩存的使用。 您會(huì)發(fā)現(xiàn),如果計(jì)劃的編譯成本很高,那么即使很長(zhǎng)一段時(shí)間都沒(méi)有被重新使用,也仍然會(huì)停留在緩存中,這是因?yàn)槠涑跏汲杀咎吡恕=?jīng)常被重新使用的計(jì)劃也會(huì)長(zhǎng)期停留在緩存中,這是因?yàn)槊慨?dāng)它被重新使用時(shí)其成本已被重新設(shè)置,遲緩寫(xiě)入器不會(huì)看到它的成本降為 0。圖 10 顯示遲緩寫(xiě)入器處理緩存的流程。圖 10. 遲緩寫(xiě)入器處理緩存的流程處理客戶機(jī)的 SQL下面再看看提交 SQL 語(yǔ)句之后的處理過(guò)程。首先,我們將研究客戶機(jī)向 SQL Server 發(fā)送 RPC 事件。因?yàn)?SQL Server 收到了 RPC 事件,所以它會(huì)知道該事件是某種參數(shù)化的 SQL;它是準(zhǔn)備/執(zhí)行模型,或者是 EXECUTESQL。SQL Server 需要構(gòu)建一個(gè)緩存鍵,以標(biāo)識(shí)這個(gè)具體的 SQL Server 文本。如果 SQL Server 處理的是實(shí)際的存儲(chǔ)過(guò)程,則不需要建立它自己的鍵;直接使用該過(guò)程的名稱(chēng)即可。對(duì)于通過(guò) RPC 調(diào)用發(fā)來(lái)的簡(jiǎn)單 SQL 文本,則通過(guò)雜湊該 SQL 文本來(lái)建立緩存鍵。此外,該鍵還要反映一定的狀態(tài)信息,如某些 ANSI 設(shè)置。使所有 ANSI 設(shè)置為 ON 的連接和另一個(gè)使所有 ANSI 設(shè)置為 OFF 的連接,即使它們來(lái)自相同的查詢,也不能使用相同的計(jì)劃。處理過(guò)程是不同的。例如,如果一個(gè)連接把 concat_null_yields_null 設(shè)置為 ON,另一個(gè)把 concat_null_yields_null 設(shè)置為 OFF 的連接,即使它們執(zhí)行的是完全相同的 SQL 文本,但所產(chǎn)生的結(jié)果則完全不同。這樣,SQL Server 可能需要在緩存中保存計(jì)劃的多個(gè)版本,每個(gè)版本對(duì)應(yīng)于一個(gè)不同的 ANSI 設(shè)置組合。啟用的選項(xiàng)設(shè)置是鍵的一部分,而鍵字是使用這種緩存處理機(jī)制檢查對(duì)象的核心,因此 SQL Server 建立這種鍵并用來(lái)檢查緩存。如果在緩存中沒(méi)有發(fā)現(xiàn)該計(jì)劃,則 SQL Server 會(huì)按照前面介紹的方式編譯該計(jì)劃,并把該計(jì)劃與鍵一起存入緩存中。SQL Server 還需要確定該命令是否是準(zhǔn)備操作,這意味著該計(jì)劃應(yīng)該只編譯但不執(zhí)行。如果是準(zhǔn)備操作,則 SQL Server 會(huì)給客戶機(jī)返回一個(gè)句柄,供客戶機(jī)在以后檢索并執(zhí)行該計(jì)劃。如果不是一個(gè)準(zhǔn)備操作,則 SQL Server 提取并執(zhí)行該計(jì)劃,就像最初從緩存中找到該計(jì)劃一樣。準(zhǔn)備/執(zhí)行模型為緩存管理增加了復(fù)雜因素。預(yù)備給出了今后能夠執(zhí)行該計(jì)劃的句柄。應(yīng)用程序可以在幾小時(shí)或幾天之內(nèi)保持該句柄是激活的,以定期執(zhí)行計(jì)劃。即使需要在緩存中為更多的活動(dòng)計(jì)劃或數(shù)據(jù)頁(yè)面騰出空間,也不能使該句柄無(wú)效。SQL Server 實(shí)際所做的就是將計(jì)劃放入緩存,此外還從預(yù)備操作中將 SQL 保存到更加緊湊的空間。如果空間緊張,則可按前述的方式釋放計(jì)劃所占用的空間,但仍有 SQL 的副本準(zhǔn)備著。如果客戶機(jī)要執(zhí)行預(yù)備的 SQL,但在緩存中沒(méi)有找到計(jì)劃,則 SQL Server 能夠檢索到該文本并編譯它,再將它放回緩存中。這樣,緩存中的 16 千字節(jié) (KB) 或更多的頁(yè)面用來(lái)保存可重用的計(jì)劃,而長(zhǎng)期占用的空間或許是存儲(chǔ)在其他處的 SQL 代碼的 100 或 200 字節(jié)。處理來(lái)自客戶機(jī)的語(yǔ)句時(shí)的另一種情況是,查詢是作為 SQL 語(yǔ)言事件出現(xiàn)的。除了一點(diǎn)以外,此流程并無(wú)太大的差異。在這種情況下,SQL Server 試圖使用稱(chēng)為自動(dòng)參數(shù)化的技術(shù)。SQL 文本與自動(dòng)參數(shù)化模板相匹配。自動(dòng)參數(shù)化是個(gè)棘手的問(wèn)題,因此,過(guò)去一直能夠利用共享的 SQL 的其他數(shù)據(jù)庫(kù)管理產(chǎn)品, 一般并沒(méi)有提供這一選項(xiàng)。隨之而來(lái)的問(wèn)題是,如果 SQL Server 自動(dòng)地參數(shù)化每個(gè)查詢,那么對(duì)于隨后提交的某些特定值而言,這些查詢中的某些(或絕大多數(shù))將獲得非常糟糕的計(jì)劃。在程序員將參數(shù)標(biāo)記放在代碼之中的場(chǎng)合下,其假定是程序員知道所期望的值的范圍,并愿意接受 SQL Server 提供的計(jì)劃。但當(dāng)程序員實(shí)際補(bǔ)充一個(gè)特定的值,并且 SQL Server 決定將該值當(dāng)做一個(gè)可變的參數(shù)來(lái)對(duì)待時(shí),所產(chǎn)生的任何適合于某個(gè)值的計(jì)劃可能不適合于后續(xù)的值。利用存儲(chǔ)過(guò)程,通過(guò)在過(guò)程中放入 WITH RECOMPILE 選項(xiàng),程序員可以強(qiáng)制產(chǎn)生新的計(jì)劃。利用自動(dòng)參數(shù)化,程序員無(wú)法指出必須為每一個(gè)新值開(kāi)發(fā)新的計(jì)劃。當(dāng) SQL Server 處理自動(dòng)參數(shù)化時(shí),它是非常保守的。被安全地自動(dòng)參數(shù)化的查詢有一個(gè)模板,并且只有匹配模板的查詢才能應(yīng)用自動(dòng)參數(shù)化。例如,假設(shè)有這樣一個(gè)查詢,其中包含帶有等于操作符、但沒(méi)有連接的 WHERE 子句,WHERE 子句中的列帶有唯一的索引。SQL Server 知道絕對(duì)不會(huì)返回一行以上,而且計(jì)劃將總是使用那個(gè)唯一的索引。SQL Server 絕對(duì)不會(huì)考慮掃描,實(shí)際值絕對(duì)不會(huì)以任何方式改變計(jì)劃。對(duì)于自動(dòng)參數(shù)化而言,這種查詢是安全的。如果查詢匹配自動(dòng)參數(shù)化模板,則 SQL Server 自動(dòng)用參數(shù)標(biāo)記(例如 @p1、@p2)代替文字,并且這就是我們發(fā)送到服務(wù)器的內(nèi)容,正如它是 sp_executesql 調(diào)用一樣。如果 SQL Server 認(rèn)為該查詢對(duì)自動(dòng)參數(shù)化并不安全,則客戶機(jī)將向 SQL Server 發(fā)送文字的 SQL 文本,以此作為特定的 SQL。圖 11 顯示客戶機(jī)向 SQL Server 發(fā)送請(qǐng)求時(shí)的處理流程。圖 11. 處理客戶機(jī)的 SQL編譯現(xiàn)在讓我們更詳細(xì)地討論一下編譯和優(yōu)化。在編譯過(guò)程中,SQL Server 分析語(yǔ)句,并創(chuàng)建所謂的次序樹(shù),即語(yǔ)句的內(nèi)部表述。這是 SQL Server 6.5 實(shí)際保留在 SQL Server 7.0 中的幾個(gè)數(shù)據(jù)結(jié)構(gòu)之一。該次序樹(shù)是正常化的。正常化程序的主要功能是執(zhí)行綁定。綁定包括檢驗(yàn)表和列的存在,以及裝載有關(guān)表和列的元數(shù)據(jù)。有關(guān)必需的(隱含的)轉(zhuǎn)換信息也附加在次序樹(shù)上,例如,如果查詢?cè)噲D向數(shù)字值添加整數(shù) 10,則 SQL Server 將向該樹(shù)插入隱含的轉(zhuǎn)換。正常化還用視圖的定義代替對(duì)該視圖的引用。最后,正常化執(zhí)行一些基于語(yǔ)法的優(yōu)化。如果該語(yǔ)句是傳統(tǒng)的 SQL 語(yǔ)句,則 SQL Server 從關(guān)于該查詢的次序樹(shù)中提取信息,并創(chuàng)建稱(chēng)為查詢圖表的特殊結(jié)構(gòu),設(shè)置查詢圖表是為了使優(yōu)化器工作非常有效。然后優(yōu)化該查詢圖表,一個(gè)計(jì)劃就產(chǎn)生了。圖 12 顯示編譯過(guò)程流程。圖 12. 編譯優(yōu)化SQL Server 優(yōu)化器其實(shí)是由獨(dú)立的段組成的。第一段是一個(gè)非基于成本的優(yōu)化器,稱(chēng)為瑣細(xì)計(jì)劃優(yōu)化。瑣細(xì)計(jì)劃優(yōu)化的完整概念是,當(dāng) SQL 語(yǔ)句確實(shí)只有一個(gè)可變計(jì)劃時(shí),基于成本的優(yōu)化太昂貴了。最好的例子是,帶 VALUES 子句的 INSERT 語(yǔ)句組成的查詢。它只可能有一個(gè)計(jì)劃。另一個(gè)例子是,所有的列都在唯一的封面索引(且沒(méi)有其他列的索引)中的 SELECT 語(yǔ)句。這兩例中,SQL Server 只要簡(jiǎn)單地生成一個(gè)計(jì)劃,用不著在多個(gè)計(jì)劃選一個(gè)更好的方案。瑣細(xì)計(jì)劃優(yōu)化器可找到真正顯而易見(jiàn)的計(jì)劃,而且通常非常便宜。所以,最簡(jiǎn)單的查詢?cè)谔幚淼那捌诰挖呌诒磺宄瑑?yōu)化器不花很多時(shí)間來(lái)搜索一個(gè)好計(jì)劃。這是好事,因?yàn)殡S著 SQL Server 將雜湊連接、合并連接和索引相交增加到其處理技術(shù)列表上, SQL Server 7.0 版上的潛在計(jì)劃數(shù)呈天文數(shù)字增長(zhǎng)。如果瑣細(xì)計(jì)劃優(yōu)化器不能找到一個(gè)計(jì)劃,SQL Server 便進(jìn)入優(yōu)化的下一部分,稱(chēng)為簡(jiǎn)化。簡(jiǎn)化是查詢本身的語(yǔ)法變換,尋找可交換的特性和可重新排列的運(yùn)算。SQL Server 可進(jìn)行常數(shù)合并,以及無(wú)需考慮成本或分析索引是什么但能得出更有效查詢的其他運(yùn)算。SQL Server 然后上載關(guān)于索引和列的統(tǒng)計(jì)信息,并輸入優(yōu)化的最后的主要部分,即基于成本的優(yōu)化器。基于成本的優(yōu)化有三個(gè)階段。第一個(gè)基于成本的階段,稱(chēng)為交易處理階段,查找簡(jiǎn)單請(qǐng)求的計(jì)劃,即典型的交易處理系統(tǒng)。這些請(qǐng)求一般比由瑣細(xì)計(jì)劃優(yōu)化器處理的那些請(qǐng)求要復(fù)雜些,并要求比較眾多計(jì)劃查找出成本最低的計(jì)劃。當(dāng)交易處理階段完成時(shí),SQL Server 便將找到的成本最低的計(jì)劃與內(nèi)部閾值進(jìn)行比較。閾值用于決定是否要求進(jìn)一步的優(yōu)化。如果計(jì)劃成本比閾值低,那么,進(jìn)行附加優(yōu)化比只執(zhí)行已找到的計(jì)劃成本要高。所以,SQL Server 不做進(jìn)一步優(yōu)化,并使用交易處理階段找到的計(jì)劃。如果交易處理階段找到的計(jì)劃,仍比該階段的閾值貴,SQL Server 便進(jìn)入第二個(gè)階段。這個(gè)階段有時(shí)稱(chēng)為 QuickPlan 階段。QuickPlan 階段擴(kuò)大搜索范圍來(lái)尋找一個(gè)好計(jì)劃,包括選擇好的、適度復(fù)雜的查詢。QuickPlan 檢查可能的計(jì)劃范圍,完成之后,將最佳計(jì)劃的成本與第二個(gè)閾值進(jìn)行比較。因?yàn)樵诮灰滋幚黼A段,如果發(fā)現(xiàn)了一個(gè)成本比閾值低的計(jì)劃,優(yōu)化便終止,并使用那個(gè)計(jì)劃。一般來(lái)說(shuō),SQL Server 6.5 版中已有的查詢的計(jì)劃,在 SQL Server 7.0 版中也應(yīng)當(dāng)是最佳的,這個(gè)計(jì)劃將要么被瑣細(xì)計(jì)劃優(yōu)化器找到,要么被基于成本的優(yōu)化的頭兩個(gè)階段中的一個(gè)發(fā)現(xiàn)。這些規(guī)則被有意地組織起來(lái)以達(dá)到這個(gè)目的。這個(gè)計(jì)劃將很可能由使用單一的索引和使用嵌套循環(huán)聯(lián)合組成。優(yōu)化的最后階段,稱(chēng)為完全優(yōu)化,旨在對(duì)復(fù)雜和非常復(fù)雜的查詢產(chǎn)生一個(gè)好計(jì)劃。對(duì)復(fù)雜的查詢來(lái)說(shuō),QuickPlan 產(chǎn)生的計(jì)劃,經(jīng)常被認(rèn)為比繼續(xù)搜索一個(gè)更好的計(jì)劃要昂貴得多,而完全優(yōu)化將被執(zhí)行。在完全優(yōu)化中,實(shí)際上有兩個(gè)適用的獨(dú)立選擇。如果 QuickPlan 階段產(chǎn)生的最佳成本比“并行成本閾值”的配置值要高,并且如果服務(wù)器是一個(gè)多處理器機(jī)器,那么優(yōu)化器的最后階段將涉及查找一個(gè)能在多個(gè)處理器上并行運(yùn)行的計(jì)劃。如果 QuickPlan 階段的最佳計(jì)劃的成本比配置的“并行成本閾值”低,那么,優(yōu)化器將只考慮串行計(jì)劃。完全優(yōu)化階段能執(zhí)行各種可能性,而且很耗時(shí),因?yàn)樵谶@最后階段必須找到一個(gè)計(jì)劃。優(yōu)化器仍可能沒(méi)有檢查每個(gè)可得到的計(jì)劃,因?yàn)樗鼘⑷魏螡撛诘挠?jì)劃成本與優(yōu)化中得出此結(jié)果的成本進(jìn)行比較,并且它估算繼續(xù)試用不同優(yōu)化的可能成本。在某些情況下,優(yōu)化器可能認(rèn)為,使用現(xiàn)有的計(jì)劃比繼續(xù)查找更優(yōu)方案還要便宜,而且支付繼續(xù)優(yōu)化的附加編譯成本將不具備高的成本效率比。在這最后階段處理的各種查詢的計(jì)劃一般只使用一次,所以,幾乎沒(méi)有這樣的機(jī)會(huì):為編譯和優(yōu)化所付出的額外代價(jià),會(huì)在后續(xù)執(zhí)行的計(jì)劃重用中一次結(jié)清。那些后續(xù)執(zhí)行很可能不會(huì)發(fā)生。找到一個(gè)計(jì)劃后,該計(jì)劃便變?yōu)閮?yōu)化器的輸出,然后 SQL Server 在執(zhí)行該計(jì)劃之前,遍歷前面已討論過(guò)的全部緩存機(jī)制。您應(yīng)該意識(shí)到,如果完全優(yōu)化階段產(chǎn)生了該查詢的并行計(jì)劃,并不一定意味著該計(jì)劃將在多個(gè)處理器上執(zhí)行。如果機(jī)器很忙,而且不支持在多個(gè) CPU 上運(yùn)行單一的查詢,該計(jì)劃則使用單一的處理器。圖 13 顯示了優(yōu)化器的處理流程。圖 13. 優(yōu)化執(zhí)行查詢處理的最后一步是執(zhí)行。除了這一小段外,我們不會(huì)再討論執(zhí)行的詳細(xì)過(guò)程。執(zhí)行引擎采用優(yōu)化器生成的計(jì)劃,并執(zhí)行之。處理實(shí)際執(zhí)行以外,執(zhí)行引擎還為要運(yùn)行的處理器調(diào)度線程,并提供線程間的通信。摘要如前所述,SQL Server 的內(nèi)部機(jī)制與結(jié)構(gòu)是一個(gè)非常大的主題,遠(yuǎn)遠(yuǎn)超過(guò)了我們能在本文中提供的內(nèi)容。我們重在直接介紹 SQL Server 與客戶機(jī)的交互方式,以及 SQL Server 關(guān)系引擎如何處理來(lái)自客戶機(jī)的請(qǐng)求。我們希望,在了解 SQL Server 如何處理查詢,以及如何和何時(shí)編譯或重新編譯它們之后,您就能利用 SQL Server 7.0 的功能和技巧編寫(xiě)出更好的應(yīng)用程序。
標(biāo)簽:
Sql Server
數(shù)據(jù)庫(kù)
排行榜
