本文描述了MySQL,這是一個利用第三方資料庫開發電子貿易和其它複雜、動態網站的有效工具。 MySQL 是一種快速、多執行緒和全功能的SQL伺服器。除了描述MySQL系統的基本架構以外,本文也提供了以Tcl和C++編寫的簡單範例,幫助您開發支援資料庫的Web應用。 本文描述了MySQL,這是一個利用第三方資料庫開發電子貿易和其它複雜、動態網站的有效工具。 MySQL 是一種快速、多執行緒和全功能的SQL伺服器。除了描述MySQL系統的基本架構以外,本文也提供了以Tcl和C++編寫的簡單範例,幫助您開發支援資料庫的Web應用。
一個必須儲存或存取大量資訊的應用程式可以從使用第三方資料庫產品中受益匪淺。在資訊的存取必須在程式的多個實例上進行時更是如此。基於Web的應用(包括電子貿易)就是它的良好例證。
為什麼要使用獨立資料庫?
Web伺服器必須使其處理腳本有辦法儲存有關供其以後存取的狀態資訊。儘管有可能使用比較原始一些的方法--例如轉儲到文字檔案或開發自製的迷你資料庫--但只有成熟的資料庫應用程式才能提供更為複雜的網路應用程式所需的所有服務。因為有一些免費獲得的軟體包可用於該目的,所以編寫客製化的特定於應用的資料庫引擎並沒有太大好處。 另外,使用第三方資料庫也讓Web開發者不必投入開發和維護資料庫的任務。
MySQL資料庫
透過使用腳本語言和編譯型系統語言(例如C),將資料庫整合到Linux應用程式就可能相當容易。可免費取得的MySQL(在GNU Public License下發行)資料庫提供了一系列複雜的SQL功能,並且易於整合到應用中。 MySQL是快速、多執行緒的,並支援ANSI和ODBC SQL標準。加上第三方軟體,MySQL就支援用於事務處理應用的事務安全的表。
註:什麼是事務處理?
事務是需要以原子方式執行的對資料庫所做的一系列更改。它們要么必須全部執行,要么一個都不執行。 例如,在Web上銷售產品時所有必要的資料庫變更組成一個事務。
資料庫需要同時減去客戶帳戶餘額和產品庫存,否則失敗且一個操作都不執行。
無論伺服器出於何種原因發生崩潰都不應該引起事務被部分執行。例如帳單多算、產品沒有交付,或庫存不實等都有可能是部分完成的交易的結果。
支援事務處理的資料庫可以將一組資料庫程式碼封裝在一個事務中,在事務執行期間的任何失敗都會讓資料庫回滾到事務開始之前的狀態。
這是透過維護所有資料庫操作的日誌,以及其原始狀態表的副本來實現的,在失敗後下一次重新啟動伺服器時允許回滾操作。 這種時間和空間上的開銷是事務安全資料庫系統所必需的一種折衷。
單一MySQL伺服器控制著一系列資料庫,它們都可以透過伺服器以類似方式來存取。每個資料庫其實都是一組任意數量的表,概念與其它SQL資料庫的使用者類似。每個表都由帶類型的資料列組成。資料可以是整數、實數值、字串或其它類型,包括原始二進位流。 表中的每一行都是儲存在資料庫中的一個記錄。
MySQL被設計和建構成客戶機/伺服器。伺服器mysqld可以在任何能從網際網路存取到的機器上運作(最好與Web伺服器在同一台或最接近的一台機器上,以確保合理的回應時間)。 MySQL客戶機使用請求來與MySQL伺服器聯繫,修改或查詢伺服器所擁有的資料庫。在支援資料庫的Web應用程式中,資料庫客戶機是Web伺服器或由Web伺服器產生的CGI腳本。這些客戶機可以用高階腳本語言或低階系統語言編寫,只要有這種語言的資料庫API即可。在Linux中,大多數腳本語言是以C 實現的,因為存在MySQL C API,所以要將MySQL支援新增到任何現有的腳本語言或工具應該很容易。絕大部分腳本語言已經完成了這一步。
MySQL API
MySQL API可用於各種語言,包括幾乎所有編寫網站後端所實際使用的語言。 使用這些API,我們可以建立由Web伺服器控制的MySQL客戶機。
API(用於資料庫存取)以基於連接的模式工作。客戶機必須做的第一件事是開啟與MySQL伺服器的連線。這包括適當地使用伺服器認識的使用者名稱和口令來對連線進行身份認證。建立了連線後,伺服器選擇要使用的特定資料庫。確定了初始化後,客戶機應用程式(就我們來說是伺服器方CGI腳本)就能自由地與資料庫以兩種方式中的一種進行互動:可以執行常規SQL命令,包括新增和刪除表,以及向它們添加記錄;也可以對傳回結果的資料庫執行查詢。查詢產生一組與查詢相符的記錄,然後,客戶機可以逐一存取記錄,直到查看所有記錄,或客戶機取消暫掛的記錄檢索。一旦腳本完成了對資料庫的操作後,與伺服器的連線就被關閉。
要建立整合資料庫存取的網站,需要編寫CGI腳本來根據資料庫狀態產生動態結果。 Web伺服器啟動CGI腳本,然後將適當格式化的HTML輸出到它們的標準輸出流。 Web伺服器捕捉到HTML後將它傳送回客戶機,如同請求是對靜態HTML頁面進行的那樣。 在產生HTML 的過程中,腳本可以修改資料庫,也可以查詢並將結果合併到它們的輸出中。
作為簡單解釋上述流程的範例,以下的程式碼(以C和Tcl編寫)查詢一個包含某公司供銷售的產品清單的資料庫。 這絕對沒有使用兩種語言MySQL API的所有特性,但提供了快速、簡易擴充的範例,可以對資料庫內容執行任何SQL指令。 在該例中,腳本顯示了低於特定價格的所有產品。在實踐中,使用者可能在網頁瀏覽器中輸入該價格,然後將它發送給伺服器。 我們省去了從環境變數中進行讀取來確定HTML 表單值的細節,因為它與不支援資料庫的CGI 腳本中執行的情況沒有什麼差別。 為清晰起見,我們假設事先設定了特定一些參數(例如要查詢的價格)。
以下程式碼是使用免費獲得的Tcl Generic Database Interface以Tcl實現的。這樣一種介面的好處在於Tcl是解釋型的,可以對程式碼進行迅速開發和快速修改。
Tcl範例
#This code prints out all products in the database
# that are below a specified price (assumed to have been determined
# beforehand, and stored in the variable targetPrice)
# The output is in HTML table format, appropriate for CGI output
#load the SQL shared object library. the Tcl interpreter could also
#have been compiled with the library, making this line unnecessary
load /home/aroetter/tcl-sql/sql.so
#these are well defined beforehand, or they could
#be passed into the script
set DBNAME "clientWebSite";
set TBLNAME "products";
set DBHOST "backend.company.com"
set DBUSER "mysqluser"
set DBPASSWD "abigsecret"
set targetPrice 200;
#connect to the database
set handle [sql connect $DBHOST $DBUSER $DBPASSWD]
sql selectdb $handle $DBNAME ;# get test database
#run a query using the specified sql code
sql query $handle "select * from $TBLNAME where price <= $targetPrice"
#print out html table header
puts "
Product Id | Description | Price ($)" #output table rows - each fetchrow retrieves one result |
---|---|---|
$prodid | $descrip | $price" } puts " |
#empty the query result buffer - should already be empty in this case
sql endquery $handle
#close the db connection - in practice this same connection
#is used for multiple queries
sql disconnect $handle
下面的程式碼是使用正式MySQL C++ API MySQL++以C++編寫的等價腳本。這個版本的優點在於它是編譯型的,因此比解釋語言更快。經常用在特定站點的資料庫程式碼應該以C或C++編寫,然後由腳本或直接由Web伺服器訪問,以改善整體運行時間。
C++範例
#include
#include
#include
const char *DBNAME = "clientWebSite";
const char *DBTABLE = "products";
const char *DBHOST = "backend.company.com";
const char *DBUSER = "mysqluser";
const char *DBPASSWD = "abigsecret":
int main() {
try {
//open the database connection and query
Connection con(DBNAME, DBHOST, DBUSER, DBPASSWD);
Query query = con.query();
//write valid sql code to the query object
query << "select * from " << DBTABLE;
//run the query and store the results
Result res = query.store();
//write out the html table header
cout << "
Product Id | Description" << " | Price ($)" << endl; Result::iterator curResult; //iterate over each result and put it into an html table |
---|---|---|
" << row[0] << " | " << row[1] << " | " << row[2] << endl; } |
} catch (BadQuery er) {
// handle a bad query (usually caused by a sql syntax error)
cerr << "Error: " << er.error << endl;
return -1;
} catch (BadConversion er) {
//handle conversion errors out of the database as well
cerr << "Error: Can't convert "" << er.data << "" to a ""
<< er.type_name << ""." << endl;
return -1;
}
return 0;
}
安全性
在Web上創建以Web支援的應用程式有一些開發者需要考慮的問題。所有有關Web伺服器上CGI程式的問題,例如Web伺服器處理許可權和腳本方的輸入檢查,也仍然需要考慮。
除此之外,維護資料庫系統的安全性也很有必要。這涉及保護資料庫伺服器的許可權系統,以及使從資料庫客戶機到伺服器的連線安全。
MySQL提供了一個深入的安全性系統,有人形容它是「進階但不標準」。 MySQL允許根據使用者名稱、客戶機主機和要存取的資料庫對客戶機進行存取。要創建安全的系統,讓所有使用者使用強口令,不要給他們任何他們不是絕對需要的存取權。這包括表面上無害的特權,例如可以讓使用者查看所有正在運行的進程(包括更改其他使用者口令的那些)的處理特權。最好的方法是以無特權的Unix用戶運行伺服器程序本身,這樣如果一個資料庫被洩露,就不會壓垮整個系統。這與以用戶nobody而非root用戶運行httpd類似。 描述系統存取的表是作為單獨的MySQL資料庫儲存的,可以由MySQL root用戶更新。 請注意,MySQL伺服器根據MySQL使用者名稱授予特權,這些使用者名稱與Unix使用者名稱不同。不過,有一個MySQL root用戶名,它對資料庫有全部權限。一旦伺服器確定了連接客戶端是誰,以及它們在嘗試連接什麼之後,就根據給定的一組權限來控制存取權。若要防止存取表中主機名稱被DNS電子欺騙,可以輸入所有主機的IP位址,或要求伺服器將IP位址解析回原始主機名稱來使其他人截獲DNS請求和回答更困難。
除了伺服器存取表以外,與伺服器的通訊也必須很安全。從客戶機登入伺服器上時,口令不以純文字方式發送;不過所有後續SQL 命令將以純文字方式發送。為達到更高的安全性,請使用ssh來設定連接埠轉送。它將伺服器和客戶機之間的所有通訊進行加密,防止有人在傳輸中觀察它。來自客戶機的資料傳送到客戶機本機機器中本機ssh伺服器所偵聽的連接埠上。它由本地ssh伺服器使用,加密後發送給遠端ssh伺服器,由它進行解密並轉發到MySQL伺服器連接埠。
在實際中,最安全的方法是在Web 伺服器所在的機器上執行資料庫伺服器,並讓由Web伺服器產生的CGI腳本透過UNIX(本地)套接字與MySQL伺服器進行通訊。此設定可以讓資料庫管理員停用所有與MySQL伺服器的遠端連線。如果Web和資料庫伺服器必須位於不同的機器上,加密它們之間的所有通信,或者將兩台機器透過自己專用的、物理上隔離的網路連接。只建立一個由Web伺服器使用的使用者帳號(除root 使用者外)以登入資料庫伺服器。
由資料庫驅動的網站是一些功能強大的工具,可以讓開發者建立提供更新資訊的動態站點,並讓由客戶機發起的變更在多個會話之間持續。後端資料庫的使用對於管理電子貿易和其它應用的用戶來說必不可少。透過使用可免費取得的軟體,有可能建立由資料庫驅動的站點,安全地將資料庫連結性整合到站點現有的CGI體系結構中。