程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 數據庫知識 >> MYSQL數據庫 >> 關於MYSQL數據庫 >> Perl DBI 基礎

Perl DBI 基礎

編輯:關於MYSQL數據庫

DBI 數據類型

    從某些方面來說,使用Perl DBI API 類似於使用第6章介紹的C 客戶機庫。在使用C 客戶機庫時,主要依靠指向結構或數組的指針來調用函數和訪問與MySQL相關的數據。在使用DBI API 時,除了函數稱為方法,指針稱為引用外,也調用函數和使用指向結構的指針。

指針變量稱為句柄,句柄指向的峁鉤莆韻蟆?br>     DBI 使用若干種句柄。它們往往通過表7-1所示的慣用名稱在DBI 文件中引用。而慣用的非句柄變量的名稱如表7 - 2所示。實際上,在本章中,我們並不使用每個變量名,但是,在閱讀其他人編寫的DBI 腳本時,了解它們是有用的。

表7-1慣用的Perl DBI 句柄變量名

名稱說明 $dbh 數據庫對象的句柄 $sth 語句(查詢)對象的句柄 $fh 打開文件的句柄 $h “通用”句柄;其意義取決於上下文表7-2 慣用的Perl DBI 非句柄變量的名稱

名稱說明 $rc 從返回真或假的操作中返回的代碼 $rv 從返回整數的操作中返回的值 $rows 從返回行數的操作中返回的值 @ary 查詢返回的表示一行值的數組(列表)

一個簡單的DBI 腳本

    讓我們從一個簡單腳本d um p _ member s開始,它舉例說明了DBI 程序設計中若干標准概念,如與MySQL服務器的連接和斷開、檢索數據等。此腳本產生的結果為以制表符分隔形式列出的歷史同盟成員。這個格式本身並不讓人感興趣:在這裡,了解如何使用DBI 比產生漂亮的輸出更為重要。

    dump_members 如下:
(study.iader.com)





    要想自己試驗這個腳本,可以下載它(請參閱符錄A),或使用文本編輯器創建它,然後使之可執行,以便能運行。當然,可能至少需要更改一些連接參數(主機名、數據庫名、用戶名和口令)。本章中的其他DBI 腳本也是這樣。在參數缺省時,本章下載腳本的權限設置為只允許讀。如果您將自己的MySQL用戶名和口令放在它們之中,我建議將它們保留為這種方式,以便其他人不能讀取這些值。以後,在7 . 2 . 8節“指定連接參數”中,我們將看到如何從選項文件中獲得這些參數,而不是將它們直接放在腳本中。

    現在,讓我們逐行看完這個腳本。第一行是標准行,指出哪裡可以找到Perl 的指示器:

    #! /usr/bin/perl

    在本章將要討論的腳本中,每個腳本都包含這行;以後不再說明。此腳本中至少應該含有一個簡短的目的說明,這是一個好主意,所以下一行是一個注釋,給閱讀此腳本的人提供一個關於它做什麼的線索:

    # dump_members.dump Historical League's membership list

    從‘#’字符到行尾部的文本為注釋。有必要做一些練習,就是在整個腳本中編寫一些注釋來解釋它們如何工作。

    接下來是兩個use 行:

    use DBI;

    use strict;

    use DBI 告知Perl 解釋程序它需要引入DBI 模塊。如果沒有這一行,試圖在腳本中做與DBI 相關的任何事,都將出現錯誤。不需要指出想要哪個DBD 級別的模塊。在連接數據庫時,DBI 會激活相應的模塊。

    use strict 告知Perl,在使用它們之前需要聲明變量。如果沒有use strict 行,也可以編寫腳本,但是,它有助於發現錯誤,所以建議始終要包括這行。例如,置為嚴格模式時,如果聲明變量$ my _ v a r,但是之後錯誤地用$mv_var 來訪問,則在運行這個腳本時,將獲得下面的消息:

    Global symbol "$mv_var" requires explicit package name at line n

    這個消息會使您想,“怎麼了?$ m v _ v a r?我從未使用過這種名稱的變量!”,然後,找到腳本中的第n行,看是什麼問題,並改正它。如果不用嚴格模式, Perl 不會給出$ m v _ v a r;將只是簡單地按具有un d e f(未定義的)值的該名稱創建一個新的變量,並毫無動靜地使用它,然後,您會莫名其妙腳本為什麼不工作。

    因為我們在嚴格模式下操作,所以我們將定義腳本使用的變量:



    現在我們准備連接數據庫:



    connect( ) 調用作為DBI->connect( ) 來調用,因為它是DBI 類的方法。不必真正知道它是什麼意思;它只是一個使人頭痛的面向對象的行話(如果的確想知道,那麼它意味著connect( ) 是“屬於”DBI 的一個函數)。connect( ) 有若干參數:

    數據源。(經常調用的數據源名稱,或D S N。)數據源格式由要使用的特定DBD 模塊需求來確定。對於MySQL驅動程序,允許的格式如下:

    "DBI:MySQL:db_name"

    "DBI:MySQL:db_name:host_name"

    對於第一種格式,主機名缺省為localhost(實際上有其他允許的數據源格式,我們將在後面7 . 2 . 8節“指定連接參數”中討論)。“DBI”大寫沒關系,但是“ MySQL”必須小寫。

    用戶名和口令。

    表示額外連接屬性的可選參數。這個參數控制DBI 的錯誤處理行為,我們指定的看起來有點奇怪的構造啟用了RaiseError 屬性。這導致DBI 檢查與數據庫相關的錯誤,並顯示消息,而且只要它檢測到錯誤就退出(這就是為什麼在dump_members 腳本中的任何地方都沒有看到錯誤檢查代碼的原因; DBI 將它全部處理了)。7 . 2 . 3節“處理錯誤”包括了對錯誤響應的可選方法。

   如果connect( ) 調用成功,則它返回數據庫句柄,我們分配給$dbh(如果connect( ) 失敗,通常返回un d e f。然而,因為我們在腳本中啟用了R a i s e E r r o r,所以connect( )不返回;但是,DBI 將顯示一條錯誤消息,並且在出現錯誤時退出)。

    連接到數據庫後, dump_members 發布一條SELECT 語句查詢來檢索全體成員列表,然後,執行一個循環來處理返回的每一行。這些行構成了結果集。

    為了完成S E L E C T語句,首先需要准備,然後再運行它:

    # issue query

    $sth=$dbh->prepare("SELECT last_name,first_name,suffix,email,"

         "street,city,state,zip,phone FROM member ORDER BY last_name");

    $sth->execute();

    利用數據庫句柄調用prepare( );在執行前,它將SQL 語句傳遞給預處理的驅動程序。實際上,在這裡某些驅動程序做了一些有關這條語句的事情。其他驅動程序只是記住它,直到調用execute( ) 使這條語句被執行為止。從prepare( ) 返回的值是一個語句句柄$ s t h,如果出現錯誤,則為un d e f。在進一步處理與這條語句相關的所有內容時,都使用這個語句句柄。

    請注意,指定的這個查詢沒有分號結束符。您無疑有這樣的(經過長時間使用MySQL程序養成的)習慣,用‘ ;’字符終止SQL 語句。然而,在使用DBI時,最好打破這個習慣,因為分號經常導致查詢出現語法錯誤而失敗。向查詢增加‘ \ g’也類似,使用DBI 時不要這樣。

    在調用一個方法而不用向它傳遞任何參數時,可以沒有這個圓括號。下列兩個調用是等價的:

    $sth->execute();

    $sth->execute;

    我寧願有圓括號,因為它使人感到這個調用看上去不像變量。您的選擇就可能不同了。

    調用execute( ) 後,可以處理成員列表的行。在dump_members 腳本中,提取行的循環簡單地顯示了每行的內容:
-



    fetchrow_array( ) 返回含有當前行的列值的數組,在沒有剩余的行時,返回一個空數組。這樣,此循環提取了由SELECT 語句返回的連續行,並顯示列值之間用制表符分隔的每一行。在數據庫中NULL 作為undef 值返回到Perl 腳本,但是將它們顯示為空字符串,而不是單詞“NULL”。

    請注意,制表符和換行符(表示為‘ \ t’和‘\ n’)括在雙引號中。在Perl 中,只解釋出現在雙引號內的轉義符序列,不解釋出現在單引號內的轉義符序列。如果使用單引號,則輸出將為字符串“\ t”和“\ n”。

    提取行的循環終止以後,調用finish( ) 告知DBI 不再需要語句句柄,並且釋放分配給它的所有臨時資源。實際上,除非只提取結果集的一部分(無論是設計的原因,還是因為出現一些問題),否則不需要調用finish( )。然而,在提取循環之後, finish( ) 始終是很保險的,我認為調用並執行finish( ),比區分何時需要,何時不需要更容易一些。

    我們已經顯示完了全部成員列表,所以我們可以從服務器上斷開連接,並且退出:

    $dbh->disconnect();

    exit(0);

    dump_members 示出了許多DBI 程序的大多數通用概念,而且不必了解更多的知識,就可以著手編寫自己的DBI 程序。例如,要想寫出一些其他表的內容,所需要做的只是更改傳遞給prepare( ) 方法的SELECT 語句的文本。而且實際上,如果想了解這種技術的某些應用,可略過這部分,直接跳到7 . 3節“運行DBI”中討論如何生成歷史同盟一年一度的宴會成員列表程序和League 打印目錄的部分。然而,DBI 提供許多其他有用的功能。下一節介紹了一些,以便能夠在Perl 腳本中看看如何完成比運行一條簡單的SELECT 語句更多的事情。

處理錯誤

    在dump_members 調用connect( )方法時,應該啟用RaiseError 錯誤處理屬性,以便這些錯誤用一條錯誤消息就能自動地終止相應的腳本。也可以用其他方式處理這些錯誤。例如,可以自己檢查錯誤而不必使用DBI。

    為了查看如何控制DBI 的錯誤處理行為,我們來仔細查看一下connect( ) 調用的最終參數。下面兩個相關的屬性是RaiseError 和P r i n t E r r o r:

    如果啟用R a i s e E r r o r(設為非零值),如果在DBI 方法中出現錯誤,則DBI 調用dIE( ) 來顯示一條消息並且退出。

    如果啟用P r i n t E r r o r,在出現DBI錯誤時,DBI 會調用warn( ) 來顯示一條消息,但是相應腳本會繼續執行。

    缺省時, RaiseError 是禁用的,而PrintError 啟用。在此情況下,如果connect( )調用失敗,則DBI 顯示一條消息,而且繼續執行。這樣,如果省略connect( ) 的四個參數,則得到缺省的錯誤處理行為,可以如下檢查錯誤:

    $dbh=DBI->connect($dsn,$user_name,$passWord) or exit (1);

    如果出現錯誤,則connect( ) 返回undef 表示失敗,並且觸發對exit( ) 的調用。因為DBI 已經顯示了錯誤消息,所以您就不一定要顯示它了。

    如果明確給出該錯誤檢查屬性的缺省值,可如下調用connect( )。

    $dbh=DBI->connect($dsn,$user_name,$passWord,{RaiseError=>0,PrintError=>1})

    or exit (1);

    這就需要更多的編寫工作,但是即使對不經意的讀者,處理錯誤行為也會更為明顯。

    如果想自己檢查錯誤,並顯示自己的消息,應該禁用RaiseError 和P r i n t E r r o r:



    變量$DBI::err 和$ DBI : :er r s t r,只用於所顯示的dIE( ) 調用中,有助於構造錯誤消息。它們含有MySQL錯誤代碼和錯誤字符串,非常像C API 函數中的mysql_errno( ) 和MySQL_error( )。

    如果僅僅要DBI 處理錯誤,以便不必自己檢查它們,則啟用R a i s e E r r o r:

    $dbh=DBI->connect ($dsn,$user_name,$passWord,{RaiseError=>1});

    避免啟用RaiseError 屬性的另一個原因是DBI 在它的消息中顯示技術信息,如下:

    disconnect(DBI::db=HASH(0x197aae4)invalidates 1active statement.Either

    destroy statement handles or call finish on them before disconnecting.

    對於編程者來說,這是好的信息,但對普通用戶可能沒有什麼意義。在此情形,最好自己檢查錯誤,以便可以顯示對期望使用這個腳本的人更有意義的消息。或者也可在這裡考慮重新定義$SIG{_DIE_} 句柄。這樣可能很有用,因為它允許啟用RaiseError 來使錯誤處理簡單化,而不是用自己的消息替換DBI 給出的缺省錯誤消息。為了提供自己的_DIE_ 句柄,可在執行任何DBI 調用以前,進行下面的工作:

    $SIG{_DIE_}=sub{dIE "Sorry,an error occurred\n";};

    也可以用普通的風格定義一個子例程,並利用這個子例程的引用來設置這個句柄值:



    除了在connect( ) 調用中逐字傳遞錯誤處理屬性之外,還可以利用散列定義它們,並傳遞對這個散列的引用。有人發現以這種方式准備屬性設置使腳本更容易閱讀和編輯,但是在功能上這兩種方法是相同的。下面是一個說明如何使用屬性散列的樣例:



    下面的腳本dump_members2 舉例說明了當要自己檢查錯誤並顯示自己的消息時,如何編寫腳本。dump_member2 處理和dump_members 一樣的查詢,但是明確地禁用PrintError 和R a i s e E r r o r,然後測試每個DBI 調用的結果。如果出現錯誤,在退出以前,腳本調用了子例程bail_out( ) 顯示消息及$DBI::err 和$DBI::errstr 的內容:





處理不返回結果集的查詢



    if (!defined ($rows)){#error}

    if (!$rows) {#error}

    在數值環境中,“0 E 0”與0 等價。下面的代碼將正確地顯示$rows 的任何非undef 值的行數:



    也可以用printf( ) 使用‘% d’格式顯示$row 來強制進行隱含的數字轉換:



    do( ) 方法等價於後跟execute( ) 的prepare( )。前面的INSERT 語句可以不調用do( ),如下發布:



處理返回結果集的查詢

    本章提供了有關實現SELECT 查詢中提取行循環的若干選項的詳細信息(或其他類似於SELECT 的返回行的查詢,如DESCRIB E、EXPLAIN 和S H O W )。還討論了如何獲得結果中行數的計數值,如何處理不需要循環的結果集,以及如何一次檢索整個結果集的全部內容等。

    1. 編寫提取行的循環

    dump_members 腳本利用DBI 方法的標准序列檢索數據:prepare( ) 使驅動程序處理查詢,execute( ) 開始執行這個查詢, fetchrow_array( ) 提取結果集中的每一行, finish( ) 釋放與這個查詢相關的資源。

    prepare( )、execute( ) 和finish( ) 是處理返回行的查詢中非常標准的部分。然而,對於提取的行,fetchrow_array( ) 實際上只是若干方法中的一種(請參閱表7 - 3)。

方法名返回值 fetchrow_array( ) 行值的數組 fetchrow_arrayref( ) 對行值數組的引用 fetch( ) 與fetchrow_arrayref( ) 相同 fetchrow_hashref( ) 對行值的散列引用,列名鍵索引    下面的例子說示出了怎樣使用每個提取行方法。這些例子在整個結果集的行中循環,對於每一行,顯示由逗號分隔的列值。在某些情況下,編寫這些顯示代碼還有一些更有效的方法,但是這些例子是以能夠說明訪問單個列值的語法的方式編寫的。

  可如下使用fetchrow_array( ):



    對fetchrow_array( ) 的每個調用都返回行值數組,不再有行時,返回一個空數組。

    選擇將返回值分配給數組變量,可以在一組標量變量中提取列值。如果想使用比$ a r y [ 0 ]、$ary[1] 等更有意義的變量名,就可以這樣做。假設要在變量中檢索名稱和電子郵件值,可使用fetchrow_array( ),可以如下選擇並提取行:



    當然,在以這種方式使用一列變量時,必須保證查詢按正確的次序選擇列。DBI 不關心SELECT 語句指定列的次序,所以正確地分配變量是您的職責。在提取行時,使用一種稱為參數約束的技術,也可以使列值自動分配給單獨的變量。

    fetchrow_arrayref( ) 類似於fetchrow_array( ),但不返回包含當前行的列值的數組,而是返回這個數組的引用,在沒有乘余行時,返回un d e f。如下使用:



    通過數組引用$ary_ref 訪問數組元素。這類似於引用指針,所以使用了$ary_ref->[$i] 而不是$ a r y [ $ i ]。要想引用整個數組,就要使用@{$ary_ref} 結構。

    fetchow_arrayef( ) 不適合在列表中提取變量。例如,下面的循環不起作用:



    實際上,只要fetchrow_arrayref( ) 提取一行,這個循環就能正確地運行。但是在沒有更多的行時, fetchrow_arrayref( ) 返回un d e f,並且@{undef} 不合法(它有些像在C 程序中試圖廢棄一個NULL 指針)。

    提取行的第三個方法fetchrow_hashref( ),如下使用:



   如果希望將一行值傳遞給某個函數而又不需要這個函數知道SELECT 語句中指定列的次序時,fetchrow_hashref( ) 是非常有用的。既然如此,可以調用fetchrow_hashref( ) 來檢索行,並且編寫一個使用列名訪問來自行散列值的函數。

    如果使用fetchrow_hashref( ),請記住下列警告:

    如果性能很重要,則fetchrow_hashref( ) 並不是最好的選擇,因為它沒有fetchrow_array( ) 或fetchrow_arrayref( ) 的效率高。

    作為散列鍵值使用的列名具有與SELECT 語句中寫出時相同的字符。在MySQL中,列名不區分大小寫,所以此查詢也是這樣,不管以大寫字母還是小寫字母給出列名,查詢結果都是一樣的。但是Perl 散列索引名是區分大小寫的,這可能會帶來一些問題。為了避免潛在的大小寫不匹配問題,可通過傳遞NAME_lc 或NAME_uc 屬性,告知fetchrow_hashref( ) 強迫列名為大寫或小寫:



    散列對每個唯一的列名含有一個元素。如果正在執行從多個具有重疊名稱的表中返回列的連接,則不能訪問所有的列值。例如,如果發布下面的查詢, fetchrow_hashref( )將返回只有一個元素的散列:

    SELECT a.name FROM a,b WHERE a.name=b.name

    2. 確定查詢返回的行數

    如何知道SELECT 或類似於SELECT 的查詢返回的行數?一種方法是,當提取它們時,計算這些行的數量。實際上,這是知道SELECT 查詢返回多少行的唯一方便的方法。使用MySQL驅動程序,可以在調用execute( ) 後利用語句句柄調用rows( ) 方法,但是這對其他數據庫引擎並不方便。而且即使就MySQL來說,如果已經設置了MySQL_use_result 屬性,rows( ) 也不能返回正確的結果,直到提取了所有行(有關的詳細信息,請參閱附錄G)。所以只能如提取行一樣對它們進行計數。

    3. 提取單行的結果

    如果結果集只含單個行,則不需要運行循環來獲得結果。假設要編寫得出歷史同盟成員當前數量的腳本count _ member s。完成查詢的代碼如下所示:



    SELECT 語句只返回一行,所以不需要循環;我們只調用fetchrow_array( ) 一次。另外,因為我們只選擇一列,所以甚至不需要將返回值分配給數組。當在標量環境中(單個值而不是所期望的一列)調用fetchrow_array( ) 時,它返回這個行的第一列,如果沒有更多的有效行,則返回un d e f。

    另一種期望最多有一個記錄的查詢是一個含有LIMIT 1來約束返回的行數的查詢。其一般的用法是返回特定列含有最大或最小值的行。例如,下面的查詢給出最近出生的總統姓名和出生日期:



    必須無提取循環的其他類型的查詢利用MAX( ) 或MIN( ) 來選擇單個值。但是在所有這些情況下,獲得單個行結果的一種更容易的方法就是使用數據庫句柄方法selectrow_array( ),它結合了prepare( )、execute( ) 並在單個調用中提取行。它返回一個數組(而不是一個引用),如果出現錯誤,則返回一個空數組。前一例子可利用selectrow_array( ) 編寫如下:





    4. 處理完整的結果集

    在使用提取循環時, DBI 不提供在結果集中隨意查找的方法,或以任何次序而不是以循環返回的次序來處理行。同樣,提取行以後,如果沒有保存,前一行會丟失。這種做法並不一定合適以下情況:

    以非連續的次序處理行。考慮一種情況,想以歷史同盟的president 表中列出的美國總統為主體,進行一些測驗。如果希望每次測驗時都以不同的次序提出問題,則可以從president 表中選擇所有行。然後,可能以任意的次序提取行來改變與所問問題有關的總統的次序。要想任意地提取一行,就必須同時訪問所有的行。

元素。下面的討論說明這兩種方法。

    使用提取循環來捕獲結果集的一種方法是使用fetchrow_array( ) 並保存對這些行引用的數組。除了保存所有的行,然後顯示矩陣舉例說明了如何確定矩陣中的行數和列數,及如何訪問矩陣的個別成員以外,下面的代碼和dump_members 中提取和顯示的循環作用是一樣的。





    在確定矩陣的維數時,必須首先確定行數,因為無論這個矩陣是否為空,都可能計算列數。如果$rows 為0,則這個矩陣為空,並且$cols 也為0。否則,列數可能作為行數組中的元素數量來計算,用語法@{$matrix[$i]} 來整體訪問行$ i。

    在前述的樣例中, 我們提取每一行, 然後保存對它的引用。可以設想調用fetchrow_arrayref( ) 而不是直接地檢索行引用可能更有效率:



    它不能正常工作,因為fetchrow_arrayref( ) 重新使用了引用指向的數組。結果矩陣是一個引用的數組,數組中的每個元素都指向相同行—最後檢索的行。因此,如果想一次提取一行,則要使用fetchrow_array( ) 而不是fetchrow_arrayref( )。

    另一個選擇是使用提取循環,可以使用返回整個結果集的DBI 方法中的一個。例如,fetchall_arrayref( ) 返回對引用數組的引用,數組的每個元素都指向結果集中某行。這非常簡單,但很有效,這個返回值是對矩陣的引用。要想使用fetchall_arrayref( ),則調用prepare( )和execute( ),然後如下檢索結果:



    如果結果集為空,則fetchall_arrayref( ) 返回一個對空數組的引用。如果出現錯誤,則結果為un d e f,所以如果沒有啟用R a i s e E r r o r,則在開始使用它以前,要確保檢查返回值。

    行數和列數由矩陣是否為空來確定。如果想作為一個數組訪問這個矩陣的整個行$ i,應該使用語法@ { $ m a t r i x _ r e f - > [ $ i ] }。

    使用fetchall_arrayref( ) 來檢索結果集當然比編寫一個提取行的循環要更簡單一些,盡管訪問數組元素的語法是一個小技巧。一個與fetchall_arrayref( ) 方法相類似,但卻做了更多工作的方法是selectall_arrayref( )。這個方法為您完成了整個prepare( )、execute( )、提取循環、finish( ) 序列。為了使用selectall_arrayref( ),應該利用數據庫句柄直接將查詢傳遞給它:



    5. 檢查NULL 值

    從數據庫中檢索數據時,可能需要區分值為NULL 、為0 或者為空字符串的列。因為DBI 返回NULL 列值作為un d e f,所以這種區分是容易的。然而,必須確保使用正確的測試。如果試用下面的代碼段,則它所有三次顯示都為“ f a l s e !”:



    而且,對於這兩個測試,這段代碼都顯示“ f a l s e !”:



    下面這段代碼效果相同:



    要想區分NULL 列值和非NULL 列值,則使用defined( )。知道了沒有出現NULL 值之後,使用適當的測試可以在其他類型值之間加以區分。例如:



    以適當的次序完成這些測試是很重要的,因為如果$col_val 為空字符串,則第二個和第三個比較就都為真。如果顛倒比較的次序,則會錯誤地將空字符串標識為0。

引用問題

    迄今為止,我們已經利用引用字符串以最基本的方式構造了查詢。在引用的字符串含有引用值時,會在Perl 詞匯一級產生問題。在插入或者選擇含有引號、反斜槓或二進制數據的值時,在SQL 中也可能出問題。如果指定一個查詢作為Perl 引用的字符串,則必須避免在查詢字符串本身中出現引用字符:



    Perl 和MySQL都允許用單引號或雙引號引用字符串,所以混合使用引用字符有時可以避免這種無法引用引用字符自身的情況:



    然而,在Perl 中,這兩種類型的引號並不等價。只有在雙引號內部才解釋為變量引用。因此,當想通過在查詢字符串中嵌入變量引用來構造查詢時,單引號並不是非常有用的。例如,如果$var 的值為14,則下面的兩個字符串並不等價:



    兩個字符串的解釋如下所示;顯然,第一個字符串與希望傳遞給MySQL服務器的內容更為相像:



    用雙引號來引用字符串的另一個選擇是使用QQ{} 結構,它告訴Perl 在‘q q {’和‘}’之間的每個字符都要看作為雙引號括起的字符串(兩個q 表示“雙引號”)。例如,下列兩行是等價的:



    使用qq{} 時,構造查詢不用過多考慮引號的問題,因為可以在這個查詢字符串內自由地使用引號(單引號或雙引號),而不用避開它們。此外,還解釋了變量引用。QQ{} 的這兩種特性可用下面的INSERT 語句來說明:



    不一定使用‘ {’和‘ }’作為qq 的分隔符。其他格式,如qq( ) 和q q / /,也可以使用,只要封閉的分隔符不出現在字符串內即可。我喜歡用q q { },因為‘ {’不像‘)’或‘/’會出現在查詢的文本內,並且在查詢字符串的結尾也可能有問題。例如,‘)’出現在所顯示的INSERT 語句的內部,所以QQ( ) 對於引用查詢字符串來說不是一個有用的結構。

    QQ{} 結構能跨行,如果想讓查詢字符串在Perl 代碼中醒目,這很有用:



    如果希望將查詢格式分為多個行,從而使它的可讀性更強,這樣也很有用。例如,dump_members 腳本中的SELECT 語句如下:



    用q q { }編寫如下:





    雙引號字符串也可以跨行。但是,對於編寫多行的字符串,我更喜歡用q q { }。我發現當在一行中看到不匹配的引號時,我就自然地去想,“這會是語法錯誤嗎?”,然後,我就會浪費時間去尋找相匹配的引號。

    QQ{} 結構在Perl 詞匯級注意了引用的問題,因此可將引號容易地放到字符串內,而不會使Perl 搞混它們。然而,還必須考慮SQL 級的語法。考慮向member 表中插入一條記錄:



    do( ) 發送給MySQL的字符串如下所示:



    這是不合法的SQL 語句,因為在單引號字符串內出現了單引號。在第6章中,我們遇到過類似的引用問題。在那裡,我們使用MySQL_escape_string( ) 來處理這個問題。DBI 提供了一個類似的機制—在一條語句中,對想按字面使用的每個引用值,都調用quote( ) 方法,並使用它的返回值。

    前面的例子可編寫如下:



    現在,do( ) 發送給MySQL的字符串如下所示,具有出現在引用字符串內的可能對服務器轉義的引號:

    請注意,在查詢字符串中引用$last 和$first 時,不要增加括起來的引號; quote( ) 方法支持它們。如果增加了引號,則查詢將出現過多的引號,如下面的例子所示:



    這些語句產生下面的輸出:



占位符和參數約束

    在前面各節中,我們通過把要插入或選擇的值作為選擇標准,直接放在查詢字符串中構造了查詢。不一定非要這樣做。DBI允許在查詢字符串內部放置一些稱為占位符的特殊標記符,然後,在執行該查詢時,將這些值代替那些標識符來使用。這樣做的主要原因是提高性能,特別是在循環中反復執行某個查詢的時候。

    為了說明占位符如何工作,舉例說明。假設學校新學期剛開始,打算清理學分薄的student 表,然後利用包含在文件中的一列學生姓名將其初始化,使其包含新學生。不用占位符,可以如下這樣刪除現有表的內容,並裝入新的姓名:



    這樣做效率很低,因為INSERT 查詢的基本格式每次都是相同的,並且在整個循環中,do( ) 每次都調用prepare( ) 和execute( )。在進入這個循環以前,只調用一次prepare( ) 來設置INSERT 語句,並且在這個循環內部只調用execute( ),這樣做效率更高一些。只調用一次prepare( ),可避免其他多次調用。DBI 允許我們這樣做:



    請注意這個INSERT 查詢中的‘ ?’就是一個占位符。調用execute( ) 時,將查詢發送給服務器,傳遞這個值來代替占位符。一般來說,如果發現在循環內部調用了do( ),應該在循環前調用prepare( ),並在這個循環內部調用execute( ) 更好一些。

    有關占位符的一些注意事項:

    在查詢字符串內,不要在引號中封裝占位符字符。如果這樣做,不能識別為占位符。

    不要使用quote( ) 方法來指定占位符的值,否則將在插入的值中得到額外的引號。

    在查詢字符串中可以有一個以上的占位符,但是要確保占位符的標記符與傳遞給execute( ) 的值一樣多。

    每個占位符都必須指定一個單獨的值,而不是一列值。例如,不能運行這樣的語句:



    為了將NULL 指定為占位符,應該使用un d e f。

    不要對關鍵字使用占位符。這樣會出問題,因為占位符的值是由quote( ) 自動處理的。

    關鍵字將被放在引號括起來的查詢中,因此,這個查詢會由於語法錯誤而失敗。

    除了在循環中提高效率以外,對於某些數據庫引擎,可以從占位符的使用中獲得其他的性能好處。某些引擎高速緩存了准備好的查詢,以及為有效地運行這個查詢所生成的計劃。也就是說,如果以後這個服務器收到同樣的查詢,則它可以再次使用相應的計劃而不用生成。查詢高速緩存特別有助於復雜的SELECT 語句,因為可能需要花費時間生成較好的執行計劃。占位符提供了一個在高速緩存中尋找查詢的好機會,因為它們使查詢比直接在查詢字符串中嵌入指定的列值來構造查詢更通用。對於MySQL,在這種方式下,占位符並不提高性能,因為沒有高速緩存查詢。然而,可能仍想使用占位符編寫自己的查詢;如果偶然將DBI 腳本傳遞給支持查詢高速緩存的引擎,則這個腳本比沒有占位符時運行效率更高。

    在查詢運行時,允許在查詢字符串中用占位符代替這些值。換句話說,可以參數化這個查詢的“輸入”。在提取行而不必將值賦給變量時,DBI 也提供一個稱為參數約束的輸出操作,允許通過檢索自動進入這些變量的列值使“輸出”參數化。


    bind_col( ) 的每個調用都應該指定一個列號和一個希望與該列相聯的變量的引用。列號從1開始。bind_col( ) 應該在execute( ) 之後調用。

    還有一種選擇,就是單獨調用bind_col( ),可以在bind_columns( ) 的單個調用中傳遞全部變量引用:



指定連接參數

    建立服務器的連接的最直接的方法為,調用connect( ) 方法時指定所有連接參數:



    如果遺漏連接參數,則DBI 做下面的事情:

    如果未定義數據源或未定義空字符串,則使用DBI_DSN 環境變量。如果未定義用戶名和口令,則使用DBI_USER 和DBI _ PASS 環境變量(但如果它們為空字符串則不使用)。在Windows 下,如果未定義用戶名,則使用user 變量。

    如果遺漏了主機名,其缺省值為localhost。

    如果將用戶名指定為undef 或空字符串,則其缺省為UNIX 的登錄名稱。在Windows 下,用戶名缺省為ODBC。

    如果將口令指定為undef 或空字符串,則不傳送口令。

    通過將某些選項添加到字符串的初始部分,每個都在分號前面,可以在數據源中指定這些選項。例如,可以使用MySQL_read_default_file 選項來指定一個選項文件的路徑名:



    當執行這個腳本時,它將從這個文件中讀取連接參數。假設/ u/ paul /.my.cnf 含有下面的內容:



    然後connect( ) 調用試圖連接到p i t - v i per-snake-net 上的MySQL服務器,並且用口令secret 及用戶名paul 連接。如果想允許具有正確地設置選項文件的任何人使用您的腳本,則像這樣指定數據源:



    $ENV{HOME} 含有用戶運行這個腳本的主目錄的路徑名,所以這個腳本使用的主機名、用戶名和口令將會從每個用戶自己的選項文件中抽取出來。以這種方式編寫腳本,不必在這個腳本中逐字地嵌入連接參數。

    還可以使用MySQL_read_default_group 選項,來指定一個選項文件組。這自動地導致讀取用戶的.my.cnf 文件,並且除了[clIEnt] 組以外,還允許讀取一個指定的選項組。例如,如果在DBI 腳本中具有指定的選項,則可以將它們列在[dbi] 組中,然後以如下方式使用數據源值:



    mysql_read_default_file 和mysql_read_default_group 需要MySQL3.22.10 或更新的版本,以及DBD::mysql1.21.06 或更新的版本。有關指定的數據源字符串的選項的詳細信息,請參閱附錄G。有關MySQL選項文件格式的詳細信息,請參閱附錄E。

    使用選項文件並不防礙在connect( ) 調用中指定連接參數(例如,如果想這個腳本作為特殊的用戶來連接)。在connect( ) 調用中指定的任何明確的主機名、用戶名和口令值都將覆蓋在選項文件中找到的連接參數。例如,想要腳本從命令行中分析- - host、--user 和--passWord 選項,並使用那些值,如果給定,則優先於在選項文件中發現的任何內容。這是有用的,因為它是標准的MySQL客戶機操作的方式。DBI 腳本將因此符合它的行為。

    對於在本章中我們開發的保留在命令行中的腳本,我將使用一些標准的連接設置代碼及卸載代碼。我只在這裡說明它一次,以便我們可以將精力集中在每個腳本的主體上,我們編寫如下代碼:





    我們腳本的最後部分也類似於從腳本到腳本;它簡單地終止這個連接並退出:

  $dhb->disconnect();

    exit (0);

    當我們讀到Web 程序設計的部分,即7 . 4節“在Web 應用程序中使用DBI”時,將修改一些這個連接設置代碼,但是基本的思想是類似的。

調試

    當想調試有故障的DBI 腳本時,通常使用兩項技術,即單獨使用一個或一前一後地配合使用。首先,在腳本的整個過程中編寫顯示語句。它允許將自己調試的輸出設計為想要的方式,但必須手工地增加語句。其次,可以使用DBI 的內建跟蹤能力。這更加通用,但也更加

系統,而且它在打開以後,則會自動地出現。DBI 跟蹤也說明一些除此以外就無法獲得的有關驅動程序的操作信息。

    1. 使用顯示語句調試

    在MySQL郵件清單中,常見問題之一是:“有一個查詢,當我在MySQL中執行它時運行得很好,但是它不能在我的DBI 腳本中工作,怎麼回事?”尋找發布不同查詢的DBI 腳本和這個發問者所期望的一樣是很平常的。如果在執行它之前顯示查詢,則可能會驚異地看到真

正發送到這個服務器上的內容。假設將一個查詢鍵入到MySQL中(沒有終止的分號)



    然後,在DBI 腳本中試著做相同的事情:



    盡管它是同樣的查詢,但它不能工作。不是嗎?試著顯示:

    print "$query\n"

    結果如下:

    INSERT member (last_name,first_name,expiration)

    VALUES(Brown,Warcia,2002-6-3)

    從這個輸出中,可以看到是您忘記了VALUES( ) 列表中這些列值前後的引號。指定查詢的正確方法如下:



    或者,可以使用占位符指定查詢,並傳遞這些值,直接插入到do( ) 方法中:



    不幸的是,當做這些的時候,使用顯示語句不能看到完整查詢的樣子,因為直到調用d o ( )才能估計占位符的值。當使用占位符時,跟蹤可能對調試方法更有幫助。

    2. 使用跟蹤調試

    當試圖查出腳本不能正確工作的原因時,可以告知DBI 來生成跟蹤(調試)信息。跟蹤級別范圍從0(關閉)到9(最多信息)。一般來說,跟蹤級別1和2 是最有用的。級別2 跟蹤說明正在執行的查詢文本(包括占位符替換的結果)、調用quote( ) 的結果等等。這可能對捕獲問題有極大的幫助。

    使用trace( ) 方法,可以從獨立的腳本內部控制跟蹤,或者可以設置DBI_TRACE 環境變量來影響所運行的所有DBI 腳本的跟蹤。

    要想使用trace( ) 調用,則傳遞一個跟蹤級別參數,並可以有選擇地再傳遞一個文件名。如果沒有指定文件名,則所有的跟蹤輸出到STDERR 中;否則,它就轉到這個命名的文件中。一些樣例如下:



    當調用DBI->trace( ) 時,跟蹤所有的DBI 操作。一個更精細的方法是,可以用獨立的處理級別啟用跟蹤。當沒想好腳本中問題的位置,並對在那點出現的每件事的跟蹤輸出不想插手時,這是有幫助的。例如,如果特定的SELECT 查詢有問題,則可以跟蹤與這個查詢相關的語句句柄:



    如果對任何trace( ) 調用指定一個文件名參數,則無論對DBI 作為整體還是單獨的句柄,所有的跟蹤輸出都要到那個文件中。

    要想對運行的所有DBI 腳本全部都打開跟蹤,則從命令解釋程序中設置DBI_TRACE 環境變量。它的語法取決於使用的命令解釋程序:

    value 的模式和所有命令解釋程序的模式一樣:數字n表示在級別n打開跟蹤到S T D E R R中;文件名打開級別2 跟蹤到這個命名的文件,或n=file_name 打開級別n跟蹤到這個命名的文件中。下面的樣例使用了csh 語法:



    如果打開跟蹤到命令解釋程序中的文件,則確保一旦解決了這個問題,就將它關閉。將調試輸出增加到這個跟蹤文件中,而不用重寫它,所以如果不小心,則這個文件可能變得非常大。極其不好的想法是在命令解釋程序的啟動文件(如.cshrc、.lonin 或. p r o f i l e)中定義DBI _ T R A C E!在UNIX 下,可以使用下面兩個命令( csh 語法)之一關閉跟蹤:

    % setenv DBI_TRACE 0

    % unsetenv DBI_TRACE

    對於s h、ksh 或b a s h,這樣做:

    $ DBI_TRACE=0

    $export DBI_TRACE

    在Windows 操作系統中,可以使用下面兩個命令之一關閉跟蹤:

    c:\>unset DBI_TRACE

    c:\>set DBI_TRACE=0

使用結果集元數據

    可以使用DBI 來獲得訪問結果集元數據——也就是有關由查詢選擇行的描述信息。訪問與結果集生成的查詢所相關的語句句柄的屬性來獲得這個信息。提供這些屬性中有一些是作為可用於橫跨所有數據庫驅動程序的標准DBI 屬性(如N U M _ O F _ F I E L D S,結果集中列的數量)。另外一些是MySQL特定的,由DBD::mysql所提供的DBI 的MySQL驅動程序。這些屬性,如mysql_max_length 告知了每列值的最大寬度,不能用於其他數據庫引擎。要想使用任何MySQL特定的屬性,都必須冒著使腳本不可移植到其他數據庫的危險。另一方面,它們可以使它更容易地獲得想要的信息。

    必須在適當時候請求元數據。一般來說,直到調用prepare( ) 和execute( ) 之後,結果集屬性才能用於SELECT 語句。除此之外,在調用finish( ) 之後,屬性可能變為無效。
5.com (andyyoo) [1] [2] [3] [4] [5] [6] [7] [8] [9] [10] [11] [12] [13] [14] [15] [16] [17] [18] [19] [20] [21] [22] [23] [24]

   讓我們來看看如何使用MySQL的一個元數據屬性mysql_ m a x _ l e n g t h,與保留查詢列名的DBI 級別的NAME 屬性一起使用。我們可以將這些屬性提供的信息合並起來,編寫一個腳本b o x _ o ut,它以交互模式運行MySQL客戶機程序時獲得的相同邊框風格,從SELECT 查詢產生輸出。box_out 的主體如下(可以用任何其他的語句替換SELECT 語句;編寫輸出的例程獨立於特定的查詢):





    用execute( ) 將這個查詢初始化之後,我們獲得了所需的元數據。$sth->{NAME} 和$ s t h ->{MySQL_max_length} 給出了列名和每列值的最大寬度。為了在這個查詢中為列命名,每個屬性值都引用了一個數組,這個數組含有結果集每列中的一個值。

    剩余的計算非常類似於在第6章中開發的客戶機程序5中所使用的那些內容。例如,為避免偏離輸出,如果列的名比該列中任何數據值都寬,則我們要向上調整列的寬度值。

    輸出函數print_dashes( ) 和print_row( ) 代碼編寫如下,它們也類似於客戶機程序5中相應的代碼:



    box_out 的輸出如下:



    我們的下一個腳本使用了列元數據來產生不同格式的輸出。這個腳本s h o w _ member,允許快速浏覽歷史同盟成員項目,而不用輸入任何查詢。給出成員的姓,它就這樣顯示所選擇的項目:





    使用成員資格號碼,或者使用與若干姓相匹配的模式也可以調用s h o w _ member s。下面的命令說明成員號碼為2 3的項目,和以字母“C”開始的姓的成員項:



    show_member 腳本的主體如下所示。它使用了NAME 屬性來確定輸出的每行所使用的標號和NUM_OF_FIELDS 屬性,找出這個結果集含有的列數:





    無論區域是什麼, show_member 的目的都是說明一個項目的全部內容。通過使用SELECT * 來檢索所有的列和NAME 屬性來看看它們是什麼,即使從member 表中增加或刪除列,這個腳本也會工作而不用做修改。

    如果不檢索任何行就想知道一個表含有哪些列,則可以發布下面這條查詢:

    SELECT * FROM tbl_name WHERE 1=0

    以正常方式調用prepare( ) 和execute( ) 之後,可以從@{$sth->{NAME}} 中得到列名。然而,請注意,盡管使用“空”查詢的這個小竅門可以在MySQL下運行,但是它不可移植,而且並不是對所有的數據庫引擎都可以工作的。

    有關DBI 和DBD::mysql所提供屬性的詳細信息,請參見附錄G。它完全可以使您確定是想通過避免MySQL特定的屬性而為可移植性花費努力,還是在可移植性的開銷方面利用它們。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved