緩衝(Buffering)
Visual FoxPro 中的緩衝技術
當遠端資料下載到用戶端時,這些資料就被壓入緩衝之中。在用戶端使用者對資料的各種移動並不反映到資料來源,而是在使用者確認對資料的變動後,才把各種變動以SQL描述的形式發送到後端。那麼為什麼不讓Visual FoxPro直接一步一動的操控遠端資料,就像在不使用緩衝技術控制Visual FoxPro本機資料那樣。我想原因在於:
-
在 Client/Server 構架的應用中,資料庫伺服器需要同時處理許多客戶的請求,如果有一個客戶“直接”控制(鎖定)它,多使用者的系統就無從談起了。
-
Visual FoxPro 通過 ODBC 與遠端資料庫通訊,如果一步一動,兩者之間的通訊肯定會成倍增加,這樣既加重了網路負擔又加重的資料庫伺服器的負擔。
基於上述原因,Visual FoxPro在遠端資料處理時強制使用緩衝技術。我們知道,在Visual FoxPro中緩衝技術與鎖結合有四種選擇:
-
保守式行緩衝。所謂“保守”,就是“編輯時鎖定”的意思,“行緩衝”是指“只緩衝處理一筆使用者加以編輯的資料記錄”。因此一旦使用這種模式,當編輯動作剛開始,資料來源的對應資料記錄便被鎖定,而且在執行以下兩項動作時,資料變動才會被發送:移動資料指標、執行TABLEUPDATE()函數。
由於在開始編輯時就鎖定資料來源的對應行,所以這種模式不被遠端資料處理採用。
-
開放式行緩衝。所謂“開放”,就是“更新時鎖定”的意思,“行緩衝”是指“只緩衝處理一筆使用者加以編輯的資料記錄”。因此使用這種模式,只有在執行以下兩項動作時,資料變動才會被發送,資料來源對應行記錄才被鎖定:移動資料指標、執行TABLEUPDATE()函數。
-
保守式表緩衝。所謂“保守”,就是“編輯時鎖定”的意思,“表緩衝”是指“緩衝處理整個使用者加以編輯的資料集(游標)”。因此一旦使用這種模式,當編輯動作剛開始,資料來源的相關記錄集便被鎖定,而且在執行以下動作時,資料變動才會被發送:執行TABLEUPDATE()函數。
由於在開始編輯時就鎖定資料來源的整個對應表或是記錄集,所以這種模式不被遠端資料處理採用。
-
開放式行緩衝。所謂“開放”,就是“更新時鎖定”的意思,“表緩衝”是指“緩衝處理整個使用者加以編輯的資料集(游標)”。因此使用這種模式,只有在執行TABLEUPDATE()函數時,資料變動才會被發送,資料來源的相關記錄集才被鎖定。
好了,我們得到以下結論:在操控遠端資料時,Visual FoxPro將對游標採用“開放式行緩衝”或“開放式表緩衝”,預設設定是“開放式行緩衝”。
以後在討論遠端資料處理時,不特別指出,行緩衝就是指開放式行緩衝,表緩衝是指開放式表緩衝。
在“開放式行緩衝”下,因為只對一條被編輯的記錄開啟緩衝,所以有兩種方式可以確認編輯、發送更新:移動指標(在上面的例子中我們已經使用過了)、TABLEUPDATE()函數。不知您能否理解“指標移動確認更新”的意思?我是這樣理解的:行緩衝只對一條被編輯的記錄有用,如果移動指標,那就必須確認更新(如果資料有變動),因為如果不確認更新(釋放緩衝區),Visual FoxPro便沒法為下一行制定緩衝區了——記住:這是行緩衝。
“在開放式表緩衝”下,Visual FoxPro對整個記錄集開啟緩衝區,所以移動指標並不會確認更新。只有使用TABLEUPDATE()函數了。
乍一看,“開放式行緩衝”比“開放式行緩衝”需要更少的系統資源,好像是個好選擇,我看不盡然:
-
有些Visual FoxPro的命令或函數會“不由自主”地移動指標,使得開發人員對更新的確認失去控制。
-
有時對資料的維護是成批的。
下面的代碼說明了怎樣控制緩衝:
USE VCustomers
CURSORSETPROP("Buffering", 3, "VCustomers")
*設定VCustomers的緩衝模式為“開放式行緩衝”。由於這時Visual FoxPro的預設設定,這一句可省略。
USE VOrders
CURSORSETPROP("Buffering", 5, "VOrders")
*設定VOrder的緩衝模式為“開放式表緩衝”。
以緩衝理解更新衝突
在圖8中我在發生更新錯誤時提示:“原先Phone=030-0074321,現在Phone=00000,兩者不等……”,那麼這個原先是“什麼時候”,“現在”又是怎樣的概念?(假設進程一、二採用行緩衝模式、用“關鍵字和已更新欄位”檢測衝突)。
“原先”是指:進程一中視圖被開啟或是最近一次重新整理成功時刻SQL Server資料表中的記錄值。讓我們先停下來,怎樣才會重新整理游標和緩衝呢?
-
遠程視圖游標被開啟(無論使用行緩衝還是表緩衝)。
-
成功執行REQUERY()函數(無論使用行緩衝還是表緩衝)。
-
發送更新(無論成功與否)
您可以想象:進程一開啟遠程視圖,Visual FoxPro自動把這個時刻SQL Server的資料值壓入緩衝中,這時進程一認為:我對SQL Server上資料的修改應建立在這個基礎上,即Phone=030-0074321,如果這個基礎不存在了,這發生更新錯誤。
在進程一還沒有把它在用戶端對Phone的修改發送到資料來源的時候,進程二也讀取了SQL Server的資料,注意這時進程二認為:我對SQL Server上資料的修改應建立在這個基礎上,即Phone=030-0074321。於是進程二修改Phone為00000,並在進程一之前確認的資料變動,這時是不會發生更新衝突的,因為進程二修改資料的依據是成立的。
進程一慢慢吞吞地把資料改為了123456,發送更新。這時問題來了:進程一告訴SQL Server這樣修改資料:
sp_executesql N'UPDATE dbo.Customers SET Phone=@P1 WHERE CustomerID=@P2 AND Phone=@P3', N'@P1 nvarchar(24),@P2 varchar(50),@P3 nvarchar(24)', N'123456 ', 'ALFKI', N'030-0074321'
我把參數填入,您就能很清楚地看到問題所在:
UPDATE dbo.Customers SET Phone='123456' WHERE CustomerID='ALFKI' AND Phone='030-0074321'
看到沒有:Visual FoxPro自動的把緩衝裡的Phone=030-0074321(在BROWSE 視窗中您已經把Phone改為了123456,Visual FoxPro“早有預謀”,把未經處理資料存在緩衝中,任你表面變化萬次——我都不怕)拿出來並結合關鍵字作為更新依據,然而由於進程二已經修改了Phone的值,在SQL Server 中哪裡還會有存在符合條件 CustomerID= 'ALFKI' AND Phone='030-0074321'的行了,只有CustomerID= 'ALFKI' AND Phone='00000'的記錄行了。於是SQL Server 告訴 Visual FoxPro找不到目標記錄,Visual FoxPro就對使用者說:更新衝突。
所以,緩衝作用是就在這裡:客戶機與伺服器通過 ODBC 這個翻譯“傳情達意”——但 ODBC 很苯——只能傳一些SQL語句。事實上任何對資料的變動,都可歸結為:Insert、Update、Delete。SQL語句與Visual FoxPro的命令函數有很大的不同——目標定位必須依靠條件陳述式(Where 子句)(Visual FoxPro可以很容易定位到第N行);緩衝為這些至關重要的定位條件提供了依據,沒有緩衝就無法產生定位語句!
確認更新、放棄更新
確認更新
上文我們多次提到確認更新有基本上算是兩種方式:移動指標、使用TABLEUPDATE()函數。移動指標只能在“開放式行緩衝“下使用,並且開發人員對法的可控性較差,一般用於互動式工具中,如上文我們使用過的SQL Server 的Enterprise Manager工具。這裡我們只討論TABLEUPDATE()函數。
在開放式行緩衝下使用TABLEUPDATE()函數:
-
文法:TABLEUPDATE(0[,lForce][,nWorkAear|cTableAlias])
-
傳回值:更新成功——.T.,更新失敗——.F.
-
必選參數:0。代表只更新目前記錄到資料來源——這裡是記錄緩衝,當然是:“只更新目前記錄到資料來源”。
-
選擇性參數——lForce。預設為.F.,指:如果發生更新錯誤就確認更新錯誤,本函數返回.F.;如果設此參數為.t., 表示發生更新錯誤時,以本用戶端的新值為準,覆蓋網路上被確認已經的其他使用者的更新,如果覆蓋成功,本函數返回.T.。
預設表示本參數時取預設值。
該參數設為.T.的實質就是臨時改變更新衝突的檢測方式為“關鍵字段”,所以只要關鍵字不發生衝突,就不會發生更新衝突,本用戶端的新值將覆蓋其他使用者做的變更。
-
選擇性參數——nWorkAear|cTableAlias。表示實行TABLEUPDATE()的工作區,預設表示對當前工作區有效。
例如:
USE VCustomers
REPLACE PHONE WITH '123456'
?TABLEUPDATE(0)
*返回 .T.更新成功,反之失敗。
USE VCustomers
REPLACE PHONE WITH '123456'
?TABLEUPDATE(0,.t.,'VCostomers')
*由於lForce設定為.t.,Visual FoxPro 臨時修改更新檢測方式為“關鍵字段”方式,所以只要關鍵字CustomerID不發生衝突,即使其他欄位已經被其他使用者修改,Visual FoxPro也不會檢測,Visual FoxPro將強制覆蓋其它使用者做的修改。
*在“關鍵字段和已修改欄位”的衝突檢測方式下:Visual FoxPro向SQL Server發送如下語句:
sp_executesql N'UPDATE dbo.Customers SET Phone=@P1 WHERE CustomerID=@P2', N'@P1 nvarchar(24),@P2 varchar(50)', N'123456', 'ALFKI'
在開放式表緩衝下使用TABLEUPDATE()函數:
-
文法:TABLEUPDATE(nRows[,lForce][,nWorkAear|cTableAlias][,cErrorArray])
-
傳回值:更新成功——.T.,更新失敗——.F.
-
必選參數——nRows。可以有兩種取值:1、2。假設使用者對游標的第 1、2、4、5、7條記錄作了修改,如果更新變動,第4條記錄上將發生更新衝突。現在,執行本函數,Visual FoxPro將依次發送五條UPDATE-SQL描述:
設本參數為1時,執行到第4條記錄時發生更新衝突,Visual FoxPro將停止發送的5、7條記錄的更新描述,並使本函數返回.F.值,Visual FoxPro的記錄指標停留在第四條記錄上。
設本參數為2時,執行到第4條記錄時發生更新衝突,Visual FoxPro將繼續發送的5、7條記錄的更新描述,並使本函數返回.F.值,Visual FoxPro的記錄指標最終停在最後一條被修改的記錄上(這裡是第7條記錄)。如果選擇性參數——cErrorArray存在,Visual FoxPro將把發生更新錯誤的記錄號(RECNO())寫入該數組,如果第5條記錄也發生衝突,那麼該數組將是一個一列兩行的數組,cErrorArray[1]=4、cErrorArray[2]=5
-
選擇性參數——lForce。預設為.F.,指:如果發生更新錯誤就確認更新錯誤,本函數返回.F.;如果設此參數為.t., 表示發生更新錯誤時,以本用戶端的新值為準,覆蓋網路上被確認已經的其他使用者的更新,如果覆蓋成功,本函數返回.T.。
預設表示本參數時取預設值。
該參數設為.T.的實質就是臨時改變更新衝突的檢測方式為“關鍵字段”,所以只要關鍵字不發生衝突,就不會發生更新衝突,本用戶端的新值將覆蓋其他使用者做的變更。
-
選擇性參數——nWorkAear|cTableAlias。表示實行TABLEUPDATE()的工作區,預設表示對當前工作區有效。
-
選擇性參數——cErrorArray。這是一個一列數組,且只有當必選參數nRows為2時有效,這時它記錄著發生更新衝突的記錄的記錄號;如果沒有發生任何更新衝突或是當必選參數nRows不為2時,本數組為一行一列,值為-1。
舉個例子:(假設使用“關鍵字段和已修改欄位”作為更新衝突檢測方案)
USE VCustomers
CURSORSETPROP("Buffering", 5, "VCustomers")
REPLACE Phone with '9999' next 4
*將第1、2、3、4、條記錄的Phone改為9999
BROWSE
*使用 SQL Server 的Query Analyzer 製造更新衝突
*啟動 SQL Server Query Analyzer,登入Northwind 資料庫
*輸入如下語句並執行:
update customers set phone='00000' where customerid='ANATR' or customerid='ANTON'
情況一:TABLEUPDATE(1,.F.,'Vcustomers')
*返回Visual FoxPro
?TABLEUPDATE(1,.F.,'Vcustomers')
*由於記錄 2更新時發生衝突,函數返回.F.
?recno('Vcustomers')
*指標停在第2條記錄上
?Aerror(err)
用Aerror函數取得Visual FoxPro錯誤資訊存入err數組中
?err(1)
*錯誤號碼:1585
?err(2)
*錯誤資訊:“更新衝突”
*回到SQL Server Query Analyzer
*輸入如下語句並執行:
select customerid,phone from customers
*您將看到:第一條記錄Phone的值已經被Visual FoxPro的用戶端修改了,值是:9999。而後三條記錄沒有發生變化。說明Visual FoxPro依次發送SQL描述到SQL Server時,遇到更新錯誤就停止繼續往下工作。
事實上,查看 SQL Server 的 Profiler 工具也證明了以上論述:
sp_executesql N'UPDATE dbo.Customers SET Phone=@P1 WHERE CustomerID=@P2 AND Phone=@P3', N'@P1 nvarchar(24),@P2 varchar(50),@P3 nvarchar(24)', N'9999 ', 'ALFKI', N'bbbb '
sp_executesql N'UPDATE dbo.Customers SET Phone=@P1 WHERE CustomerID=@P2 AND Phone=@P3', N'@P1 nvarchar(24),@P2 varchar(50),@P3 nvarchar(24)', N'9999 ', 'ANATR', N'1234 '//此處發生更新錯誤,Visual FoxPro停止往下工作
情況二:TABLEUPDATE(1,.T.,'Vcustomers')
*返回Visual FoxPro
?TABLEUPDATE(1,.T.,'Vcustomers')
*函數返回.T.
?recno('Vcustomers')
*指標停在第4條記錄上
*回到SQL Server Query Analyzer
*輸入如下語句並執行:
select customerid,phone from customers
*您將看到:頭四條記錄Phone的值已經被Visual FoxPro的用戶端修改了,值是:9999。按理說第2條記錄被更新時會發生衝突,但由於Visual FoxPro臨時變更了更新衝突的檢測方案為“關鍵字段”,這樣原本應該能檢測到的衝突被忽略了,Visual FoxPro用戶端的新值強行覆蓋其它用戶端的修改。
事實上,查看 SQL Server 的 Profiler 工具也證明了以上論述:
sp_executesql N'UPDATE dbo.Customers SET Phone=@P1 WHERE CustomerID=@P2', N'@P1 nvarchar(24),@P2 varchar(50)', N'9999 ', 'ALFKI'
sp_executesql N'UPDATE dbo.Customers SET Phone=@P1 WHERE CustomerID=@P2', N'@P1 nvarchar(24),@P2 varchar(50)', N'9999 ', 'ANATR'
sp_executesql N'UPDATE dbo.Customers SET Phone=@P1 WHERE CustomerID=@P2', N'@P1 nvarchar(24),@P2 varchar(50)', N'9999 ', 'ANTON'
sp_executesql N'UPDATE dbo.Customers SET Phone=@P1 WHERE CustomerID=@P2', N'@P1 nvarchar(24),@P2 varchar(50)', N'9999 ', 'AROUT'
情況三:TABLEUPDATE(2,.F.,'Vcustomers',Arry)
*返回Visual FoxPro
?TABLEUPDATE(2,.F.,'Vcustomers',Arry)
*參數nRows設為2,即使記錄 2、3發生更新衝突,Visual FoxPro仍然繼續往下執行,但參數函數返回.F.
?recno('Vcustomers')
*指標停在第4條記錄上
?Aerror(err)
用Aerror函數取得Visual FoxPro錯誤資訊存入err數組中
?err(1)
*錯誤號碼:1585
?err(2)
*錯誤資訊:“更新衝突”
?Arry[1]
*2
?Arry[2]
*3
*Arry返回傳生更新錯誤的記錄號
*回到SQL Server Query Analyzer
*輸入如下語句並執行:
select customerid,phone from customers
*您將看到:第一條、第四條記錄Phone的值已經被Visual FoxPro的用戶端修改了,值是:9999。而第二條、第三條記錄沒有發生變化。說明Visual FoxPro依次發送SQL描述到SQL Server時,遇到更新衝突忽略之,繼續往下工作。
事實上,查看 SQL Server 的 Profiler 工具也證明了以上論述:
sp_executesql N'UPDATE dbo.Customers SET Phone=@P1 WHERE CustomerID=@P2 AND Phone=@P3', N'@P1 nvarchar(24),@P2 varchar(50),@P3 nvarchar(24)', N'9999 ', 'ALFKI', N'cccc '
sp_executesql N'UPDATE dbo.Customers SET Phone=@P1 WHERE CustomerID=@P2 AND Phone=@P3', N'@P1 nvarchar(24),@P2 varchar(50),@P3 nvarchar(24)', N'9999 ', 'ANATR', N'cccc '//發生更新衝突,Visual FoxPro繼續往下執行
sp_executesql N'UPDATE dbo.Customers SET Phone=@P1 WHERE CustomerID=@P2 AND Phone=@P3', N'@P1 nvarchar(24),@P2 varchar(50),@P3 nvarchar(24)', N'9999 ', 'ANTON', N'cccc '//發生更新衝突,Visual FoxPro繼續往下執行
sp_executesql N'UPDATE dbo.Customers SET Phone=@P1 WHERE CustomerID=@P2 AND Phone=@P3', N'@P1 nvarchar(24),@P2 varchar(50),@P3 nvarchar(24)', N'9999 ', 'AROUT', N'cccc '
情況四:TABLEUPDATE(2,.T.,'Vcustomers',Arry)
*返回Visual FoxPro
?TABLEUPDATE(2,.T.,'Vcustomers',Arry)
*但參數函數返回.T.
?recno('Vcustomers')
*指標停在第4條記錄上
?Arry[1]
*-1
*沒有發生更新錯誤
*回到SQL Server Query Analyzer
*輸入如下語句並執行:
select customerid,phone from customers
*您將看到:所有記錄Phone的值已經被Visual FoxPro的用戶端修改了,值是:9999。按理說第2條、第3條記錄被更新時會發生衝突,但由於Visual FoxPro臨時變更了更新衝突的檢測方案為“關鍵字段”,這樣原本應該能檢測到的衝突被忽略了,Visual FoxPro用戶端的新值強行覆蓋其它用戶端的修改。
事實上,查看 SQL Server 的 Profiler 工具也證明了以上論述:
sp_executesql N'UPDATE dbo.Customers SET Phone=@P1 WHERE CustomerID=@P2', N'@P1 nvarchar(24),@P2 varchar(50)', N'9999 ', 'ALFKI'
sp_executesql N'UPDATE dbo.Customers SET Phone=@P1 WHERE CustomerID=@P2', N'@P1 nvarchar(24),@P2 varchar(50)', N'9999 ', 'ANATR'
sp_executesql N'UPDATE dbo.Customers SET Phone=@P1 WHERE CustomerID=@P2', N'@P1 nvarchar(24),@P2 varchar(50)', N'9999 ', 'ANTON'
sp_executesql N'UPDATE dbo.Customers SET Phone=@P1 WHERE CustomerID=@P2', N'@P1 nvarchar(24),@P2 varchar(50)', N'9999 ', 'AROUT'
放棄更新
如果要“放棄用戶端對游標已經實施的變動”,該怎麼辦呢?這很簡單,請使用TABLEREVERT()函數。這裡有一個概念很重要:任何情況下執行本函數均不都會與遠端資料源發生通訊,Visual FoxPro只是從緩衝中把原先的數值取回填寫入游標中。
那麼Visual FoxPro是怎樣從緩衝中取回資料的呢?您可以用OLDVAL()函數得到相同的效果,它的用法這裡暫不介紹!
在開放式行緩衝下使用TABLEREVERT()函數:
-
文法:TABLEUPDATE(.f.[,nWorkAear|cTableAlias])
-
傳回值:1。如果目前記錄沒有被修改,則返回0。
-
選擇性參數——nWorkAear|cTableAlias。表示實行TABLEREVERT()的工作區,預設表示對當前工作區有效。
例如:
USE VCustomers
CURSORSETPROP("Buffering", 3, "VCustomers")
REPLACE Phone with '9999'
?VCustomer.phone
*9999
?TABLEREVERT(.F.,'VCustomers')
*返回1
?VCustomer.phone
*123456
USE VCustomers
CURSORSETPROP("Buffering", 3, "VCustomers")
?TABLEREVERT(.F.,'VCustomers')
*返回0
在開放式表緩衝下使用TABLEREVERT()函數:
-
文法:TABLEUPDATE(lAllRows[,nWorkAear|cTableAlias])
-
傳回值:放棄更新的記錄數目
-
必選參數——lAllRows。預設值為.F.,表示對目前記錄放棄更新;本參數設定為 .T.,放棄更新所有被修改過的記錄。
-
選擇性參數——nWorkAear|cTableAlias。表示實行TABLEREVERT()的工作區,預設表示對當前工作區有效。
USE VCustomers
CURSORSETPROP("Buffering", 5, "VCustomers")
REPLACE Phone with '9999' next 4
*將第1、2、3、4、條記錄的Phone改為9999
BROWSE
go 2
?TABLEREVERT(.F.,'VCustomers')
*1
?TABLEREVERT(.T.,'VCustomers')
*3