Delphi多線程下的ADO編程
前言:
幾個(gè)月前接到一個(gè)任務(wù):將一后臺程序訪問數(shù)據(jù)庫的方式從BDE改為ADO,原因是由于業(yè)務(wù)量的增加,通過BDE不論是向數(shù)據(jù)庫寫入數(shù)據(jù)還是從數(shù)據(jù)庫中讀出數(shù)據(jù)的速度都變得無法忍受,大家都知道ADO在數(shù)據(jù)庫訪問速度方面比BDE要快的多了(我寫了一個(gè)測試程序使用ADO比使用BDE快了近100倍!)。這個(gè)任務(wù)還不簡單嘛,只要將BDE的控件更換成ADO的再修改一些代碼不就搞定了!我當(dāng)時(shí)確實(shí)是這么想的,而且用了不到一個(gè)小時(shí)就搞定,測試運(yùn)行一段沒問題,大功告成了,我想。誰知道一個(gè)惡夢就此開始,我的愚昧無知使我在程序中埋下了一個(gè)超級炸彈,它的威力不次于9.11撞擊世貿(mào)大廈的兩架客機(jī),整個(gè)系統(tǒng)被它無情的催跨。程序在運(yùn)行很長一段時(shí)間候捕獲到一系列的異常:
OLE error 800A0E7F
Access violation at address 00135770. Write of address 005D8B78
Access violation at address 00178EC6. Read of address FFFFFFFF
Access violation at address 1F499BDD in module 'msado15.dll'. Read of address 0000000C
…….
接下來我們的系統(tǒng)就像世貿(mào)大廈一下悲壯的倒下了。
為什么?
為什么?程序在為改動(dòng)之前使用BDE運(yùn)行得好好的,我并沒有更改程序的結(jié)構(gòu)啊?我十分的迷惑,當(dāng)然要想解決問題一切都得從錯(cuò)誤代碼開始。
OLE error 800A0E7F:什么咚咚來的?它什么意思?什么原因引起的?我找了半天也沒有在我的系統(tǒng)里找到它的說明,好在現(xiàn)在網(wǎng)絡(luò)發(fā)達(dá),也許有人遇到跟我一樣的問題吧,于是我用OLE error 800A0E7F作為關(guān)鍵字搜了一下,嘿嘿,果真被我找到了:
>0x800A0E7F Operation cannot be performed while executing
> asynchronously.
異步執(zhí)行時(shí)操作不能被執(zhí)行(完成),還是不太清楚錯(cuò)誤的原因,于是我在一個(gè)網(wǎng)站發(fā)布了帖子求助,一些人告訴我ADO線程不安全,需要線程同步,事實(shí)上我的程序做了同步,而且針對不同的應(yīng)用使用了多個(gè)ADOConnection,我想我應(yīng)該自己動(dòng)手來好好研究一下這個(gè)問題了,它很意思。接下來我該好好分析我的程序并做一系列的測試來找到那個(gè)炸彈。
找出炸彈
在我的程序里所有訪問數(shù)據(jù)庫都是通過一個(gè)DataModule單元TDataModule1類提供的接口來完成,共有三個(gè)線程使用到了TDataModule1的對象DataModule1,DataModule1是一全局變量,下面是數(shù)據(jù)庫的訪問模式的結(jié)構(gòu)模型圖。(實(shí)際結(jié)構(gòu)要復(fù)雜很多)
圖1
說明:
ADOQuery控件用來修改table2記錄,①代表為線程1所有,
白色代表使用頻率很低(顏色越深說明使用頻率越高)
查詢table2,③代表為線程3所有,使用頻率較高
查詢table2,③代表為線程2所有,使用頻率很高
ADO存儲過程控件向表table2插入數(shù)據(jù),屬于線程1頻繁使用
修改ADOProcedure1插入的記錄,屬于線程1頻繁使用
其中線程3和線程2使用ADO控件時(shí)沒有加鎖,而線程1的所有訪問都加鎖了(這樣做毫無作用)
程序的結(jié)構(gòu)出來了,問題在哪里呢?接下來我寫了一個(gè)小小的測試程序,該程序的結(jié)構(gòu)與上面相同,它擁有三個(gè)線程和一個(gè)DataMoule單元,線程一通過ADOQuery1查詢數(shù)據(jù)庫DBTest的table1的記錄,線程二通過ADOQuery2向table1中插入記錄,線程三通過ADOQuery3修改table1中最后一條記錄的某個(gè)字段。ADOQuery1、ADOQuery2、ADOQuery3都通過ADOConnection1與數(shù)據(jù)庫DBTest1建立連接,一開始,所有的線程都不做同步,運(yùn)行,OK!錯(cuò)誤出來了其中兩個(gè)錯(cuò)誤正是我所想要的,這就是我的程序報(bào)的錯(cuò)啊。
圖二
接下來我將三個(gè)ADOQuery都加上鎖,再運(yùn)行沒問題,我又將ADOQuery分別通過三個(gè)不同的ADOConnection來連接數(shù)據(jù)庫且不加鎖也沒有問題。看來我是找到那個(gè)可惡的炸彈了,怎么拆了它?
排除炸彈
炸彈找到了,我該怎么拆它?是簡單的做線程同步還是每個(gè)線程都是用一個(gè)ADOConnection?這下我再也不敢蠻干了,我得好好看看這方面的資料,在Delphi幫助文檔,《Using the main VCL thread》我找到了下面一段話:
……
Data access components are thread-safe as long as each thread has its own database session component. The one exception to this is when you are using Access drivers. Access drivers are built using the Microsoft ADO library, which is not thread-safe.
…..
同樣在Delphi的幫助文檔《Managing multiple sessions》中給我明確的建議:
……
If you create a single application that uses multiple threads to perform database operations, you must create one additional session for each thread.
…..
喔找到了:ADO控件是線程不安全的,所以如果你的程序是使用多線程訪問數(shù)據(jù)庫的話你應(yīng)該確保每個(gè)線程都有自己的會(huì)話。
事實(shí)上在另外一本書《Delphi 4編程技術(shù)內(nèi)幕》一書在談到線程安全數(shù)據(jù)庫訪問也有相同的建議,不過臺灣李維先生在他的《Delphi 5.X ADO/MTS/COM+高級程序設(shè)計(jì)篇》卻說,如果你的程序不是連接多個(gè)數(shù)據(jù)庫的話,最好同一數(shù)據(jù)庫使用一個(gè)連接,不要使用多個(gè)連接。怎么辦?誰對誰錯(cuò)?為什么要使用一個(gè)連接呢?這主要是從服務(wù)器來考慮,因?yàn)閿?shù)據(jù)庫服務(wù)器需要為每個(gè)連接分配一定的資源并對其進(jìn)行維護(hù),連接數(shù)越多服務(wù)器方所耗的資源就越多,服務(wù)器的性能也就越差,所以要盡可能的減少客戶端的連接數(shù)。好在我的程序是作為服務(wù)器程序增加一些連接對數(shù)據(jù)庫服務(wù)器的影響不會(huì)很大,現(xiàn)在我可以重新設(shè)置我的數(shù)據(jù)庫訪問結(jié)構(gòu)模型了
圖三
我增加了一個(gè)ADOConnection以保證每個(gè)線程都有一個(gè)自己連接(會(huì)話),從而避免出現(xiàn)資源沖突,我的問題是不是解決了呢?是的,這個(gè)問題已經(jīng)解決了,將我的程序與數(shù)據(jù)庫放在同一臺機(jī)器上運(yùn)行沒有問題,但是當(dāng)程序與數(shù)據(jù)庫服務(wù)器不在同一臺機(jī)器上運(yùn)行時(shí)會(huì)出現(xiàn)一個(gè)新的問題。
[DBNMPNTW]ConnectionWrite(writeFile())錯(cuò)誤
這個(gè)錯(cuò)誤不是多線程引起的,而是Micrsoft自己的一個(gè)問題,產(chǎn)生該問題的原因可能是因?yàn)榫W(wǎng)絡(luò)異常而引起的,可以通過SQLServer客戶端的默認(rèn)的網(wǎng)絡(luò)協(xié)議named pipes network propocol 改為 TCP/IP Sockets,具體做法請參考Micrsoft技術(shù)支持網(wǎng)站的《Microsoft Knowledge Base Article - Q178040》
總結(jié)
由于ADO控件的線程不安全性(事實(shí)上這種不安全性是來自Micrsoft ADO Library,所以在其它開發(fā)工具中也存在同樣的問題)因此在使用多線程ADO編程時(shí)應(yīng)該注意一下問題:
第一:要保證每個(gè)線程都擁有自己的會(huì)話。
第二:作為客戶端程序應(yīng)該盡可能的減少與數(shù)據(jù)庫庫服務(wù)器的連接數(shù)。
第三:在退出線程之前確保釋放所有的資源。
參考文獻(xiàn):
1、李維《Delphi 5.X ADO/MTS/COM+高級程序設(shè)計(jì)篇》 機(jī)械工業(yè)出版社 2000。
2、Charlie Calvert《Delphi 4編程技術(shù)內(nèi)幕》瀟湘工作室 譯 機(jī)械工業(yè)出版社 1999