15

現在、私のコード (PHP) には SQL クエリが多すぎます。例えば...

// not a real example, but you get the idea...
$results = $db->GetResults("SELECT * FROM sometable WHERE iUser=$userid");
if ($results) {
    // Do something
}

これを減らしてもう少し堅牢にするためにストアドプロシージャを使用することを検討していますが、いくつか懸念があります..

私は Web サイトで何百もの異なるクエリを使用していますが、それらの多くは非常に似ています。これらのクエリがコンテキスト (結果を使用するコード) から削除され、データベースのストアド プロシージャに配置された場合、これらすべてのクエリをどのように管理すればよいでしょうか?

4

10 に答える 10

30

最善の行動方針は、データ アクセスにどのように取り組んでいるかによって異なります。次の 3 つの方法があります。

  • ストアド プロシージャを使用する
  • コード内にクエリを保持します (ただし、前述のように、すべてのクエリを関数に入れ、パラメーターに PDO を使用するようにすべてを修正します)。
  • ORM ツールを使用する

独自の未加工の SQL をデータベース エンジンに渡したい場合、PHP コードから未加工の SQL を取得し、それを比較的変更せずに維持するだけであれば、ストアド プロシージャが適しています。ストアド プロシージャと生の SQL の議論は聖戦のようなものですが、K. Scott Allen は、データベースのバージョン管理に関する記事で、使い捨てではありますが、優れた点を挙げています。

第 2 に、ストアド プロシージャは私の目には好まれなくなりました。私は、ストアド プロシージャを常に使用する必要があるという WinDNA 教化派の出身です。今日、私はストアド プロシージャをデータベースの API レイヤーと見なしています。これは、データベース レベルで API レイヤーが必要な場合に適していますが、必要のない追加の API レイヤーを作成して維持するためのオーバーヘッドが発生する多くのアプリケーションを目にします。これらのアプリケーションでは、ストアド プロシージャはメリットよりも負担になります。

私は、ストアド プロシージャを使用しない傾向があります。私は、DB がストアド プロシージャを介して公開された API を持つプロジェクトに取り組んできましたが、ストアド プロシージャは独自の制限を課す可能性があり、それらのプロジェクトはすべて、程度の差こそあれ、コード内で動的に生成された生の SQL を使用して DB にアクセスします。

DB に API レイヤーを配置すると、DB チームと開発チームの間の責任が明確になりますが、クエリがコード内に保持されていた場合の柔軟性がいくらか犠牲になります。この描写から利益を得るのに十分なチーム。

概念的には、おそらくデータベースをバージョン管理する必要があります。ただし、実際には、データベースをバージョン管理するよりも、コードだけをバージョン管理する可能性の方がはるかに高くなります。コードを変更するときにクエリを変更する可能性がありますが、データベースに対して格納されたストアド プロシージャのクエリを変更している場合は、コードをチェックインするときにそれらをチェックインしていない可能性があります。アプリケーションの重要な領域に対するバージョニングの多くの利点。

ただし、ストアド プロシージャを使用しないことを選択したかどうかに関係なく、少なくとも、各データベース操作がページの各スクリプトに埋め込まれているのではなく、独立した関数に格納されていることを確認する必要があります。基本的には、DB の API レイヤーです。コードで維持およびバージョン管理されます。ストアド プロシージャを使用している場合、これは事実上、DB に 2 つの API レイヤーが存在することを意味します。1 つはコードを含み、もう 1 つは DB を含みます。プロジェクトに個別のチームがない場合、これは不必要に複雑になると感じるかもしれません。私は確かにそうします。

問題がコードの簡潔さの 1 つである場合、SQL が詰まったコードをより見やすくする方法があります。以下に示す UserManager クラスは、開始するのに適した方法です。このクラスには、「ユーザー」テーブルに関連するクエリのみが含まれています。各クエリにはクラス内に独自のメソッドがあり、クエリは準備ステートメントにインデントされ、ストアド プロシージャでフォーマットするのと同じようにフォーマットされます。

// UserManager.php:

class UserManager
{
    function getUsers()
    {
        $pdo = new PDO(...);
        $stmt = $pdo->prepare('
            SELECT       u.userId as id,
                         u.userName,
                         g.groupId,
                         g.groupName
            FROM         user u
            INNER JOIN   group g
            ON           u.groupId = g.groupId
            ORDER BY     u.userName, g.groupName
        ');
        // iterate over result and prepare return value
    }

    function getUser($id) {
        // db code here
    }
}

// index.php:
require_once("UserManager.php");
$um = new UserManager;
$users = $um->getUsers();
foreach ($users as $user) echo $user['name'];

ただし、クエリが非常に似ていても、複雑なページング、並べ替え、フィルタリングなど、クエリ条件に膨大な数の順列がある場合は、既存のコードをオーバーホールするプロセスが必要になるものの、オブジェクト/リレーショナル マッパー ツールを使用することをお勧めします。ツールの使用は非常に複雑になる可能性があります。

ORM ツールを調べることにした場合は、 Yiiの ActiveRecord コンポーネントであるPropel、または王様の PHP ORM であるDoctrineを調べる必要があります。これらのそれぞれにより、あらゆる種類の複雑なロジックを使用して、データベースへのクエリをプログラムで構築することができます。Doctrine は最も機能が充実しており、Nested Set ツリー パターンなどをすぐに使用してデータベースをテンプレート化できます。

パフォーマンスに関しては、ストアド プロシージャが最も高速ですが、通常は生の SQL に比べてそれほど大きくはありません。ORM ツールは、非効率的または冗長なクエリ、各リクエストで ORM ライブラリをロードする際の膨大なファイル IO、各クエリでの動的 SQL 生成など、さまざまな方法でパフォーマンスに重大な影響を与える可能性があります。これらすべてが影響を与える可能性がありますが、 ORM ツールを使用すると、手動クエリを使用して独自の DB レイヤーを作成するよりもはるかに少ないコードで、利用できる機能を大幅に増やすことができます。

ただし、 Gary Richardsonの言うとおりです。コードで SQL を使用し続ける場合は、クエリを使用しているかストアド プロシージャを使用しているかに関係なく、常に PDO のプリペアド ステートメントを使用してパラメーターを処理する必要があります。入力のサニタイズは、PDO によって実行されます。

// optional
$attrs = array(PDO::ATTR_PERSISTENT => true);

// create the PDO object
$pdo = new PDO("mysql:host=localhost;dbname=test", "user", "pass", $attrs);

// also optional, but it makes PDO raise exceptions instead of 
// PHP errors which are far more useful for debugging
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

$stmt = $pdo->prepare('INSERT INTO venue(venueName, regionId) VALUES(:venueName, :regionId)');
$stmt->bindValue(":venueName", "test");
$stmt->bindValue(":regionId", 1);

$stmt->execute();

$lastInsertId = $pdo->lastInsertId();
var_dump($lastInsertId);

警告: ID が 1 であると仮定すると、上記のスクリプトは を出力しますstring(1) "1"PDO->lastInsertId()実際の列が整数であるかどうかに関係なく、ID を文字列として返します。PHP は文字列を整数に自動的にキャストするため、これが問題になることはおそらくないでしょう。

以下は出力されますbool(true)

// regular equality test
var_dump($lastInsertId == 1); 

しかし、 is_intや PHP の「本当に、本当に、100% 等しい」演算子のように、値が整数であることを期待しているコードがある場合:

var_dump(is_int($lastInsertId));
var_dump($lastInsertId === 1);

いくつかの問題が発生する可能性があります。

編集:ここでストアドプロシージャに関するいくつかの良い議論

于 2008-09-18T02:23:58.567 に答える
4

まず、変数を直接補間するのではなく、クエリでプレースホルダーを使用する必要があります。PDO/MySQLi では、次のようなクエリを記述できます。

SELECT * FROM sometable WHERE iUser = ?

API は値を安全にクエリに代入します。

また、データベースではなくコードでクエリを実行することも好みます。クエリがコードに含まれている場合、RCS を使用する方がはるかに簡単です。

ORM を操作するときの経験則があります。一度に 1 つのエンティティを操作する場合は、インターフェイスを使用します。レコードを集計してレポート/作業している場合は、通常、SQL クエリを作成してそれを実行します。これは、コード内のクエリがほとんどないことを意味します。

于 2008-09-01T15:19:37.707 に答える
3

すべての SQL を別の Perl モジュール (.pm) に移動します。多くのクエリは、わずかに異なるパラメーターを使用して同じ関数を再利用できます。

開発者にとってよくある間違いは、ORM ライブラリ、パラメーター化されたクエリ、およびストアド プロシージャに飛び込むことです。その後、コードを「より良く」するために何ヶ月も作業を続けますが、それは開発のような方法でのみ「より良く」なります。あなたは新しい機能を作っていません!

コードの複雑さは、顧客のニーズに対応する場合にのみ使用してください。

于 2009-01-10T09:55:46.080 に答える
3

インジェクションの脆弱性に悩まされている多くの(重複/類似の)クエリがあるプロジェクトをクリーンアップする必要がありました。私が取った最初のステップは、プレースホルダーを使用し、すべてのクエリに、オブジェクト/メソッドと、クエリが作成されたソース行をラベル付けすることでした。(PHP 定数METHODLINEを SQL コメント行に挿入します)

それは次のように見えました:

-- @Line:151 UserClass::getuser():

SELECT * FROM USERS;

すべてのクエリを短時間ログに記録することで、どのクエリをマージするかについていくつかの出発点が得られました。(そしてどこに!)

于 2008-09-17T21:14:54.393 に答える
2

ORMパッケージを使用してください。半分まともなパッケージなら、

  1. 単純な結果セットを取得する
  2. 複雑な SQL をデータ モデルに近づける

非常に複雑な SQL を使用している場合、ビューは、アプリケーションのさまざまなレイヤーに表示しやすくするのにも役立ちます。

于 2008-09-01T11:41:16.080 に答える
2

私たちはかつて同じような苦境に立たされました。特定のテーブルに対して、50 以上のさまざまな方法でクエリを実行しました。

最終的に行ったことは、WhereClause のパラメーター値を含む単一の Fetch ストアド プロシージャを作成することでした。WhereClause は Provider オブジェクトで構築され、SQL インジェクション攻撃に対して スクラブできる Facade デザイン パターンを採用しました。

そのため、メンテナンスに関する限り、変更は簡単です。SQL Server も非常に仲が良く、動的クエリの実行プランをキャッシュするため、全体的なパフォーマンスはかなり良好です。

独自のシステムとニーズに基づいてパフォーマンスの欠点を判断する必要がありますが、これは私たちにとって非常にうまく機能します。

于 2008-09-01T11:48:01.693 に答える
1

PEAR の MDB2 など、クエリをより簡単かつ安全にするライブラリがいくつかあります。

残念なことに、それらを設定するには少し長文になる可能性があり、同じ情報を 2 回渡す必要がある場合もあります。私はいくつかのプロジェクトで MDB2 を使用してきましたが、特にフィールドのタイプを指定するために、MDB2 の周りに薄いベニアを書く傾向がありました。私は通常、特定のテーブルとその列を認識するオブジェクトを作成し、MDB2 クエリ関数を呼び出すときにフィールド タイプを入力するヘルパー関数を作成します。

例えば:

function MakeTableTypes($TableName, $FieldNames)
{
    $Types = array();

    foreach ($FieldNames as $FieldName => $FieldValue)
    {
        $Types[] = $this->Tables[$TableName]['schema'][$FieldName]['type'];
    }

    return $Types;
}

明らかに、このオブジェクトはテーブル名のマップを持っています->それが知っているスキーマであり、指定したフィールドの型を抽出するだけで、MDB2 クエリでの使用に適した一致する型の配列を返します。

次に、MDB2 (および同様のライブラリ) がパラメーター置換を処理するため、更新/挿入クエリの場合、列名から値へのハッシュ/マップを構築し、「autoExecute」関数を使用して関連するクエリを構築および実行します。

例えば:

function UpdateArticle($Article)
{
    $Types = $this->MakeTableTypes($table_name, $Article);

    $res = $this->MDB2->extended->autoExecute($table_name,
        $Article,
        MDB2_AUTOQUERY_UPDATE,
        'id = '.$this->MDB2->quote($Article['id'], 'integer'),
        $Types);
}

MDB2 はクエリを構築し、すべてを適切にエスケープします。

ただし、MDB2 でパフォーマンスを測定することをお勧めします。これは、PHP アクセラレータを実行していない場合に問題を引き起こす可能性のあるかなりの量のコードを取り込むためです。

私が言うように、セットアップのオーバーヘッドは最初は気が遠くなるように見えますが、いったん完了すると、クエリはより簡単に/よりシンボリックに記述および (特に) 変更できます。MDB2 はスキーマについてもう少し知っておくべきだと思います。これにより、一般的に使用される API 呼び出しの一部が簡素化されますが、前述のようにスキーマを自分でカプセル化し、配列 MDB2 はこれらのクエリを実行する必要があります。

もちろん、必要に応じて query() 関数を使用してフラットな SQL クエリを文字列として実行することもできます。そのため、完全な「MDB2 方式」に切り替える必要はありません。少しずつ試してみて、嫌いかどうか。

于 2008-09-01T17:14:44.360 に答える
0

私はかなり一般的な関数を使用しようとし、それらの違いを渡すだけです。このようにして、ほとんどのデータベース SELECT を処理する関数が 1 つだけになります。明らかに、すべての INSERTS を処理する別の関数を作成できます。

例えば。

function getFromDB($table, $wherefield=null, $whereval=null, $orderby=null) {
    if($wherefield != null) { 
        $q = "SELECT * FROM $table WHERE $wherefield = '$whereval'"; 
    } else { 
        $q = "SELECT * FROM $table";
    }
    if($orderby != null) { 
        $q .= " ORDER BY ".$orderby; 
    }

    $result = mysql_query($q)) or die("ERROR: ".mysql_error());
    while($row = mysql_fetch_assoc($result)) {
        $records[] = $row;
    }
    return $records;
}

これは私の頭のてっぺんから外れていますが、あなたはその考えを理解しています。それを使用するには、関数に必要なパラメーターを渡すだけです。

例えば。

$blogposts = getFromDB('myblog', 'author', 'Lewis', 'date DESC');

この場合、 $blogpostsはテーブルの各行を表す配列の配列になります。次に、 foreach を使用するか、配列を直接参照できます。

echo $blogposts[0]['title'];
于 2008-09-17T22:55:30.690 に答える
0

この他の質問には、いくつかの役立つリンクもあります...

于 2008-09-01T12:08:03.753 に答える
0

QCodo のような ORM フレームワークを使用 - 既存のデータベースを簡単にマッピングできます

于 2008-11-14T22:31:47.210 に答える