ホームページ >バックエンド開発 >PHPチュートリアル >PHP データベースでよくある 5 つの間違い_PHP チュートリアル
PHP データベースでよくある 5 つの間違い --- データベース スキーマ設計、データベース アクセス、データベースを使用するビジネス ロジック コードを含む --- とその解決策。
データベースの使用方法が 1 つだけ正しい場合...
データベース設計、データベース アクセス、データベース ベースの PHP ビジネス ロジック コードを作成する方法はたくさんありますが、通常はエラーが発生します。この記事では、データベース設計およびデータベースにアクセスする PHP コードで発生する 5 つの一般的な問題と、それらが発生した場合の修正方法について説明します。
質問 1: MySQL を直接使用します
よくある問題は、古い PHP コードが mysql_ 関数を使用してデータベースに直接アクセスすることです。リスト 1 は、データベースに直接アクセスする方法を示しています。
リスト 1. access/get.php
<?php
関数 get_user_id( $name )
{
$db = mysql_connect('localhost', 'root', 'passWord');
mysql_select_db('ユーザー');
$res = mysql_query( "ユーザー WHERE から ID を選択します。login=’".$name."'" );
while( $row = mysql_fetch_array( $res ) ) { $id = $row[0] }
$id を返す
}
var_dump(get_user_id('jack'));
?>
データベースへのアクセスには mysql_connect 関数が使用されることに注意してください。また、文字列連結を使用して $name パラメーターをクエリに追加するクエリにも注目してください。
このテクノロジには、PEAR DB モジュールと PHP データ オブジェクト (PDO) クラスの 2 つの優れた代替手段があります。どちらも、特定のデータベース選択からの抽象化を提供します。その結果、コードは、あまり調整することなく、IBM® DB2®、MySQL、PostgreSQL、または接続したいその他のデータベース上で実行できます。
PEAR DB モジュールと PDO 抽象化レイヤーを使用するもう 1 つの利点は、SQL ステートメントで ? 演算子を使用できることです。これにより、SQL の保守が容易になり、アプリケーションを SQL インジェクション攻撃から保護できます。
PEAR DB を使用した代替コードを以下に示します。
リスト 2./get_good.php にアクセスします
<?php
require_once("DB.php");
関数 get_user_id( $name )
{
$dsn = ‘mysql://root:password@localhost/users’;
$db =& DB::Connect( $dsn, array() );
if (PEAR::isError($db)) { die($db->getMessage()) }
$res = $db->query( 'SELECT ID FROM users WHERE login=?',array( $name ) );
$id = null;
while( $res->fetchInto( $row ) ) { $id = $row[0] }
;
$id を返す
}
var_dump(get_user_id('jack'));
?>
$dsn のデータベース接続文字列を除いて、MySQL の直接的な使用はすべて削除されていることに注意してください。さらに、SQL では ? 演算子を介して $name 変数を使用します。次に、クエリ データは、query() メソッドの最後で配列を通じて送信されます。
質問 2: 自動インクリメント機能を使用していない
最新のデータベースと同様に、MySQL にはレコードごとに自動インクリメントされる一意の識別子を作成する機能があります。さらに、最初に SELECT ステートメントを実行して最大の ID を検索し、次にその ID を 1 ずつインクリメントして、新しいレコードを検索するコードが引き続き表示されます。リスト 3 は、悪いパターンの例を示しています。
リスト 3. Badid.sql
ユーザーが存在する場合はテーブルを削除します
CREATE TABLE ユーザー (
id MEDIUMINT、
ログインテキスト、
パスワードテキスト
);
INSERT INTO users VALUES (1, 'jack', 'pass' );
INSERT INTO users VALUES ( 2, ‘joan’, ‘pass’ );
INSERT INTO users VALUES ( 1, ‘jane’, ‘pass’ );
ここでの id フィールドは単純に整数として指定されます。したがって、一意である必要がありますが、CREATE ステートメントに続くいくつかの INSERT ステートメントに示されているように、任意の値を追加できます。リスト 4 は、このタイプのスキーマにユーザーを追加するための PHP コードを示しています。
リスト 4. Add_user.php
<?php
require_once("DB.php");
関数 add_user( $name, $pass )
{
$rows = 配列();
$dsn = ‘mysql://root:password@localhost/bad_badid’;
$db =& DB::Connect( $dsn, array() );
if (PEAR::isError($db)) { die($db->getMessage()) }
$res = $db->query( "ユーザーから最大(id)を選択" );
$id = null;
while( $res->fetchInto( $row ) ) { $id = $row[0] }
;
$id += 1;
$sth = $db->PRepare( "ユーザーの値に挿入(?,?,?)" );
$db->execute( $sth, array( $id, $name, $pass ) );
$id を返す
}
$id = add_user('ジェリー', 'パス');
var_dump( $id );
?>
add_user.php のコードは、最初にクエリを実行して id の最大値を見つけます。次に、ファイルは、ID 値を 1 増やして INSERT ステートメントを実行します。このコードは、負荷の高いサーバーでは競合状態で失敗します。さらに、それは非効率でもあります。
では、代替手段は何でしょうか? MySQL の自動インクリメント機能を使用して、挿入ごとに一意の ID を自動的に作成します。更新されたスキーマは次のようになります。
リスト 5. Goodid.php
ユーザーが存在する場合はテーブルを削除します
CREATE TABLE ユーザー (
id MEDIUMINT NOT NULL AUTO_INCREMENT、
ログインテキストが NULL ではありません、
パスワードのテキストが NULL ではありません、
主キー( id )
);
INSERT INTO users VALUES (null、'jack'、'pass');
INSERT INTO users VALUES (null、'joan'、'pass' );
INSERT INTO users VALUES (null、'jane'、'pass' );
フィールドが空であってはいけないことを示すために、NOT NULL フラグを追加しました。また、フィールドが自動インクリメントされていることを示す AUTO_INCREMENT フラグと、どのフィールドが ID であるかを示す PRIMARY KEY フラグも追加しました。これらの変更により、作業がスピードアップします。リスト 6 は、テーブルにユーザーを挿入するための更新された PHP コードを示しています。
リスト 6. Add_user_good.php
<?php
require_once("DB.php");
関数 add_user( $name, $pass )
{
$dsn = ‘mysql://root:password@localhost/good_genid’;
$db =& DB::Connect( $dsn, array() );
if (PEAR::isError($db)) { die($db->getMessage()) }
$sth = $db->prepare( "ユーザーの値に挿入(null,?,?)" );
$db->execute( $sth, array( $name, $pass ) );
$res = $db->query( "SELECT last_insert_id()" );
$id = null;
while( $res->fetchInto( $row ) ) { $id = $row[0] }
;
$id を返す
}
$id = add_user('ジェリー', 'パス');
var_dump( $id );
?>
ここでは、最大の ID 値を取得する代わりに、INSERT ステートメントを直接使用してデータを挿入し、次に SELECT ステートメントを使用して最後に挿入されたレコードの ID を取得します。このコードは、元のバージョンおよびそれに関連するパターンよりもはるかにシンプルで効率的です。
質問 3: 複数のデータベースの使用
場合によっては、各テーブルが別個のデータベースに存在するアプリケーションを目にすることがあります。これは非常に大規模なデータベースでは妥当ですが、一般的なアプリケーションではこのレベルのパーティショニングは必要ありません。さらに、データベース間でリレーショナル クエリを実行することはできません。これにより、複数のデータベース間でテーブルを管理することがより困難になることは言うまでもなく、リレーショナル データベースを使用するという概念自体が失われてしまいます。では、複数のデータベースはどのようにあるべきでしょうか?まず、いくつかのデータが必要です。リスト 7 は、このようなデータを 4 つのファイルに分割したものを示しています。
リスト 7. データベース ファイル
ファイル.sql:
テーブルファイルの作成 (
id MEDIUMINT、
user_id MEDIUMINT、
名前テキスト、
パスのテキスト
);
Load_files.sql:
INSERT INTO files VALUES ( 1, 1, ‘test1.jpg’, ‘files/test1.jpg’ );
INSERT INTO files VALUES ( 2, 1, ‘test2.jpg’, ‘files/test2.jpg’ );
Users.sql:
ユーザーが存在する場合はテーブルを削除します
CREATE TABLE ユーザー (
id MEDIUMINT、
ログインテキスト、
パスワードテキスト
);
Load_users.sql:
INSERT INTO users VALUES (1, 'jack', 'pass' );
INSERT INTO users VALUES ( 2, ‘jon’, ‘pass’ );
これらのファイルのマルチデータベース バージョンでは、SQL ステートメントを 1 つのデータベースにロードしてから、ユーザーの SQL ステートメントを別のデータベースにロードする必要があります。特定のユーザーに関連付けられたファイルをデータベースにクエリするために使用される PHP コードを以下に示します。
リスト 8. Getfiles.php
<?php
require_once("DB.php");
関数 get_user( $name )
{
$dsn = ‘mysql://root:password@localhost/bad_multi1’;
$db =& DB::Connect( $dsn, array() );
if (PEAR::isError($db)) { die($db->getMessage()) }
$res = $db->query( "ユーザー WHERE から ID を選択します。login=?", array( $name ) );
$uid = null;
while( $res->fetchInto( $row ) ) { $uid = $row[0] }
;
$uid を返す
}
関数 get_files( $name )
{
$uid = get_user( $name );
$rows = 配列();
$dsn = ‘mysql://root:password@localhost/bad_multi2’;
$db =& DB::Connect( $dsn, array() );
if (PEAR::isError($db)) { die($db->getMessage()) }
$res = $db->query( "SELECT * FROM files WHERE user_id=?", array( $uid ) );
while( $res->fetchInto( $row ) ) { $rows[] = $row;
$行を返す
}
$files = get_files('ジャック');
var_dump( $files );
?>
get_user 関数は、users テーブルを含むデータベースに接続し、特定のユーザーの ID を取得します。 get_files 関数は、files テーブルに接続し、特定のユーザーに関連付けられたファイル行を取得します。
これらすべてを実行するより良い方法は、データをデータベースにロードしてから、次のクエリなどのクエリを実行することです。
リスト 9. Getfiles_good.php
<?php
require_once("DB.php");
関数 get_files( $name )
{
$rows = 配列();
$dsn = ‘mysql://root:password@localhost/good_multi’;
$db =& DB::Connect( $dsn, array() );
if (PEAR::isError($db)) { die($db->getMessage()) }
$res = $db->query("SELECT files.* FROM ユーザー、ファイル WHERE
users.login=? AND users.id=files.user_id",
配列( $name ) );
while( $res->fetchInto( $row ) ) { $rows[] = $row;
$行を返す
}
$files = get_files('ジャック');
var_dump( $files );
?>
コードが短くなっただけでなく、理解しやすくなり、効率も向上しました。 2 つのクエリを実行する代わりに、1 つのクエリを実行します。
この質問は突飛に聞こえるかもしれませんが、実際には、よほどの特別な理由がない限り、すべてのテーブルは同じデータベース内にあるべきであると通常結論付けられます。 問題 4: リレーションシップを使用していない
リレーショナル データベースは配列型を持たないという点でプログラミング言語とは異なります。代わりに、テーブル間の関係を使用して、オブジェクト間に 1 対多の構造を作成します。これは、配列と同じ効果があります。私がアプリケーションで見た問題の 1 つは、エンジニアがカンマ区切りの識別子を含むテキスト文字列を使用して配列を作成することにより、データベースをプログラミング言語のように使用しようとする場合です。以下のパターンを参照してください。
リスト 10. Bad.sql
ファイルが存在する場合はテーブルを削除します
テーブルファイルの作成 (
id MEDIUMINT、
名前テキスト、
パスのテキスト
);
ユーザーが存在する場合はテーブルを削除します
CREATE TABLE ユーザー (
id MEDIUMINT、
ログインテキスト、
パスワードテキスト、
ファイルテキスト
);
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' );
システム内のユーザーは複数のファイルを持つことができます。プログラミング言語では、ユーザーに関連付けられたファイルを表すために配列を使用する必要があります。この例では、プログラマは、ファイル ID のカンマ区切りリストを含むファイル フィールドを作成することを選択します。特定のユーザーのすべてのファイルのリストを取得するには、プログラマはまず users テーブルから行を読み取り、次にファイルのテキストを解析し、ファイルごとに個別の SELECT ステートメントを実行する必要があります。コードを以下に示します。
リスト 11. Get.php
<?php
require_once("DB.php");
関数 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( "ユーザー WHERE からファイルを選択します。login=?", array( $name ) );
$files = null
while( $res->fetchInto( $row ) ) { $files = $row[0] }
;
$rows = 配列();
foreach( Split( ’,’,$files ) as $file )
{
$res = $db->query( "SELECT * FROM files WHERE id=?",
配列( $file ) );
while( $res->fetchInto( $row ) ) { $rows[] = $row;
}
$行を返します
}
$files = get_files('ジャック');
var_dump( $files );
?>
このテクノロジーは遅く、保守が難しく、データベースをうまく活用できません。唯一の解決策は、以下に示すように、スキーマを再構築して従来のリレーショナル形式に変換することです。
リスト 12. Good.sql
ファイルが存在する場合はテーブルを削除します
テーブルファイルの作成 (
id MEDIUMINT、
user_id MEDIUMINT、
名前テキスト、
パスのテキスト
);
ユーザーが存在する場合はテーブルを削除します
CREATE TABLE ユーザー (
id MEDIUMINT、
ログインテキスト、
パスワードテキスト
);
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’ );
ここで、各ファイルは user_id 関数を通じてファイル テーブル内のユーザーに関連付けられます。これは、複数のファイルを配列として考える人の考え方に反するかもしれません。もちろん、配列はそれに含まれるオブジェクトを参照しません。実際にはその逆も同様です。しかし、リレーショナル データベースではこれが仕組みであり、そのおかげでクエリがはるかに高速かつ簡単になります。リスト 13 は、対応する PHP コードを示しています。
リスト 13. Get_good.php
<?php
require_once("DB.php");
関数 get_files( $name )
{
$dsn = ‘mysql://root:password@localhost/good_rel’;
$db =& DB::Connect( $dsn, array() );
if (PEAR::isError($db)) { die($db->getMessage()) }
$rows = 配列();
$res = $db->query("SELECT files.* FROM users, files WHERE users.login=?
かつ、 users.id=files.user_id",array( $name ) );
while( $res->fetchInto( $row ) ) { $rows[] = $row;
$行を返す
}
$files = get_files('ジャック');
var_dump( $files );
?>
ここでは、データベースにクエリを実行してすべての行を取得します。コードは複雑ではなく、意図したとおりにデータベースを使用します。
質問 5: n+1 モード
コードが最初にいくつかのエンティティ (顧客など) を取得し、次にそれらを 1 つずつ取得して各エンティティの詳細を取得するという大規模なアプリケーションを何度見たかわかりません。クエリが何度も実行されるため、これを n+1 モードと呼びます。1 つのクエリですべてのエンティティのリストが取得され、その後、n 個のエンティティごとに 1 つのクエリが実行されます。 n=10 の場合は問題ありませんが、n=100 や n=1000 の場合はどうでしょうか。そうなると、必ず非効率が生じます。リスト 14 は、このパターンの例を示しています。
リスト 14. Schema.sql
著者が存在する場合はテーブルを削除します
CREATE TABLE 作成者 (
id MEDIUMINT NOT NULL AUTO_INCREMENT、
名前のテキストが NULL ではありません、
主キー (ID)
);
本が存在する場合はテーブルを削除します
CREATE TABLE ブック (
id MEDIUMINT NOT NULL AUTO_INCREMENT、
author_id MEDIUMINT が NULL ではありません、
名前のテキストが NULL ではありません、
主キー (ID)
);
INSERT INTO authors VALUES ( null, ‘Jack Herrington’ );
著者の値に挿入 (null、'Dave Thomas' );
INSERT INTO Books VALUES ( null, 1, ‘コード生成の動作’ );
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, ‘プログラミング Ruby’ );
モデルは信頼でき、エラーはありません。問題は、以下に示すように、データベースにアクセスして特定の著者によるすべての書籍を検索するコードにあります。
リスト 15. Get.php
<?php
require_once('DB.php');
$dsn = ‘mysql://root:password@localhost/good_books’;
$db =& DB::Connect( $dsn, array() );
if (PEAR::isError($db)) { die($db->getMessage()) }
関数 get_author_id( $name )
{
グローバル $db
$res = $db->query( "SELECT ID FROM authors WHERE name=?", array( $name ) );
$id = null;
while( $res->fetchInto( $row ) ) { $id = $row[0] }
;
$id を返す
}
関数 get_books( $id )
{
グローバル $db
$res = $db->query( "書籍 WHERE author_id=?から ID を選択", array( $id ) );
$ids = 配列();
while( $res->fetchInto( $row ) ) { $ids []= $row[0] }
$id を返す
}
関数 get_book( $id )
{
グローバル $db
$res = $db->query( "SELECT * FROM Books WHERE id=?", array( $id ) );
while( $res->fetchInto( $row ) ) { return $row }
null を返す
}
$author_id = get_author_id('ジャック・ヘリントン');
$books = get_books( $author_id );
foreach( $books as $book_id ) {
$book = get_book( $book_id );
var_dump( $book );
}
?>
以下のコードを見ると、「これは非常に明確で簡単だ」と思うかもしれません。まず、著者 ID を取得し、次に書籍のリストを取得し、次に各書籍に関する情報を取得します。はい、それは明確でシンプルですが、効果的ですか?答えはいいえだ。 Jack Herrington の書籍を取得するためだけに実行されたクエリの数を確認します。 1 回目は ID を取得し、もう 1 回目は書籍のリストを取得し、書籍ごとにクエリを実行します。 3 冊の本には 5 つのクエリが必要です。
解決策は、以下に示すように、関数を使用して多数のクエリを実行することです。
リスト 16. Get_good.php
<?php
require_once('DB.php');
$dsn = ‘mysql://root:password@localhost/good_books’;
$db =& DB::Connect( $dsn, array() );
if (PEAR::isError($db)) { die($db->getMessage()) }
関数 get_books( $name )
{
グローバル $db
$res = $db->query("SELECTbooks.* FROM 著者,books WHERE Books.author_id=authors.id AND authors.name=?",
配列( $name ) );
$rows = 配列();
while( $res->fetchInto( $row ) ) { $rows []= $row;
$行を返す
}
$books = get_books('ジャック・ヘリントン');
var_dump( $books );
?>
リストを取得するには、迅速な単一のクエリが必要になります。これは、異なるパラメーターを持つこれらのタイプのメソッドをいくつか用意する必要がある可能性が高いことを意味しますが、実際には選択の余地はありません。スケーラブルな PHP アプリケーションを作成したい場合は、データベースを効率的に使用することが必須であり、これはよりスマートなクエリを意味します。
この例の問題は、少し明確すぎることです。一般に、この種の n+1 または n*n の問題は、はるかに微妙です。また、これらは、システムにパフォーマンスの問題がある場合に、データベース管理者がシステム上でクエリ プロファイラを実行する場合にのみ表示されます。
http://www.bkjia.com/PHPjc/371536.html
www.bkjia.com
本当
http://www.bkjia.com/PHPjc/371536.html
技術記事