PHP 數(shù)據(jù)庫 常見問題小結(jié)
更新時(shí)間:2009年06月22日 00:40:48 作者:
揭露 PHP 應(yīng)用程序中出現(xiàn)的五個(gè)常見數(shù)據(jù)庫問題 —— 包括數(shù)據(jù)庫模式設(shè)計(jì)、數(shù)據(jù)庫訪問和使用數(shù)據(jù)庫的業(yè)務(wù)邏輯代碼 —— 以及它們的解決方案。
該代碼不僅更短,而且也更容易理解和高效。我們不是執(zhí)行兩個(gè)查詢,而是執(zhí)行一個(gè)查詢。
盡管該問題聽起來有些牽強(qiáng),但是在實(shí)踐中我們通??偨Y(jié)出所有的表應(yīng)該在同一個(gè)數(shù)據(jù)庫中,除非有非常迫不得已的理由。
問題 4:不使用關(guān)系
關(guān)系數(shù)據(jù)庫不同于編程語言,它們不具有數(shù)組類型。相反,它們使用表之間的關(guān)系來創(chuàng)建對象之間的一到多結(jié)構(gòu),這與數(shù)組具有相同的效果。我在應(yīng)用程序中看到的一個(gè)問題是,工程師試圖將數(shù)據(jù)庫當(dāng)作編程語言來使用,即通過使用具有逗號分隔的標(biāo)識符的文本字符串來創(chuàng)建數(shù)組。請看下面的模式。
復(fù)制代碼 代碼如下:
DROP TABLE IF EXISTS files;
CREATE TABLE files (
id MEDIUMINT,
name TEXT,
path TEXT
);
DROP TABLE IF EXISTS users;
CREATE TABLE users (
id MEDIUMINT,
login TEXT,
password TEXT,
files TEXT
);
INSERT INTO files VALUES ( 1, 'test1.jpg', 'media/test1.jpg' );
INSERT INTO files VALUES ( 2, 'test1.jpg', 'media/test1.jpg' );
INSERT INTO users VALUES ( 1, 'jack', 'pass', '1,2' );
清單 10. Bad.sql
系統(tǒng)中的一個(gè)用戶可以具有多個(gè)文件。在編程語言中,應(yīng)該使用數(shù)組來表示與一個(gè)用戶相關(guān)聯(lián)的文件。在本例中,程序員選擇創(chuàng)建一個(gè) files 字段,其中包含一個(gè)由逗號分隔的文件 id 列表。要得到一個(gè)特定用戶的所有文件的列表,程序員必須首先從用戶表中讀取行,然后解析文件的文本,并為每個(gè)文件運(yùn)行一個(gè)單獨(dú)的 SELECT 語句。該代碼如下所示。
復(fù)制代碼 代碼如下:
<?php
require_once("DB.php");
function get_files( $name )
{
$dsn = 'mysql://root:password@localhost/bad_norel';
$db =& DB::Connect( $dsn, array() );
if (PEAR::isError($db)) { die($db->getMessage()); }
$res = $db->query( "SELECT files FROM users WHERE login=?",
array( $name ) );
$files = null;
while( $res->fetchInto( $row ) ) { $files = $row[0]; }
$rows = array();
foreach( split( ',',$files ) as $file )
{
$res = $db->query( "SELECT * FROM files WHERE id=?",
array( $file ) );
while( $res->fetchInto( $row ) ) { $rows[] = $row; }
}
return $rows;
}
$files = get_files( 'jack' );
var_dump( $files );
?>
清單 11. Get.php
該技術(shù)很慢,難以維護(hù),且沒有很好地利用數(shù)據(jù)庫。惟一的解決方案是重新架構(gòu)模式,以將其轉(zhuǎn)換回到傳統(tǒng)的關(guān)系形式,如下所示。
復(fù)制代碼 代碼如下:
DROP TABLE IF EXISTS files;
CREATE TABLE files (
id MEDIUMINT,
user_id MEDIUMINT,
name TEXT,
path TEXT
);
DROP TABLE IF EXISTS users;
CREATE TABLE users (
id MEDIUMINT,
login TEXT,
password TEXT
);
INSERT INTO users VALUES ( 1, 'jack', 'pass' );
INSERT INTO files VALUES ( 1, 1, 'test1.jpg', 'media/test1.jpg' );
INSERT INTO files VALUES ( 2, 1, 'test1.jpg', 'media/test1.jpg' );
清單 12. Good.sql
這里,每個(gè)文件都通過 user_id 函數(shù)與文件表中的用戶相關(guān)。這可能與任何將多個(gè)文件看成數(shù)組的人的思想相反。當(dāng)然,數(shù)組不引用其包含的對象 —— 事實(shí)上,反之亦然。但是在關(guān)系數(shù)據(jù)庫中,工作原理就是這樣的,并且查詢也因此要快速且簡單得多。清單 13 展示了相應(yīng)的 PHP 代碼。
復(fù)制代碼 代碼如下:
<?php
require_once("DB.php");
function get_files( $name )
{
$dsn = 'mysql://root:password@localhost/good_rel';
$db =& DB::Connect( $dsn, array() );
if (PEAR::isError($db)) { die($db->getMessage()); }
$rows = array();
$res = $db->query(
"SELECT files.* FROM users,files WHERE users.login=?
AND users.id=files.user_id",
array( $name ) );
while( $res->fetchInto( $row ) ) { $rows[] = $row; }
return $rows;
}
$files = get_files( 'jack' );
var_dump( $files );
?>
清單 13. Get_good.php
這里,我們對數(shù)據(jù)庫進(jìn)行一次查詢,以獲得所有的行。代碼不復(fù)雜,并且它將數(shù)據(jù)庫作為其原有的用途使用。
問題 5:n+1 模式
我真不知有多少次看到過這樣的大型應(yīng)用程序,其中的代碼首先檢索一些實(shí)體(比如說客戶),然后來回地一個(gè)一個(gè)地檢索它們,以得到每個(gè)實(shí)體的詳細(xì)信息。我們將其稱為 n+1 模式,因?yàn)椴樵円獔?zhí)行這么多次 —— 一次查詢檢索所有實(shí)體的列表,然后對于 n 個(gè)實(shí)體中的每一個(gè)執(zhí)行一次查詢。當(dāng) n=10 時(shí)這還不成其為問題,但是當(dāng) n=100 或 n=1000 時(shí)呢?然后肯定會出現(xiàn)低效率問題。清單 14 展示了這種模式的一個(gè)例子。
復(fù)制代碼 代碼如下:
DROP TABLE IF EXISTS authors;
CREATE TABLE authors (
id MEDIUMINT NOT NULL AUTO_INCREMENT,
name TEXT NOT NULL,
PRIMARY KEY ( id )
);
DROP TABLE IF EXISTS books;
CREATE TABLE books (
id MEDIUMINT NOT NULL AUTO_INCREMENT,
author_id MEDIUMINT NOT NULL,
name TEXT NOT NULL,
PRIMARY KEY ( id )
);
INSERT INTO authors VALUES ( null, 'Jack Herrington' );
INSERT INTO authors VALUES ( null, 'Dave Thomas' );
INSERT INTO books VALUES ( null, 1, 'Code Generation in Action' );
INSERT INTO books VALUES ( null, 1, 'Podcasting Hacks' );
INSERT INTO books VALUES ( null, 1, 'PHP Hacks' );
INSERT INTO books VALUES ( null, 2, 'Pragmatic Programmer' );
INSERT INTO books VALUES ( null, 2, 'Ruby on Rails' );
INSERT INTO books VALUES ( null, 2, 'Programming Ruby' );
[code]
清單 14. Schema.sql
該模式是可靠的,其中沒有任何錯(cuò)誤。問題在于訪問數(shù)據(jù)庫以找到一個(gè)給定作者的所有書籍的代碼中,如下所示。
[code]
<?php
require_once('DB.php');
$dsn = 'mysql://root:password@localhost/good_books';
$db =& DB::Connect( $dsn, array() );
if (PEAR::isError($db)) { die($db->getMessage()); }
function get_author_id( $name )
{
global $db;
$res = $db->query( "SELECT id FROM authors WHERE name=?",
array( $name ) );
$id = null;
while( $res->fetchInto( $row ) ) { $id = $row[0]; }
return $id;
}
function get_books( $id )
{
global $db;
$res = $db->query( "SELECT id FROM books WHERE author_id=?",
array( $id ) );
$ids = array();
while( $res->fetchInto( $row ) ) { $ids []= $row[0]; }
return $ids;
}
function get_book( $id )
{
global $db;
$res = $db->query( "SELECT * FROM books WHERE id=?", array( $id ) );
while( $res->fetchInto( $row ) ) { return $row; }
return null;
}
$author_id = get_author_id( 'Jack Herrington' );
$books = get_books( $author_id );
foreach( $books as $book_id ) {
$book = get_book( $book_id );
var_dump( $book );
}
?>
清單 15. Get.php
如果您看看下面的代碼,您可能會想,“嘿,這才是真正的清楚明了?!?首先,得到作者 id,然后得到書籍列表,然后得到有關(guān)每本書的信息。的確,它很清楚明了,但是其高效嗎?回答是否定的??纯粗皇菣z索 Jack Herrington 的書籍時(shí)要執(zhí)行多少次查詢。一次獲得 id,另一次獲得書籍列表,然后每本書執(zhí)行一次查詢。三本書要執(zhí)行五次查詢!
解決方案是用一個(gè)函數(shù)來執(zhí)行大量的查詢,如下所示。
復(fù)制代碼 代碼如下:
<?php
require_once('DB.php');
$dsn = 'mysql://root:password@localhost/good_books';
$db =& DB::Connect( $dsn, array() );
if (PEAR::isError($db)) { die($db->getMessage()); }
function get_books( $name )
{
global $db;
$res = $db->query(
"SELECT books.* FROM authors,books WHERE
books.author_id=authors.id AND authors.name=?",
array( $name ) );
$rows = array();
while( $res->fetchInto( $row ) ) { $rows []= $row; }
return $rows;
}
$books = get_books( 'Jack Herrington' );
var_dump( $books );
?>
清單 16. Get_good.php
現(xiàn)在檢索列表需要一個(gè)快速、單個(gè)的查詢。這意味著我將很可能必須具有幾個(gè)這些類型的具有不同參數(shù)的方法,但是實(shí)在是沒有選擇。如果您想要具有一個(gè)擴(kuò)展的 PHP 應(yīng)用程序,那么必須有效地使用數(shù)據(jù)庫,這意味著更智能的查詢。
本例的問題是它有點(diǎn)太清晰了。通常來說,這些類型的 n+1 或 n*n 問題要微妙得多。并且它們只有在數(shù)據(jù)庫管理員在系統(tǒng)具有性能問題時(shí)在系統(tǒng)上運(yùn)行查詢剖析器時(shí)才會出現(xiàn)。
結(jié)束語
數(shù)據(jù)庫是強(qiáng)大的工具,就跟所有強(qiáng)大的工具一樣,如果您不知道如何正確地使用就會濫用它們。識別和解決這些問題的訣竅是更好地理解底層技術(shù)。長期以來,我老聽到業(yè)務(wù)邏輯編寫人員抱怨,他們不想要必須理解數(shù)據(jù)庫或 SQL 代碼。他們把數(shù)據(jù)庫當(dāng)成對象使用,并疑惑性能為什么如此之差。
他們沒有認(rèn)識到,理解 SQL 對于將數(shù)據(jù)庫從一個(gè)困難的必需品轉(zhuǎn)換成強(qiáng)大的聯(lián)盟是多么重要。如果您每天使用數(shù)據(jù)庫,但是不熟悉 SQL,那么請閱讀 The Art of SQL,這本書寫得很好,實(shí)踐性也很強(qiáng),可以指導(dǎo)您基本了解數(shù)據(jù)庫。
相關(guān)文章
php保留數(shù)字小數(shù)點(diǎn)后兩位的方法
我們在學(xué)習(xí)或生活中經(jīng)常會遇到需要保留數(shù)字小數(shù)點(diǎn)后兩位的問題,所以本文小編給大家介紹了使用php保留數(shù)字小數(shù)點(diǎn)后兩位的方法,文中通過代碼示例介紹的非常詳細(xì),感興趣的同學(xué)可以參考閱讀下2023-12-12PHP面向?qū)ο蟪绦蛟O(shè)計(jì)之類常量用法實(shí)例
這篇文章主要介紹了PHP面向?qū)ο蟪绦蛟O(shè)計(jì)之類常量用法,是PHP面向?qū)ο蟪绦蛟O(shè)計(jì)中非常重要的一個(gè)概念,對于PHP初學(xué)者來說更是有必要加以牢固掌握,需要的朋友可以參考下2014-08-08php !function_exists("T7FC56270E7A70FA81A5935B72EACBE29
今天在百度知道上面有個(gè)朋友問php代碼解密的問題,看了代碼不是常見幾種比較感興趣,特意搜索了下,發(fā)現(xiàn)下面的方法,解決了,具體的看最后的說明。2011-01-01php遇到錯(cuò)誤Call to undefined function ImageCreate()解決方法
剛配置好服務(wù)器,運(yùn)行php的時(shí)候提示Call to undefined function imagecreate錯(cuò)誤,經(jīng)過百度發(fā)現(xiàn)是php不支持gd庫,linux服務(wù)器需要重新make,windows下比較簡單了,下面是具體的方法2021-09-09php 將字符串按大寫字母分隔成字符串?dāng)?shù)組
php 將字符串按大寫字母分隔成字符串?dāng)?shù)組,需要的朋友可以參考下。2010-04-04