DBI 資料庫模組剖析:Perl DBI 資料庫通訊模組規範,工作原理和例項

zk1878發表於2011-04-13

原文:http://tech.ddvip.com/2010-06/1275904179154934.html

     本文詳細地介紹了 Perl 語言中用於和資料庫通訊的 DBI 模組。以細膩的筆法和生動地示例給讀者講述了 DBI 模組的主要組成部分,結構和供使用者程式設計時呼叫的方法。同時,本文也涉及了一些 DBI 模組中的高階主題,如對儲存過程的呼叫,對併發事務的處理和資料庫及執行語句控制程式碼屬性的設定和後設資料的處理。除此之外,本文還對 DBD 模組的介面給予了入門級的介紹,希望可以對讀者在專研第三方資料庫的 DBD 模組時可以有所幫助。在本文的最後,通過定義了一套通用的 DBI 模組測試介面,對三種主流資料庫 DB2,Sybase 和 MySql 進行測試,通過比較測試結果,得出了 DBI 模組在這三種資料庫上的異同點和支援上的盲點。

  DBI 資料庫模組剖析

  為了和資料庫進行通訊,Perl 的社群開發出了統一資料庫通訊介面模組:DBI。DBI 作為 Perl 語言中和資料庫進行通訊的標準介面,它定義了一系列的方法,變數和常量,成功地提供一個和具體資料庫平臺無關的資料庫持久層。

  DBI 模組的體系結構

  概述

  整個 DBI 模組的結構可以被分成兩個主要的部分:DBI 模組本身和實現與具體資料庫平臺通訊的驅動模組。DBI 模組用於定義提供給 Perl 開發者使用的程式設計介面,和對不同資料庫驅動模組的呼叫方法。具體的資料庫通訊驅動模組的實現和特定的資料庫平臺有關,並且負責和具體資料通訊的實際操作。

  DBI 模組中的三種控制程式碼

  在 DBI 模組的定義中,使用者可以建立三種不同型別的控制程式碼。

  驅動模組控制程式碼(Driver Handle):驅動模組控制程式碼代表一個在記憶體中載入的驅動,它在 DBI 模組載入其對應的驅動模組被建立。它和實際的驅動模組之間是一一對應的關係。驅動模組控制程式碼提供了兩個主要的方法是 data_sources() 和 connect()。

  資料庫控制程式碼(Database Handles):資料庫控制程式碼是程式設計師使用 DBI 模組和後臺資料庫進行通訊的第一步,它包含了一個到特定資料庫的某個獨立的連線。表一給出了對於常見資料庫的連線字串:

  執行語句控制程式碼(Statement Handles):執行語句控制程式碼在 DBI 規範中被定義為和資料庫進行互動和操作的介面。這些控制程式碼包裝了一條條 SQL 語句,並將它們交送給後臺資料庫執行。一個使用執行語句控制程式碼執行 SQL 語句的例子見程式一。

表 1. 常見資料庫的連線字串

 

資料庫 連線字串(示例)
DBD::mysql “DBI:mysql:database=$dbname;host=$hostname;port=$port”;
DBD::Oracle “dbi:Oracle:$dbname”; “dbi:Oracle:host=$hostname;sid=$sid”;
DBD::DB2 “dbi:DB2:$dbname”;
DBD::Sybase “dbi:Sybase:host=$hostname;database=$dbname;port=$port”;

 

清單 1. 使用 DBI 模組連線資料庫並執行 SQL 語句

 $dbh = DBI->connect ($connection_string, $userid, $passwd); 
 $sth = $dbh->prepare (“SELECT * FROM tbl”); 
 $sth->execute(); 
 while (@row = $sth->fetchrow_array()) { 
 print “Each record: @row \n”; 
 } 
 $sth->finish(); 
 $dbh->disconnect(); 

  建立和釋放連線

  使用 DBI 模組提供的 connect()方法建立一個資料庫控制程式碼是,程式設計師必須提供一個資料來源用於指定要連線的資料庫。DBI 模組的規範要求資料來源的名字並須以 dbi: 開頭,然後接上資料庫通訊驅動模組的名字並且以‘ : ’結尾,比如‘ dbi:db2: ’。和 connect()方法相對應,DBI 模組中還定義了一個 disconnect()方法。

  通過呼叫 DBI->available_drivers()方法,程式設計師可以得到已安裝在當前機器上的所有資料庫通訊驅動模組的列表。接著用驅動模組名作為引數,呼叫 DBI->data_sources()方法,可以得到對應此驅動模組的所有資料來源的列表,程式二給出了一個這樣的例子 .

清單 2. 得到所有支援驅動模組的所有資料來源

 my @drivers = DBI->available_drivers(); 
 die “No dirvers defined! \n” unless @drivers; 
 foreach my $driver (@drivers) { 
 print “Driver: $driver \n”; 
 my @data_sources = DBI->data_sources ($driver) { 
 foreach my $data_source (@data_sources) { 
  print “\t Data Source: $data_source \n”; 
 } 
 print “\n”; 
 } 

  錯誤處理

  在 DBI 模組中提供了兩種錯誤處理的方法。第一種方法依靠程式設計師手工檢測被呼叫方法的返回值;第二種方法通過 DBI 模組對錯誤進行自動檢測,這種方法類似於程式的異常處理機制。對於手工錯誤處理,可以通過將“PrintError”和“RaiseError”兩個變數設定為 0 來啟用。在預設情況下,“PrintError”引數是被啟用的。

  方法一,在呼叫 DBI 的 connect()函式的引數中設定:

 %attr = (PrintError => 0, RaiseError=>0); 
 my $dbh = DBI->connect (“dbi:Oracle:testdb”, “username”, “password”, \%attr); 

  方法二,在資料庫控制程式碼中直接設定:

 $dbh->{PrintError} = 0; $dbh->{RaiseError} = 0; 

  對於自動錯誤檢測,DBI 提供了兩種不同級別的錯誤處理方法。用於控制程式碼的“PrintError”引數在被設定為 1 的時候,DBI 模組會呼叫 warn()函式進行錯誤處理。它會將錯誤資訊列印到螢幕,但是並不會中止程式。而用於控制程式碼的“RaiseError”引數在被設定為 1 的時候,DBI 模組會呼叫 die()函式,並且中止程式。這兩個不同級別的錯誤處理方法可以在 DBI 模組的任何一個有效的控制程式碼中被啟用。

  除了錯誤處理手段以外,DBI 模組還提供了對錯誤資訊進行診斷的方法。這些方法可以對於任何一個有效的控制程式碼進行使用,它們的返回值包括錯誤號和錯誤資訊。

 $rv = $h->err(); $str = $h->errstr(); $str = $h->state(); 

  $h->err()方法返回一個由底層資料庫生成的錯誤號;$h->errstr()方法則返回一個由底層資料庫生成的錯誤資訊描述;$h->state()方法返回一個 5 位的 SQLSTATE 錯誤字串。除了上面的三種方法會返回錯誤資訊供排錯之外,在 DBI 模組級別,$DBI::err,$DBI::errstr,$DBI::state 會返回和上述函式同樣的值。一個利用 DBI 模組內建錯誤處理方法的例子見程式三。

清單 3. 利用 DBI 模組的內建錯誤處理方法

 while (1) { 
 my $dbh; 
 # disable automatic error handle 
 until { 
 $dbh = DBI->connect ($connection_string, $userid, $username); 
 $dbh->{PrintError} = 0; 
 $dbh->{RaiseError} = 0; 
 warn “Unable to connect: $DBI::errstr. sleep for 5 minutes. \n”; 
 sleep (300); 
 } 
 # enable automatic error handle 
 eval { 
 $dbh->{RaiseError} = 1; 
 my $sth = $dbh->prepare (“SELECT foo, bar from tbl”); 
 while (1) { 
 $sth->execute(); 
 while ( my @row = $sth->fetchrow_array()) { 
 print “Row: @row \n”; 
 } 
 sleep 60; 
 } 
 } 
 warn “Monitoring aborted by error: $@\n” if $@; 
 sleep 5; 
 } 

  運算元據庫

  執行簡單查詢

  應用程式與資料庫之間最常見的操作是從資料庫中查詢並且提取資料。在標準的 SQL 語句規範中,這一過程是使用關鍵字 SELECT 的。

  一個通過 DBI 執行的標準簡單查詢包括以下四個階段:

  準備階段(Perpare SQL statement):

  通過呼叫 prepare()方法,準備階段解析 SQL 語句,對 SQL 語句進行驗證,並且返回一個執行語句控制程式碼,這個控制程式碼代表將在資料庫內被執行的這條 SQL 語句。

  執行階段(Execute select statement):

  通過呼叫 execute()方法,執行階段執行 SQL 語句,查詢資料庫,並且以被查詢的資料填充 Perl 的資料結構。但是,在這一階段中,你的 Perl 應用程式並未真正地訪問到被查詢的資料。

  資料抽取階段(fetching date):

  第三階段被稱為資料抽取階段,在這一階段實際的資料從資料庫中被抽取出來,通過呼叫 fetch()方法族的一組方法。資料抽取階段從資料庫得到查詢所得的資料,以每一條資料為單位,注入 Perl 的資料結構。DBI 提供了多種方法對資料進行抽取,可以將被抽取的資料用一個列表,一個指向陣列的引用,或是一個指向雜湊表的引用的方式提供給應用程式。而且每條記錄中欄位的順序也是由 SQL 語句中指定的順序所決定。一個包含三種方法的例子見程式四。

  完成階段(finishing date fetch):

  最後一個階段被稱為完成階段,這個階段主要釋放資源,並且清理相關資料結構中儲存的歷史資訊,通過顯式地呼叫 finish()方法來完成。當一個執行語句控制程式碼(statement handler)被成功執行後,它的狀態會被標記成為活躍。你可以通過訪問執行語句控制程式碼的 Active 屬性來訪問它。在使用者執行 fetch()方法,從資料庫中抽取了最後一列資料之後,資料庫驅動自動關閉資料庫中正在進行地和這個執行語句控制程式碼有關的工作,並且重置 Active 屬性為不活躍狀態。這一切工作都是在讀取了最後一列資料之後被自動觸發,在大多數情況下,使用者並不需要額外地關心這一過程中後臺所作的工作。某些額外情況,需要應用程式主動呼叫 finish()方法釋放資源。一個典型的例子就是:當資料庫佔用了數目可觀的磁碟空間儲存臨時檔案,存放查詢結果,而應用程式又不需要儲存所有的查詢結果。比如執行一條形如 “SELECT EMP_DEP, count(*) FROM EMP GROUP BY EMP_DEP ORDER BY count(*) DESC”的 SQL 語句,在應用程式只需要部分統計結果的情況,應該顯示呼叫 finish()方法,釋放被申請和佔用的機器資源。

清單 4. 三種不同的資料抽取方法

 $sth->execute(); 
 # fetch data by an array 
 while ( @row = $sth->fetchrow_array() ) { 
 print “Column1: $row[0] \t Column2: $row[1]. \n”; 
 } 
 # fetch data by a reference pointing to array 
 while ( $array_ref = $sth->fetchrow_arrayref() ) { 
 print “Column1: $array_ref->[0] \t Column2: $array_ref->[1]. \n”; 
 } 
 # fetch data by a reference pointing to hash table 
 while ( $hash_ref = $sth->fetchrow_hashref() ) { 
 print “Column1: $hash_ref->{column1} \t Column2: $hash_ref->{column2}. \n”; 
 } 

  執行非查詢語句

  在資料庫的常用 DML 語句中,除了 SELECT 語句之外,還有 INSERT,DELETE,UPDATE 三種,我們統稱這三種語句為非查詢語句。與查詢語句 SELECT 不同,它們只是改變了資料庫中的部分紀錄,而不會返回一個記錄集給應用程式。所以,相對對於查詢語句中的 prepare-execute-fetch-deallocate 序列來說,非查詢語句不需要資料抽取階段,同時也可以將 prepare 和 execute 階段用一個 do()方法來完成。一個呼叫 do()方法的例子如下 :

 $affected_row_number = $dbh->do (“DELETE FROM tbl WHERE foo = ‘ bar ’”); 

  DBI 模組提供了一個 do()方法用以簡化工作,取代了原先需要被呼叫的 prepare()和 execute()方法。實際上,DBI 模組中的 do()方法只是簡單地包含了 prepare()和 execute()方法。這種方法在使用添寫方法生成 SQL 執行語句的時候和分別使用 prepare()方法和 execute()方法沒有任何效能上面的區別,但是如果使用了引數繫結的 SQL 執行語句生成方法,兩者在效能上將會有顯著地差別。因為使用 do()方法的話,以 INSERT 操作為例,對於每條被插入的記錄,資料庫必須解析每條插入語句,並且生成執行計劃。然而使用 prepare()方法的話,可以在 prepare()方法中使用佔位符,而使得所有的插入語句可以共用一個執行計劃,達到了提升效率的目地,具體的例子見程式六。

  引數繫結

  在準備階段,有一個重要的概念就是資料繫結,與之相關的有三個術語:佔位符(placeholder),引數(parameter)和繫結(binding),他們都是用來根據程式上下文動態地生成 SQL 語句。將變數放入 SQL 語句的方法有兩種:第一種通過添寫方法生成 SQL 語句(interpolated SQL statement creation),該方法將變數直接通過字串和 SQL 語句的其它部分連線起來,生成可以被執行的 SQL 語句。程式五展示了一個利用 Perl 字串技術生成的一個 SQL 執行語句。

清單 5. 使用 Perl 字串技術動態生成 SQL 執行語句的例子

 foreach $table_name ( qw (table1, table2, table3) ) { 
 $sth = $dbh->prepare(“SELECT count(*) FROM $table_name”); 
 $sth->execute(); 
 } 

  第二種方法在 SQL 語句中使用佔位符,並且通過 bind_param()方法將變數與之繫結,生成 SQL 語句。無論何時,bind_param()方法必須在 execute()方法之前被呼叫,否則被繫結的引數無法填入 SQL 語句中,而對 SQL 語句的呼叫也註定會失敗。採用繫結方法是成 SQL 語句(Bind value SQL statement creation)的一個典型的例子見程式六。

清單 6. 使用佔位符動態生成 SQL 執行語句的例子

 $sth = $dbh->prepare (“SELECT foo, bar FROM table WHERE foo = ? AND bar = ?”); 
 $sth->bind_param (1, “FOO”); 
 $sth->bind_param (2, “BAR”); 
 $sth->execute(); 

  第一種方法使用 Perl 的字串處理函式,生成一條完整的 SQL 語句,並將它送往後臺資料庫;而繫結方法則不同,它將含有佔位符的 SQL 語句和繫結值分開傳送給資料,在資料後臺處理資料繫結,然後執行繫結後的 SQL 語句。兩種不同的處理方法將會帶來效能上的巨大差異,尤其是在有大量相似的 SQL 語句需要被處理的情況下。主流的大型資料庫都有一種被稱為“Shared SQL Cache”的部件,它儲存了諸如查詢語句執行計劃之類的輔助資料結構,幫助資料庫執行 SQL 語句。在得到一個新的處理 SQL 語句的請求時,如果 SQL 語句已經存在於“Shared SQL Cache”中,資料庫就不需要重新處理這條語句,而可以重用 Cache 中儲存的資訊,這樣就可以帶來效能上的顯著提升。

  呼叫儲存過程

  儲存過程執行在後臺資料庫上,可以有效地減少客戶端和資料庫之間的通訊量。在這種工作模式下,客戶端不再需要將每一條需要被執行的 SQL 語句發往後臺資料庫,藉助儲存過程可以將所有要執行的 SQL 語句定義在一個儲存過程內,統一執行並且返回運算結果給客戶端。

  在 DBI 模組中有一個和 bind_param()方法相似的方法,叫做 bind_param_inout()方法,可以從執行語句控制程式碼中直接返回值。這個方法最主要的應用在於呼叫儲存過程用來接收引數和返回結果。需要注意的是,某些資料庫(比如 MySql)不支援這種方法。bind_param_inout()方法通過傳遞引用的方式向資料庫傳入一個可以接受返回值的引數,一個簡單的例子如下:

 $sth->bind_param_inout (1, \$bar, 50); 

  上面例子當中,bind_param_inout()方法的第三個引數是返回值的最大長度。

  一個在 DB2 資料庫中呼叫儲存過程的完整示例見程式七。

清單 7. 呼叫 DB2 資料庫中儲存過程的例子

 create procedure proc (in foo char(6), out bar integer) 
 specific proc_example 
 dynamic result sets 0 
 modifies sql data 
 not deterministic 
 language sql 
 begin atomic 
 insert into tbl ( ‘ foo ’ , current date); 
 select count(*) into bar from tbl where foo = ‘ foo ’ ; 
 end 
 
 # start perl script to call sql procedure 
 $sth = $dbh->prepare (“CALL proc (?,?)”); 
 $sth->bind_param (1, $foo); 
 $sth->bind_param_inout (2, \$bar, 50); 
 $sth->execute(); 
 print “stored procedure returned $bar. \n”; 

  執行資料庫事務

  資料庫事務是一種將一組相互之間有密切關係的 SQL 語句放到一起執行的技術,它們要麼都被成功執行,要麼都執行失敗,我們稱之為“all-or-nothing”模式。一個事務從它的第一條可被執行的 SQL 語句開始,到被提交(commit)或者回滾(rollback)結束。如果一個事務被提交,那麼它對資料庫所作的所有修改都會被儲存並且對其它併發過程可見;如果一個事務被回滾,它對資料庫所作的所有修改都會被放棄。

  並不是所有的資料庫軟體都支援事務,但是對於所有支援事務的資料庫,DBI 模組提供了統一的介面用於操作事務。儘管資料庫的實現各不相同,DBI 模組提供了事務自動提交(auutomatic transcation committing)和手工事務處理(powerful manual transaction)的兩種不同處理方式。如果使用者在建立一個資料庫控制程式碼(database handle)時,將控制程式碼的引數“AutoCommit”設定為 1,那麼通過這個資料庫控制程式碼執行的每一條 SQL 語句的操作都會被立即提交,而不需要任何顯式語句進行提交或回滾;反之,如果引數“AutoCommit”被設定為 0,則每個事物都必須以顯示地呼叫 commit()方法或 rollback()方法來結束。如果後臺資料庫不支援事務處理,那麼當使用者試圖修改“AutoCommit”引數為 0 的時候,DBI 模組將會丟擲錯誤。在 DBI 模組中定義了 commit()方法,用來顯式地提交在一個事務範圍內對資料庫所作的修改。該方法通過資料庫控制程式碼來呼叫,如下:

 $dbh->commit(); 

  如果呼叫 commit()方法的資料庫控制程式碼中的“AutoCommit”屬性被設定為 1,那麼呼叫 commit()方法之後會得到一個“commit ineffective with AutoCommit”的警告資訊。如果後臺資料庫不支援事務處理,那麼“AutoCommit”屬性預設開啟,每次呼叫 commit()方法之後也會得到一個相同的警告資訊。與之相似的是 rollback()方法,每次呼叫 rollback()方法會回滾事務範圍內對資料庫所作的所有修改。如果“AutoCommit”設定為一或者是後臺資料庫不支援事務處理,同樣的警告資訊也將會被返回。一個呼叫 rollback()方法的例子如下:

 $dbh->rollback(); 

  不幸的事情是,當“AutoCommit”屬性被設定為 0 的時候,顯式地呼叫 disconnect()方法終止和資料庫的連線後觸發的行為無法預測。在某些資料庫的版本中,在 disconnect()方法被 DBI 模組呼叫之前,會自動呼叫 commit()方法,提交所有對資料庫的修改;也有某些資料庫版本會呼叫 rollback()方法,回滾所有對資料庫所作的修改。一個綜合示例見程式八。

清單 8. DBI 模組處理資料庫事務的詳細例子

 $dbh->connect(); 
 $dbh->{AutoCommit} = 0; 
 $dbh->{RaiseError} = 1; 
 eval { 
 load_some_data_to_database(); 
 insert_some_data_to_database(); 
 delete_some_data_from_database(); 
 $dbh->commit() 
 } 
 if ($@) { 
 $dbh->rollback(); 
 } 
 $dbh->disconnect(); 

  DBI 模組中的高階主題

  DBI 中的控制程式碼屬性

  除了與資料庫控制程式碼和執行語句控制程式碼繫結的一組方法以外,DBI 模組也提供了一組和這些控制程式碼有關的屬性,供使用者對他們的執行環境進行調優。這些控制程式碼屬性的實質是一個由“鍵 / 值”對組成的雜湊表,可以通過和操作雜湊表引用一樣的方法來訪問和修改這些屬性。一個典型的例子見程式九。

清單 9. 修改和顯示控制程式碼的屬性

 $dbh->connect($connect_string, $userid, $passwd); 
 $dbh->{AutoCommit} = 1; 
 print “AutoCommit: $dbh->{AutoCommit} \n”; 

  當使用者訪問或者設定這些控制程式碼屬性時,DBI 模組自動檢查使用者輸入的屬性名稱。如果使用者試圖引用一個位置屬性時,DBI 模組將會產生一個錯誤。同理,如果使用者試圖修改一個只讀屬性時,DBI 模組會以同樣的方式丟擲一個錯誤。無論使用者有沒有在建立資料庫控制程式碼時設定了“RaiseError”屬性,在檢測到上述錯誤之後,DBI 模組都會使用 die()方法丟擲錯誤,返回一個錯誤值。

  控制程式碼屬性的命名規則

  控制程式碼屬性的命名看似雜亂無章,其實微言大義,在 DBI 模組的相容性問題發揮了重要的作用。控制程式碼屬性的大小寫命名規則直接反映了是誰定義了這個屬性並對其賦值,其規則有三:

  全大寫命名方式(UPPER_CASE):全大寫屬性通常是由外部標準所定義的,比如說 ISO SQL 或者 ODBC。

  混合命名方式(Mixed_Case):此類屬性的名字通常由大寫字母開頭,但是其中也混有小寫字母。這類屬性通常由 DBI 模組標準自身定義。

  全小寫命名方式(lower_case):此類屬性由各自的資料庫驅動所定義,被稱為“驅動相關”屬性。

  公共控制程式碼屬性

  表二列出了部分最常見的資料庫控制程式碼(database handle)和執行語句控制程式碼(statement handle)都支援的控制程式碼屬性。

表 2. 常見公共控制程式碼屬性

 

屬性名 說明
PrintError 如果此值被設定為 1,當 DBI 模組返回一個錯誤結果的時候,使用 warn()方法;
RaiseError 如果此值被設定為 1,當 DBI 模組一個錯誤結果的時候,使用 die()方法;

 

  資料庫控制程式碼的屬性和資料庫後設資料

  表三列出了最常見的資料庫控制程式碼所支援的控制程式碼屬性。

表 3. 常見資料庫控制程式碼屬性

 

屬性名 說明
AutoCommit 如果設定為 1,每一條通過 DBI 模組執行的結果都會被立即提交;反之,如果設定為 0,
所有的執行結果都要用 commit()方法顯示提交或用 rollback()方法回滾;
Name 只讀屬性,儲存資料庫的名字;

 

  資料庫原資料(database metadata)被稱為“描述資料的資料”,用來描述資料本身。當使用者有動態生成 SQL 執行語句或者建立動態檢視需求的時候,資料庫後設資料所提供的資訊顯得格外地重要。不用資料庫廠商儲存資料庫後設資料的方法和儲存的資料庫後設資料各不相同,大多數主流資料提供了一個系統目錄(system catalog),通過表和檢視儲存這些資訊。儘管因為上述的種種差異,使得 DBI 模組提供一個統一的訪問資料庫後設資料的介面成了一件任重道遠的任務,在 DBI 模組的定義中依然提供了兩個可供呼叫的方法,用來訪問資料庫後設資料。

  第一個方法 $dbh->tables()返回一個包含呼叫它的資料庫控制程式碼(database handle)所能發現的所有表和檢視的陣列,見程式十。

清單 10. 利用資料庫控制程式碼返回資料庫的後設資料

 my @tables = $dbh->tables(); 
  foreach my $table (@tables) { 
   print “Table Name: ” . $table . “ \n”; 
  } 

  第二個方法 $dbh->table_info()返回一個包含更多詳細資訊的執行語句控制程式碼(statement handle),可以通過和訪問普通執行語句控制程式碼一樣的方法,抽取其中的資訊。

  執行語句控制程式碼屬性和表原資料

  同資料庫控制程式碼一樣,執行語句控制程式碼(statement handle)也有自己的控制程式碼屬性。執行語句控制程式碼從建立它的資料庫控制程式碼繼承了部分屬性,並且它的大部分屬性是用來表示語句的執行狀態和結果的,屬於只讀屬性。表四列出了常見的執行語句屬性:

表 4. 常見執行語句控制程式碼屬性

 

屬性名 說明
NUM_OF_FIELDS 被 SELECT 語句返回的欄位的數量;
NAME 被 SELECT 語句返回的欄位的名稱;
NULLABLE 某個欄位是否可以為空;
TYPE 某個欄位的型別;
NUM_OF_PARAMS 一個 SQL 執行語句所使用的佔位符的數量;

 

  通過讀取某些執行語句的控制程式碼屬性,可以得到表的原資料,示例如下:

清單 11. 利用執行語句控制程式碼返回表的後設資料

 my $sth = $dbh->prepare (“SELECT * FROM tbl”); 
 $sth->execute(); 
 my $field_number = $sth->{NUM_OF_FIELDS}; 
 print “NUMBER OF FIELDS: $field_number \n”; 
 print “Column Name                 Type  Nullable? \n”; 
 print “--------------------------------------- ---- --------- \n”; 
 for (my $index=0; $index<$field_number; $index++) { 
 my $name = $sth->{NAME}->[$index]; 
 my $type = $sth->{TYPE}->[$index]; 
 my $nullable = (“No”, “Yes”, “Unknown”)[$sth->{NULLABLE}->[$index]]; 
 printf “%-30s %4d $s \n”, $name, $type, $nullable; 
 } 
 $sth->finish(); 

  符合 DBI 模組介面的 DBD 模組

  DBI 模組定義了一組和使用者互動的介面,而 DBD 實現與資料庫之間的具體介面。兩者之間的關係可以類比為物件導向程式設計中介面和繼承介面的類之間的關係。有兩種不同的資料庫驅動程式的實現方式,第一種是基於純粹的 Perl 語言,不依賴任何 C 語言編譯器。這種方法實現最為簡單,卻不被大多數的資料庫所支援,比較典型的 DBD 模組例子有 DBD::File 和 DBD::CSV。另一種方法更為普遍,使用了 C 程式的幫助和資料庫進行通訊,被稱為 C/XS 驅動程式。

  對於純粹 Perl 語言開發的資料庫驅動而言,整個 DBD 模組的核心在一個 DBD::Driver.pm 的模組上。根據實際要連線的資料庫的不同,Driver 的名字可以被替換成為 DBD::Oracle 或者 DBD::DB2。在 DBD::Driver 包中,通常包含子包,不同的子包有著不同的作用,關於它們的詳細資訊見表五。

表 5. 典型的 DBD 模組的內部設計

 

包名 作用 函式表(部分)
DBD::Driver 提供 DBD 級別的方法 driver()
DBD::Driver::dr 提供驅動模組控制程式碼級別的方法 connect()
data_sources()
DBD::Driver::db 提供資料庫控制程式碼級別的方法 do()
prepare()
table_info()
ping()
rollback()
commit()
FETCH()
STORE()
DBD::Driver::st 提供執行語句控制程式碼級別的方法 execute()
bind_param()
fetchrow_array()
fetchrow_arrayref()
fetchrow_hashref()

 

  基於 C/XS 驅動程式的 DBD 模組十分複雜,在這裡不作介紹。

  基於 MySql 資料庫的例子

  我們對 MySql 資料庫進行了簡單的測試。測試的結果見表六。

表 6. MySql 資料庫對 DBI 模組的測試結果

 

測試用例 目的 問題
test_dbh_and_sth() 測試資料庫控制程式碼和執行語句控制程式碼;
查詢,插入和刪除資料庫記錄;
欄位名稱要全部小寫;
test_fetch_metadata_of_table() 測試獲取資料標的原資料; 部分資訊錯誤;
test_call_sql_procedure() 測試呼叫 SQL 儲存過程; bind_param_inout()函式無法正常工作,要得到儲存過程的返回值需要特殊處理;

 

  基於 DB2 資料庫的例子

  同樣我們也有一個 SQL 指令碼用於在 DB2 資料建立測試環境。(見附件三)測試結果見表六。

  使用 DBI 模組連線 DB2 資料庫的示例見程式十二。

表 7. DB2 資料庫對 DBI 模組的測試結果

 

測試用例 目的 問題
test_drh() 測試驅動程式控制程式碼; N/A
test_dbh_and_sth() 測試資料庫控制程式碼和執行語句控制程式碼;
查詢,插入和刪除資料庫記錄;
欄位名稱要全部大寫;
test_mixed_error_checing() 測試 DBI 模組的錯誤處理機制; N/A
test_bind_parameter_to_statement() 測試執行語句後期繫結功能; N/A
test_fetch_metadata_of_table() 測試獲取資料標的原資料; 部分資訊錯誤;
test_call_sql_procedure() 測試呼叫 SQL 儲存過程; N/A
test_run_transaction() 測試通過 DBI 模組執行事務; N/A

 

清單 12. 使用 DBI 模組連線 DB2 資料庫

 sub setup_connections { 
 my $connections_reference = shift; 
 my $passwd = "xxxxxxxx"; 
 
 my $db2_connection_string = "dbi:DB2:test"; 
 my $db2_userid = "db2inst1"; 
 my %db2_connection = (); 
 $db2_connection{dbName} = "DB2"; 
 $db2_connection{connStr} = $db2_connection_string; 
 $db2_connection{userid} = $db2_userid; 
 $db2_connection{passwd} = $passwd; 
 push (@{$connections_reference}, \%db2_connection); 
 } 

  總結

  本文詳細地介紹了 Perl 語言中用於和資料庫通訊的 DBI 模組。以細膩的筆法和生動地示例給讀者講述了 DBI 模組的主要組成部分,結構和供使用者程式設計時呼叫的方法。同時,本文也涉及了一些 DBI 模組中的高階主題,如對儲存過程的呼叫,對併發事務的處理和資料庫及執行語句控制程式碼屬性的設定和後設資料的處理。除此之外,本文還對 DBD 模組的介面給予了入門級的介紹,希望可以對讀者在專研第三方資料庫的 DBD 模組時可以有所幫助。在本文的最後,通過定義了一套通用的 DBI 模組測試介面,對三種主流資料庫 DB2,Sybase 和 MySql 進行測試,通過比較測試結果,得出了 DBI 模組在這三種資料庫上的異同點和支援上的盲點。

相關文章