從一個bug談談psqlodbc遊標的一點認識

本文源於最近修正的一個關於psqlodbc的bug,該bug在近期的psqlodbc的git上也有人提交了修正。
關於該bug的修正代碼能夠看這裏:
https://git.postgresql.org/gitweb/?p=psqlodbc.git;a=commit;h=85f6fade3git

說道這個bug,咱們要提ODBC提供的兩個API函數:SQLBulkOperations 和 SQLSetPosweb

關於這兩個函數,它們的用處是:sql

SQLBulkOperations執行大容量插入和大容量書籤操做(包括update、 delete和fetch)。
SQLSetPos函數設置在行集中的遊標位置,並容許應用程序刷新行集中的數據或用於更新或刪除在結果集中的數據。當利用SQLSetPos刪除數據時,操做設置爲 SQL_DELETE而且將RowNumber設置爲要刪除的行號(當設置RowNumber爲0時刪除結果集中的全部數據)

說白了就是你先用SQLExecute或者SQLExecDirect函數執行SQL文獲取一個結果集,而後對這個結果集進行操做。bug發生的場景是DELETE的時候,即咱們對結果集中的數據進行刪除時發生。在對結果集的數據進行刪除時,會調用Adddeleted()函數將結果集中被刪除的數據的行號、狀態信息和oid,ctid等作記錄,保存在一個結構體數組中。而結構體數組在設計是要求按照遞增的順序進行存儲的,因此你進行每一次刪除可能就要對這個結構體數組作排序操做。問題就在這個排序過程當中,排序的時候本應該是依次遍歷數組,每次for循環結束count+1,結果它這裏直接是count+num_field(這個是結果集的列數),因此,只要返回的結果集的列數大於1,bug就應該發生。數組

彷佛就是這樣清楚明白。
咱們按照如下的構造再現程序:緩存

(1) 調用SQLSetStmtAttr函數設置SQL文的SQL_ATTR_ROW_ARRAY_SIZE屬性值大於1;
(2) 調用SQLExecute或者SQLExecDirect函數執行SQL文;
(3) 調用SQLFetch或SQLFetchScroll函數獲取結果集;
(4) (3)中返回的結果集的列數大於1;
(5) (3)中返回的結果集的行數大於1;
(6) 調用ODBC的API函數SQLSetPos或者SQLBulkOperations刪除(2)中返回的結果集中的數據;
(7) (6)中刪除的記錄行數超過一行

說明:SQL_ATTR_ROW_ARRAY_SIZE函數

指定每次調用SQLFetch或SQLFetchScroll函數返回的結果集行數。 它也用於指定SQLBulkOperations函數的大容量書籤操做中的書籤數組中的行數。 默認值爲 1。

讓程序返回了50條數據,可是咱們就是再現不出來。無奈,無心中我把返回的結果集增大到了200,結果竟然在現了。post

奇怪,這個bug和結果集的行數無關啊。測試

帶着這個疑問,我調試了代碼:
在AddDeleted()函數的開頭,有這樣的語句:fetch

if (!QR_get_cursor(res))
        return TRUE;

當結果集函數較小時,直接在這裏就返回了。。。也就是說這個時候沒有遊標了。但是使用SQLExecute()執行SQL、文是默認有遊標打開的,程序從遊標中獲取數據的。不服輸的我又在測試程序開頭顯示地指定了遊標:設計

SQLSetCursorName(hstmt, "C1", SQL_NTS);

仍是如出一轍的結果。很奇怪。因而我打開了ODBC數據源的mylog和commonlog:

發現:
這個遊標竟然被關閉了!!!

難以想象。
因而我查詢了相關資料,獲得如下的認識:
3.ODBC數據源的cache size
每次執行SQLExecute或者SQLExecDirect函數執行SQL文時,程序底層是調用遊標一次性返回cache size大小行數的結果集到ODBC數據源的緩存。若是SQL文返回的結果集行數不超過cache size(即ODBC數據源的緩存能緩存下SQL文的全部結果集),那麼ODBC數據源會關閉該遊標。不然ODBC數據源會保持該遊標。

也就是說,這個是ODBC本身的緩存機制。

因而我修改再現程序:

(1) 調用SQLSetStmtAttr函數設置SQL文的SQL_ATTR_ROW_ARRAY_SIZE屬性值大於1;
(2) 調用SQLExecute或者SQLExecDirect函數執行SQL文;
(3) (2)中SQL文返回的行數大於ODBC數據源的cache size;
(4) 調用SQLFetch或SQLFetchScroll函數獲取結果集;
(5) (4)中返回的結果集的列數大於1;
(6) (4)中返回的結果集的行數大於1;
(7) 調用ODBC的API函數SQLSetPos或者SQLBulkOperations刪除(2)中返回的結果集中的數據;
(8) (7)中刪除的記錄行數超過一行

再現了該bug。

相關文章
相關標籤/搜索