最大的網站源碼資源下載站,
簡介所有 sql server 數據訪問應用程序編程接口 (api) 都提供了一個抽象來表示會話和會話中的請求。sql server 2000 以及更早的版本限制編程模型,它要求任何時候一個給定的會話中最多只能有一個待定的請求。有幾個替代辦法被用來解決這種限制,在這些替代辦法中,最常見的可能就是服務器端光標。sql server 2005 實現了 multiple active result set (mars),它解除了這個約束。本文介紹了 mars 的設計、結構和語義變更,以及為了從這些改進中得到最大收益,應用程序應當注意什么。
sql server 2000 data access recap目前支持用于構建 sql server 應用程序的主要數據訪問 api 是 odbc、ole-db、ado 和 sqlclient .net provider。1它們全部都提供一個抽象來表示一個指向服務器的已建立連接,同時提供另外一個抽象來表示在這個連接之下執行的請求。例如,sqlclient 使用 sqlconnection 和 sqlcommand 對象,而 odbc 則使用 sql_handle_dbc 類型和 sql_handle_stmt 類型的句柄。
所有發送給 sql server 的執行請求都是幾乎以下兩種形式之一:1) 一組 t-sql 語句,通常也稱為批處理,或者 2) 存儲過程或函數的名稱,加上參數值(如果合適)。請注意,提交一個 select 或 dml 語句給服務器是一個單語句批處理,這是第一類請求的特例。
在任何一種情況下,sql server 都會重申批處理或存儲過程中包含的語句,然后執行這些語句。語句可能會生成結果,也可能不生成結果,并且語句可能會向調用者返回附加信息,也可能不返回。
結果主要是由 select 和 fetch 生成的。sql server 通過將結果返回給調用者來執行 select 語句。這意味著,在查詢執行引擎產生行的同時,這些行會被寫入網絡。更確切地說,所產生的這些行會被復制到預先保留的網絡緩沖區中,然后緩沖區會被返回給調用者。網絡寫入操作會成功,并在客戶端驅動程序從網絡中讀取時釋放已用過的緩沖區。如果客戶端沒有消耗結果,在相同點上的網絡寫入操作將會被阻止,服務器中的網絡緩沖區將會被填滿,執行就會被掛起,等待狀態和執行線程,直到客戶端驅動程序捕獲讀取。這種產生結果和檢索結果的模式通常被稱為“默認結果集”,更正式的名稱則是“流水游標”。
附加信息也可能以其他方式(可能沒有結果返回方式那么明顯)被返回給調用者。這種情況包括錯誤、警告和信息性消息。它們或者通過 print 和 raiserror 語句顯式返回,或者通過語句執行期間產生的警告和錯誤隱式返回。同樣地,當 nocount 設置選項被設置為 off 時,sql server 會對每個已執行的語句發送一個“done row count”標記。這種附加信息也可能導致網絡寫入緩沖區被填滿和執行被掛起。
這種背景使我們可以理解 sql server 2000 以及更早版本在支持每連接多個待定請求時的一些編程模型限制。
“連接繁忙”對于本文中的示例,我們將假定以下這樣一個簡單的情境:一個松散連接的清單處理系統使用“operations”表作為隊列來接收來自其他組件的請求:
operations
已處理 | 位 | |
operation_id | int | 主鍵 |
operation_code | char(1) | 'd' – decrease inventory 'i' – increase inventory 'r' – reserve inventory |
product_id | uniqueidentifier | |
數量 | bigint |
我們假定這個組件管理著不同產品線和供應商的清單,而 product_id 則確定要使用哪個服務器和數據庫以及如何執行請求的操作。(也就是說,假定不可能寫入一些成組操作來處理隊列中的所有請求)。
這個組件在一個插入到表中的松散處理請求中開始運行,并在成功完成指定操作時將這些請求標記為已處理。
偽代碼類似如下所示:
while (1)
{
get all messages currently available in operations table;
for each message retrieved
{
processmessage();
mark the message as processed;
}
}前面一個使用 odbc 的方法類似如下所示(忽略了一些細節和錯誤處理):
sqlallochandle(sql_handle_stmt, hdbc1, &hstmt1);
sqlallochandle(sql_handle_stmt, hdbc1, &hstmt2);
while (true)
{
sqlexecdirect(hstmt1, (sqltchar*)"select operation_id,
operation_code, product_id, quantity from dbo.operations
where processed=0", sql_nts);
while (sql_error!=sqlfetch(hstmt1))
{
processoperation(hstmt1);
sqlprepare(hstmt2,
(sqltchar*)"update dbo.operations set processed=1
where operation_id=?", sql_nts);
sqlbindparameter(hstmt2, 1, sql_param_input, sql_c_slong,
sql_integer, 0, 0, &opid, 0, 0);
sqlexecute(hstmt2);
}
}但是,嘗試執行 hstmt2 會導致:
[microsoft][odbc sql server driver]connection is busy with results for another hstmt.使用 sqlclient 寫入到 microsoft visual c# 中的相同邏輯類似如下所示:
sqlcommand cmd = conn.createcommand();
sqlcommand cmd2 = conn.createcommand();
cmd.commandtext= "select operation_id, operation_code, product_id, quantity
from dbo.operations where processed=0";
cmd2.commandtext="update dbo.operations set processed=1
where [email protected]_id";
sqlparameter opid=cmd2.parameters.add("@operation_id", sqldbtype.int);
reader=cmd.executereader();
while (reader.read())
{
processoperation();
opid.value=reader.getint32(0); // operation_id
cmd2.executenonquery();
}同樣地,嘗試執行此邏輯會導致:
invalidoperationexception, there is already an open datareader associated with this connection which must be closed first.這些錯誤是缺少 mars 的最直接證明;在任何時候,一個給定的 sql server 連接之下最多只能有一個待定請求。
我有意忽略了 oledb,因為它會產生略微有點不同的行為。
“我已經有 mars”由于以前版本的 sqloledb 客戶端驅動程序嘗試模擬 mars,因此在 mars 方面應特殊對待 ole-db。然而,這種嘗試具有很多缺陷。上述示例的一個 oledb 片斷類似如下所示(還是那樣,沒有錯誤處理):
pidbcreatecommand->createcommand(null, iid_icommandtext, (iunknown**) &picommandtext);
pidbcreatecommand->createcommand(null, iid_icommandtext, (iunknown**) &picommandtext2);
picommandtext->setcommandtext(dbguid_dbsql,
olestr("select operation_id, operation_code, product_id, quantity
from dbo.operations where processed=0"));
picommandtext2->setcommandtext(dbguid_dbsql,olestr("update dbo.operations
set processed=1 where operation_id=?"));
//execute the command
picommandtext->execute(null, iid_irowset, null, &crowsaffected, (iunknown**) &pirowset);
...
processoperation();
...
//execute the command 2
picommandtext2->execute(null, iid_irowset, null, &crowsaffected, null);真有趣,這段代碼成功運行了,并且看來好像可以執行我想要執行的任務。如果說缺少 mars 是一個基本引擎限制,它是怎么成功執行的?在經過一些檢查之后,我們看到,sqloledb 驅動程序在 covers 下面產生了一個新連接,并在其下執行 command2。這意味著我已經有了 mars,對嗎?不完全準確。
數據庫引擎中并沒有特別執行什么任務,以便讓這兩個連接可以很好地協同工作。它們只是兩個連接,因此它們具有不同的執行環境,并且更為重要的是,它們之間可能會產生沖突。sqloledb 會防止在某個會話處于顯式事務中時產生新連接,這是因為存在一個對 itransactionlocal->starttransaction 的顯式調用,或是因為 dtc 事務中已登記了該會話。在這種情況下,執行命令 2 將會失敗。
但是,如果其中一個命令通過 tsql 開始一個事務,則 sqloledb 就不會知道這種狀況,并且會允許創建其他連接。不幸的是,兩個不同的命令(表面上是相同會話的一部分)會在不同的事務下結束運行。
提升會話的孤立級別 - 比如說 repeatable read - 會使上述應用程序片斷停留在一個不好的狀態中。命令 1 運行查詢,檢索操作表中所有未處理的行。由于孤立級別較高,因此要等到事務結束時才會解除鎖定。由于沒有顯式事務正在使用中,因此語句運行在 auto-commit 模式下,要等到語句結束時才會解除鎖定。如果對某個特定行設置了鎖定,而此時命令 2 修改該行,則會出現一個包含客戶端代碼的死鎖,而應用程序會被掛起。

為了讓結果變得更加難以預測,命令 1 中的語句將會在所生成的最后一行被復制到服務器的網絡緩沖區中時完成,而不是在客戶端應用程序讀取最后一行時完成。這么做的含義是,對于較小的行集來說,上述代碼片斷將會成功執行,但如果數據卷太大,以至于服務器無法在執行命令 2 之前完成執行命令 1,則上述代碼片斷就會失敗。一點也不奇怪,在開發環境中應用程序可以照常運行,但它在生產環境中部署時則會神奇地掛起。
從底線上說來,它可能不是可依賴 sqloledb 的類似 mars 行為的最佳應用程序設計。如果您決定使用它,請注意其他隱式連接以及這可能會帶來的語義含義。
那我應當怎么做?既然 sql server 2000 及更早版本中沒有 mars,那我怎么讓我的應用程序工作?根據應用程序要求,有時需要顯式使用多個連接。在很多情況下,使用服務器端光標會很方便。
服務器端光標為應用程序提供了一種使用一行或多個行的一個小段代碼來消耗查詢結果的方法。不再詳細了解不同類型和選項,一般說來,一個光標就代表著查詢結果。它維護著內存中的和磁盤上的狀態,這樣就可以根據需要返回查詢結果。
在通常的使用模式下,會在聲明指定查詢之前先聲明光標。一個提取操作會執行,以便從結果集中檢索每一行或多個行。一旦這些行被消耗,或是不再需要結果集,光標就會被除去,從而釋放出相關的服務器端資源。在本討論中,要說明的最重要一點是,在兩個提取操作之間沒有代表光標執行任何代碼。服務器上有保留狀態,但是沒有待定的工作。
odbc 和 ole-db 會暴露屬性,這樣查詢請求就會被映射為使用服務器端光標。
更改上述 odbc 示例可以讓第一個命令使用服務器端光標,從而使得應用程序情境成功執行并按照預期方式工作。一行更改:
sqlsetstmtattr(hstmt1, sql_attr_cursor_type, (sqlpointer)sql_cursor_dynamic, 0);類似的更改也可以應用于 ole-db,以幫助避免隱式產生連接和相關缺陷。
到此時為止,我們已經看到了服務器端光標是如何幫助解決缺少 mars 的問題的。恰如我們將在后面介紹的,并不是說提供了 mars 就不需要使用光標,也不是說所有使用光標的應用程序都必須改為使用 mars。
自然而然,下一個問題就是“為什么不總是使用服務器端光標來替代看來限制更多的默認結果集”?1) 并不是所有有效的 sql 上都支持所有光標類型,2) 光標一次只能操作一個 select 語句以及 3) 性能。
由于結果在可用時被“推出”的方式,默認結果集會比服務器端光標執行得更好。另一方面,對于每次提取操作光標都會要求一個到服務器的往返。
總之,我們可以說 mars 的所有目的就是為了解除“連接繁忙”,這對于編程模型來說是一個重大的改進。
事務和執行環境重新捕獲sql server 2000 及更早版本中的會話可以處于以下任何一種可能的狀態下:
| • | 沒有活動事務處于狀態:這通常稱為 auto-commit 模式,它意味著會話中執行的所有語句都運行在單獨的事務中。 |
| • | 本地事務處于活動狀態:會話中執行的所有語句都運行在通過顯式 tsql begin transaction 命令或 implicit_transaction on 設置而啟動的事務下。 |
| • | 已登記:會話已在其他會話或其他事務管理器所擁有的事務中登記。前者是通過使用綁定會話 (sp_getbindtoken / sp_bindsession) 來實現,后者是通過在 dtc 事務中登記來實現的。 |
由于 mars 不可用,因此在任何給定時間,在相同事務下不可能有多個語句正在執行。即使是在綁定會話或 dtc 情況下,基礎結構也會確保在事務環境中,一個會話中一次只能發生一項工作。
在 odbc 和 ole-db/ado 中,在會話中啟動事務之后,所有后續請求都會在可產生一個會話范圍的事務環境這樣的事務之下執行。
在 sqlclient 中,這個模型看來不太直觀。我們提供了一個 api 以從連接 (sqlconnection) 對象開始一個事務,這個連接對象返回一個代表新創建事務的抽象 (sqltransaction)。在一種看來比較隨意的方式下,一旦事務被創建,如果沒有將請求顯式與事務上下文建立關聯,就不能執行任何請求。sqlclient api 提供了一種編程模型,在這種編程模型下,事務不必在全局范圍內作用于連接,多個事務可以在一個會話下創建,而請求可以被自由映射到任何活動事務。盡管 sql server 2005 不支持每個連接多個活動事務,但是編程模型已經可以適應這樣一種未來的增強功能。
在 mars 下,在一個給定的會話下可以有多個請求處于待定狀態,從而,對于運行在相同事務下的請求之間發生的沖突要求適當的語義定義。
類似于 sql server 2000,請求對執行環境所做的任何更改都會成為全局會話更改。執行環境的確切含義是什么?它包含 set 選項值(例如 arithabort)、當前數據庫上下文、執行狀態變量(例如 @@error)、光標和臨時表。
在申請更改當前數據庫的請求中執行一個 use 語句會導致所有后續請求都在新上下文中執行。類似地,更改批處理中 set 選項的值則意味著所有后續執行都會在新設置值之下運行。
mars 解除了一個會話下最多只能有一個待定請求的限制,在保留后向兼容性的同時,它為執行環境中的更改定義了更為精細的語義。
multiple active result set – mars此時您對 mars 是什么可能已經有了一個模糊的概念。簡單說來,它就是能夠讓多個待定請求處于一個 sql server 連接之下的能力。多數情況下,這可以直接理解為在相同會話內部執行其他操作的同時存在多個默認結果集(流水游標)的能力。
分清 mars 不是什么也很重要:
| • | 平行執行:盡管 mars 可以在一個連接之下提交多個請求,但這并不意味著這些請求會在服務器內平行執行。mars 會在連接中的多個存在的請求之間復合執行線程,在預先定義好的點上交叉執行。 |
| • | 光標替換:前面說過,在一些情況下光標可以作為缺少 mars 的合適解決辦法;它對于遷移這些情境使用 mars 可能比較有效。但是,這并不意味著當前使用的所有光標都應當轉為 mars。 |
默認情況下,在對 sql server 2005 使用支持 mars 的客戶端驅動程序時,前面一節中包含的所有代碼片斷都“可以工作”。在支持 mars 的連接下,上面所說的死鎖情境(應用程序被掛起)現在也可以成功運行了。
支持 mars 的客戶端驅動程序如下:
| • | sql native client 中包含的 sqlodbc 驅動程序 |
| • | sql native client 中包含的 sqloledb 驅動程序 |
| • | .net framework 版本 2.0 中包含的 sqlclient .net data provider |
默認情況下,這些驅動程序將會建立支持 mars 的連接。如果出于某種原因,您想要建立保留下級驅動程序行為的連接,每個 api 都提供了一個請求非 mars 連接的選項。
sqlclient 提供了 multipleactiveresultsets 連接字符串選項。如果此項設置為 false,則不允許對會話使用 mars。如果此項設置為 true 或忽略此項,則可以使用 mars。
同樣地,odbc 提供了一個 sql_copt_ss_mars_enabled 連接選項,而 ole-db 提供了一個 ssprop_init_marsconnection 選項。再強調一次,僅當需要禁用 mars 時(因為默認是啟用 mars 的)才需要使用這些選項。
注意只有 sql native client 版的 odbc 和 oledb 提供程序才提供 mars。舊版的提供程序還沒有增強到支持 mars。不用說,新版驅動程序在連接到 sql server 2000 或 更早版本的服務器時也不支持 mars。
交叉執行在更深層含義上,mars 是關于在一個連接內啟用多個請求的交叉執行。也就是說,允許成批執行,并且在執行中還允許執行其他請求。請注意,mars 是在交叉執行而不是平行執行的意義上定義的。
mars 結構允許多個批處理以交叉方式執行,盡管執行只能在定義良好的點切換。事實上,大多數語句都必須運行在一個批處理中。只有以下語句可以在完成之前交叉執行:
| • | select |
| • | fetch |
| • | readtext |
| • | receive |
| • | bulk insert(或 bcp 接口) |
| • | 異步光標填充 |
它的確切含義是什么?它的含義是必須等到作為存儲過程或批處理的一部分執行的不在此列表中的任何其他語句運行完畢,執行才可以被切換到其他 mars。
作為示例,可以設想有一個提交了較長的運行中 dml 語句的批處理;假定就是一個 update 語句(它會影響幾十萬個記錄)。如果正在執行這個語句時,提交了第二個批處理,那么它的執行就要等到 update 語句完成時才會開始。
另一方面,如果 select 語句是第一個提交的語句,那么 update 語句就可以在 select 語句中運行。但是,在 dml 操作完成之前,不會為 select 語句生成任何新行。
這再一次說明了 mars 交叉處理而不是平行處理請求。交叉處理不會受到請求語句位置的影響,無論它是位于批處理中、位于 exec 語句中還是位于存儲過程中。
注意只要開始生成行,receive 語句就可以交叉處理。對于在 waitfor 子句中執行的 receive 語句,當該語句處于等待狀態時,它是不可交叉處理的。
注意只有在 set xact_abort on 之下執行,并且插入操作的表目標中沒有定義任何觸發器,或者指定了不引發觸發器的選項,bulk 操作才可以交叉執行。receive 只有在 xact_abort 設置為 on 是才可以交叉執行。
注意執行托管代碼時,無法交叉執行用任何 .net 語言編寫的存儲過程。如果使用了 inproc 數據提供程序,那么已經執行的批處理就要受到語句的交叉執行和原子執行的普通規則約束。
mars 性能和成本考慮上面說過,mars 是 sql server 數據訪問 api 的默認處理模式。它的執行模式 – 不像服務器端光標 – 支持大批量語句,并且還可以調用存儲過程和動態 sql 操作。對于在其中生成結果的“流水游標”模式,默認結果集 (mars) 的性能會優于服務器端光標。
盡管其中還有某種很好的打印。默認結果集會“盡可能快地”生成結果。只要客戶端驅動程序或應用程序正在消耗所生成的結果,就會產生這種情況。如果應用程序沒有消耗結果,服務器端緩沖區會被填滿,處理就會被掛起,直到結果被消耗。當執行被掛起時,很多資源會被占用:數據和架構被鎖定,服務器工作線程被占用(包括堆棧和其他相關內存)。請注意,這種情況不僅限于 mars;當一個請求生成了沒有被快速消耗的默認結果集時,它代表 sql server 2000 和更早版本所導致的開銷是相同的。mars 并不意味著在流水游標開銷方面的改進。
對于服務器端光標,并不存在這種資源占用的情況。在某種程度上有些關系,根據請求的光標類型,可能可以使用默認結果集無法使用的其他語義,即結果的可滾動性和可更新性。
由于上面已經介紹了請求的處理過程,現在我們直接推斷從 sql server(假定不要求可滾動性和可更新性)檢索結果的用法指南:如果應用程序能夠快速地消耗結果,那么 mars 下的默認結果集就可以提供最好的性能和開銷特點。如果應用程序消耗結果的速度比較緩慢,則建議使用服務器端光標,尤其是 fast_forward 光標。
在大多數情況下,適合使用 mars 默認結果集。那么有什么緩慢消耗結果的示例呢?想像一下執行返回結果的批處理或存儲過程的應用程序,行的消耗取決于數據庫外部操作(例如用戶輸入、ui 操作、與其他任務同步,等等)的完成。一般說來,請求長時間處于待定狀態會影響應用程序和 sql server 的可伸縮性。
事務語義mars 的引入改變了數據庫引擎內部很多現有的假設,包括事務語義和一個事務內的并發操作。
盡管 ole-db 在會話中有一個事務處于活動狀態時會禁止隱式產生連接,并且 odbc 也經常使用“連接繁忙”來拒絕其他請求,但在支持 mars 的世界里,這些事情都可以成功做到。如果會話中有一個活動事務,所有新的請求都會運行在指定事務之下;如果會話中沒有活動事務,那么每個批處理都會運行在 auto commit 模式下,這意味著每個執行的語句都運行在它自己的事務之下。
sqlclient 托管提供程序的模型更為明顯。specific sqlcommands 需要關聯給定的 sqltransaction 對象,以便指定特定請求運行在哪個事務之下。
一般說來,事務確定了多個用戶之間的隔離。但是在 mars 下,多個請求可以運行在相同的事務之下,這使得請求之間彼此兼容,并避免產生“重新捕獲”一節中所述的死鎖。但是,如果在相同事務下有兩個請求之間存在沖突操作,又會怎么樣呢?
有幾種可能的情況,下面分別說明:
| • | 一個請求正在讀取某些結果(比如說 select、fetch 或 readtext)。另外一個請求修改了正在讀取的數據(比如說 dml 操作)。在這種情況下,盡管更改操作成功,但讀取操作獨立于更改,因此所有讀取的數據看來就是讀取操作開始時的狀態。請注意,只有讀取操作先于修改操作開始,才有可能產生這種情況。如果 dml 語句首先開始執行,那么讀取操作就要等到后面才執行,這樣就可以看到所有更改了。 |
| • | 兩個請求嘗試修改相同數據。對于語句的原子數規則,必須在 dml 語句運行完畢后才能運行其他語句。因此,嘗試修改數據的兩個批處理永遠不會交叉執行。請求會按照順序執行,而結果會反應執行的順序。請注意,如果客戶端應用程序是多線程的,這可能會產生非確定性行為。 |
| • | 一個請求正在讀取數據(比如說 select、fetch 或 readtext),但任一基本對象的架構被修改了(比如說 dml 操作)。在這種情況下,ddl 操作會失敗,因為在相同的事務下存在沖突的待定請求。請注意,這種行為也適用于在 receive 語句產生結果時嘗試更改服務代理隊列架構的情況。 |
| • | 重疊操作發生在被批量插入的表上。bulk insert(或 bcp、irowsetfastload)可以非原子運行,即可以與其他語句交叉執行。但是,在 bulk insert 的對象目標上不能并發執行任何 ddl、dml 或讀取操作。這種情況下會產生一個錯誤,因為在相同的事務下存在沖突的待定請求。 |
請記住,上述情況僅適用于請求運行在相同事務下。對于運行在不同事務下的請求,則適用常規的鎖定、阻止或隔離語義。
順便說一下,事務框架實現的 mars 下的事務語義現在也用于綁定會話和 dtc。這意味著盡管它以前只能在沒有請求處于待定狀態時更改事務上下文,現在則能夠在支持非原子運行的一組相同語句期間切換上下文。同樣地,在 dml、ddl 和其他必須原子運行的語句執行時,不能切換事務上下文。
注意如果在給定事務下存在待定的請求,則嘗試提交事務將會失敗。
保存點事務保存點通常用于允許在一個事務內部部分回滾。通常,應用程序開始一個事務,設置保存點,進行某些工作,然后如果工作成功則繼續,不成功的話則回滾到保存點。以下示例顯式了保存點運行在相同事務下兩個事務的交叉:
| 時間 | 批處理 1 | 批處理 2 |
t1 | 開始事務; | |
t2 | 刪除 dbo.operations,其中 operation_id=5; | |
t3 | 保存事務 sp1; | |
t4 | 插入 dbo.operations 默認值; | |
t5 | 刪除 dbo.operations,其中 operation_id=10; | |
t6 | 插入 dbo.operations 默認值; | |
t7 | 如果 @@error>0 回滾事務 sp1; | |
... | ||
tn | 提交事務; |
在上述示例中,第一個請求開始一個事務,并執行某項工作(刪除一行)。然后批處理 2 開始運行(在相同事務下),并嘗試設置保存點,以確保事務內的一組給定的語句要么成功執行,要么原子失敗。
但是,在批處理 2 執行這兩個語句的時候,一個來自批處理 1 的刪除操作被交叉執行。假定批處理 2 中出現了一個錯誤,請求將會嘗試回滾到保存點 sp1。但是,這也會“靜態”回滾批處理 1 在 t5 時執行的刪除操作。
為了避免這種不可預測和難以調試的情況發生,當一個事務中有多個活動請求正在運行時,mars 不允許設置保存點、回滾到保存點和提交事務。如果上述這兩個請求按照順序執行,操作會成功,但在指定的并發請求如上述方式交叉執行的情況下,在批處理 2 中設置保存點的嘗試將會失敗,并產生一個錯誤。
執行環境前面說過,看起來執行環境好像是一個遍布整個會話的全局環境。在 mars 下,如果多個請求同時更改環境會發生什么?請看如下示例:
| 時間 | 批處理 1 | 批處理 2 | 批處理 3 |
t1 | 使用 operations; | ||
t2 | 使用 msdb; | ||
t3 | 從 dbo.operations 中選擇 operation_id; | 從 sys.objects 中選擇名稱; | |
... | |||
tn | 從 sys.objects 中選擇名稱; |
上面的示例顯式了三個批處理運行在相同的連接下。批處理 1 和批處理 2 更改數據庫上下文,然后運行 select 語句。批處理 3 則在稍后的時間運行 select 語句,但沒有指定數據庫上下文。如果執行環境是連接的真正全局狀態,那么上述組合的結果對于應用程序開發來說將是令人困惑和不可預測的。
mars 具有請求級別執行環境和會話級別默認執行環境。當請求開始執行時,會話級別環境就會被克隆成請求級別環境。在整個批處理完成之后,所產生的環境會被復制回會話級別默認執行環境。
在這種語義下,請求的執行順序(sql server 2000 中唯一允許的行為)提供了一個單一會話全局執行環境的假象。但是,在并發 mars 請求下,一個請求所做的更改并不會影響其他并發執行的請求。
在上述示例中,會話環境被復制到批處理 1 和批處理 2 上,然后這兩個批處理的 select 語句就會運行在所期望的數據庫上下文中。完成之后,這兩個批處理就會復制(和覆蓋)會話環境。請注意,在在這種情況下,所產生的會話數據庫將取決于批處理 1 和 2 的完成時間。假如批處理 3 在批處理 1 和批處理 2 完成之后開始執行,那么根據前面兩個請求的時間,返回的結果將對應于“operations”或“msdb”數據庫。
請注意在 mars 下編寫多個批處理程序的上下文復制語義。上下文的復制包括 set 選項和其余的執行環境。
注意如果某個批處理被取消,執行環境默認會被復制回取消請求被確認時的會話環境。
mars 死鎖mars 支持幾種新的情境,但由于這通常是一些具有強大功能的情況,因此它也增加了您射死自己腳步的機會。請看以下示例。
傳統上,允許對 dml 語句使用觸發器。sql server 2005 擴展了這種模型,它允許在 ddl 語句上定義觸發器。現在,我們來看看應用程序決定向調用方放回來自觸發器內部的結果(當然不是您將要按照這個可怕的練習操作的)。觸發器主體定義中包含一個 select 語句。從應用程序角度看來,偽代碼應當類似如下所示:
request 1: update table operations; // this will return results from the trigger.
for each row returned from the trigger
{
request 2: read from some other table based on current row;
}利用上述示例,我們創建了一種新型的應用程序死鎖。從 request 1 每返回一行,request 2 都會嘗試執行。但是,由于 request 1 是一個 dml 語句,因此必須等它運行完畢,任何其他語句才可以執行。相同情況也適用于 ddl 語句。在這種情況下,要等到 request 1 完成后 request 2 才會運行。但是,request 1 的完成又取決于每個 request 2 的執行。
mars 通過在死鎖檢測鏈中添加阻止網絡操作,從而解決了這個問題。對于上述情境,sql server 的死鎖監視器將會檢測環境,然后終止 request 2 并顯示一條錯誤信息,指出會話正忙于響應其他請求。
作為一般性規則,請記住哪些語句必須原子運行,并確保沒有任何操作會阻止它們前進。更重要的是,確保它們不會被依賴于前面語句完成的操作所阻止。
從觸發器返回結果是在這種環境中運行的最簡單的方式。由于這個原因和其他一些原因,我們強烈建議不要從觸發器返回結果。這包括沒有分配子句的 select 語句、沒有分配子句的 fetch 語句、print 語句以及其他在 nocount 設置為 off 時運行的語句。
監視和診斷恰如我們看到的,mars 已經改變了 sql server 引擎內的一些核心假設。很重要的一點是,在監視和診斷 sql server 實例時請記住一些新假設。
在 sql server 中,一個 spid 代表一個會話。由于以前版本中沒有 mars,因此很普遍地就將 spid 與請求關聯在一起。普遍想到要檢索某個給定 spid 的 sql 文本。普遍在多個系統過程中查找 spid 的執行統計。所有這些對于支持 mars 的情境來說可能不再足夠。
盡管系統進程繼續顯示某個會話的信息,我們還是實現了一些改進以幫助監視 mars。
新的“動態管理視圖”(dmv) sys.dm_exec_sessions 代表了會話信息的新視圖,包括會話默認執行環境。在這種視圖下,傳統上的 spid 反映在 session_id 列下。
同時,sys.dm_exec_connections 也可用于顯示所有已經建立的指向服務器的物理和邏輯連接。邏輯連接是為運行在 mars 下的每個請求建立的會話中的虛擬管道。對于邏輯連接來說,parent_connection_id 列是被填充的。普通 session_id 列也顯示了一個會話中多個邏輯連接的關系。
一個新的 dmv(sys.dm_exec_requests)表示每個會話下可用請求的詳細列表。
還引入了一個新的內部函數 current_request_id(),以便可以通過編程方式找到當前執行請求的 id。.這類似于現有的 @@spid 函數。
總結sql server 2005 中的 multiple active result sets (mars) 支持增加了開發 sql server 應用程序的選項。它帶來了光標編程模型與相關引擎的默認處理模型的性能和功能。
mars 為使用多個連接來克服缺少 mars 問題的一些應用程序提供了很好的替代辦法。但是,有時也不一定使用 mars,因為多個連接確實在服務器中提供了平行執行的能力(假如它們沒有在相同事務中登記的話)。
盡管在很多情況下,mars 可以作為服務器端光標的替代辦法,并可以提供一些性能改進,但它并不能替代光標。恰如本白皮書中介紹的,在很多情況下 mars 是很好的替代辦法,但也有很多情況使用光標會更為合適。
簡單說來,mars 是一個編程模型增強,它允許多個請求可以在服務器中交叉執行。盡管它不代表可以在服務器中平行執行,但如果正確使用的話它確實可以產生一些性能優點。
新聞熱點
疑難解答