1. 程式人生 > >Delphi多線程下的ADO編程

Delphi多線程下的ADO編程

現在 插入 一定的 插入數據 變量 nmp 技術 訪問速度 ati

前言:

幾個月前接到一個任務:將一後臺程序訪問數據庫的方式從BDE改為ADO,原因是由於業務量的增加,通過BDE不論是向數據庫寫入數據還是從數據庫中讀出數據的速度都變得無法忍受,大家都知道ADO在數據庫訪問速度方面比BDE要快的多了(我寫了一個測試程序使用ADO比使用BDE快了近100倍!)。這個任務還不簡單嘛,只要將BDE的控件更換成ADO的再修改一些代碼不就搞定了!我當時確實是這麽想的,而且用了不到一個小時就搞定,測試運行一段沒問題,大功告成了,我想。誰知道一個惡夢就此開始,我的愚昧無知使我在程序中埋下了一個超級炸彈,它的威力不次於9.11撞擊世貿大廈的兩架客機,整個系統被它無情的催跨。程序在運行很長一段時間候捕獲到一系列的異常:

OLE error 800A0E7F

Access violation at address 00135770.Write of address 005D8B78

Access violation at address 00178EC6. Readof address FFFFFFFF

Access violation at address 1F499BDD inmodule ‘msado15.dll‘. Read of address 0000000C

接下來我們的系統就像世貿大廈一下悲壯的倒下了。

為什麽?

為什麽?程序在為改動之前使用BDE運行得好好的,我並沒有更改程序的結構啊?我十分的迷惑,當然要想解決問題一切都得從錯誤代碼開始。

OLE error 800A0E7F:什麽咚咚來的?它什麽意思?什麽原因引起的?我找了半天也沒有在我的系統裏找到它的說明,好在現在網絡發達,也許有人遇到跟我一樣的問題吧,於是我用OLE error 800A0E7F作為關鍵字搜了一下,嘿嘿,果真被我找到了:

>0x800A0E7FOperation cannot be performed while executing
> asynchronously.

異步執行時操作不能被執行(完成),還是不太清楚錯誤的原因,於是我在一個網站發布了帖子求助,一些人告訴我ADO線程不安全,需要線程同步,事實上我的程序做了同步,而且針對不同的應用使用了多個ADOConnection,我想我應該自己動手來好好研究一下這個問題了,它很意思。接下來我該好好分析我的程序並做一系列的測試來找到那個炸彈。

找出炸彈

在我的程序裏所有訪問數據庫都是通過一個DataModule單元TDataModule1類提供的接口來完成,共有三個線程使用到了TDataModule1的對象DataModule1,DataModule1是一全局變量,下面是數據庫的訪問模式的結構模型圖。(實際結構要復雜很多)

技術分享

圖1

說明:

UpdateQuery ADOQuery控件用來修改table2記錄,①代表為線程1所有,

白色代表使用頻率很低(顏色越深說明使用頻率越高)

ADOQuery2 查詢table2,③代表為線程3所有,使用頻率較高

ADOQuery3 查詢table2,③代表為線程2所有,使用頻率很高

ADOProcedure1 ADO存儲過程控件向表table2插入數據,屬於線程1頻繁使用

修改ADOProcedure1插入的記錄,屬於線程1頻繁使用

其中線程3和線程2使用ADO控件時沒有加鎖,而線程1的所有訪問都加鎖了(這樣做毫無作用)

程序的結構出來了,問題在哪裏呢?接下來我寫了一個小小的測試程序,該程序的結構與上面相同,它擁有三個線程和一個DataMoule單元,線程一通過ADOQuery1查詢數據庫DBTest的table1的記錄,線程二通過ADOQuery2向table1中插入記錄,線程三通過ADOQuery3修改table1中最後一條記錄的某個字段。ADOQuery1、ADOQuery2、ADOQuery3都通過ADOConnection1與數據庫DBTest1建立連接,一開始,所有的線程都不做同步,運行,OK!錯誤出來了其中兩個錯誤正是我所想要的,這就是我的程序報的錯啊。

圖二

接下來我將三個ADOQuery都加上鎖,再運行沒問題,我又將ADOQuery分別通過三個不同的ADOConnection來連接數據庫且不加鎖也沒有問題。看來我是找到那個可惡的炸彈了,怎麽拆了它?

排除炸彈

炸彈找到了,我該怎麽拆它?是簡單的做線程同步還是每個線程都是用一個ADOConnection?這下我再也不敢蠻幹了,我得好好看看這方面的資料,在Delphi幫助文檔,《Usingthe main VCL thread》我找到了下面一段話:

Data access components are thread-safe as long as each thread hasits own database session component. The one exception to this is when you areusing Access drivers. Access drivers are built using the Microsoft ADO library,which is not thread-safe.

同樣在Delphi的幫助文檔《Managingmultiple sessions》中給我明確的建議:

If you create a single application that uses multiple threads toperform database operations, you must create one additional session for eachthread.

喔找到了:ADO控件是線程不安全的,所以如果你的程序是使用多線程訪問數據庫的話你應該確保每個線程都有自己的會話。

事實上在另外一本書《Delphi 4編程技術內幕》一書在談到線程安全數據庫訪問也有相同的建議,不過臺灣李維先生在他的《Delphi 5.X ADO/MTS/COM+高級程序設計篇》卻說,如果你的程序不是連接多個數據庫的話,最好同一數據庫使用一個連接,不要使用多個連接。怎麽辦?誰對誰錯?為什麽要使用一個連接呢?這主要是從服務器來考慮,因為數據庫服務器需要為每個連接分配一定的資源並對其進行維護,連接數越多服務器方所耗的資源就越多,服務器的性能也就越差,所以要盡可能的減少客戶端的連接數。好在我的程序是作為服務器程序增加一些連接對數據庫服務器的影響不會很大,現在我可以重新設置我的數據庫訪問結構模型了

技術分享

圖三

我增加了一個ADOConnection以保證每個線程都有一個自己連接(會話),從而避免出現資源沖突,我的問題是不是解決了呢?是的,這個問題已經解決了,將我的程序與數據庫放在同一臺機器上運行沒有問題,但是當程序與數據庫服務器不在同一臺機器上運行時會出現一個新的問題。

[DBNMPNTW]ConnectionWrite(writeFile())錯誤

這個錯誤不是多線程引起的,而是Micrsoft自己的一個問題,產生該問題的原因可能是因為網絡異常而引起的,可以通過SQLServer客戶端的默認的網絡協議namedpipes network propocol 改為 TCP/IP Sockets,具體做法請參考Micrsoft技術支持網站的《Microsoft Knowledge Base Article - Q178040

總結

由於ADO控件的線程不安全性(事實上這種不安全性是來自Micrsoft ADO Library,所以在其它開發工具中也存在同樣的問題)因此在使用多線程ADO編程時應該註意一下問題:

第一:要保證每個線程都擁有自己的會話。

第二:作為客戶端程序應該盡可能的減少與數據庫庫服務器的連接數。

第三:在退出線程之前確保釋放所有的資源。

參考文獻:

1、李維《Delphi 5.X ADO/MTS/COM+高級程序設計篇》機械工業出版社 2000。

2、CharlieCalvert《Delphi4編程技術內幕》瀟湘工作室 譯 機械工業出版社 1999。

http://blog.csdn.net/youthon/article/details/8890967

Delphi多線程下的ADO編程