2

MySQL クエリを取得して個々のクエリに変換するスクリプト、つまりクエリを動的に非正規化するスクリプトを見つけようとしています。

テストとして、4 つのテーブルを持つ単純な記事システムを構築しました。

  • 記事
    • article_id
    • article_format_id
    • 記事のタイトル
    • article_body
    • article_date
  • article_categories
    • article_id
    • カテゴリ ID
  • カテゴリー
    • カテゴリ ID
    • カテゴリ_タイトル
  • フォーマット
    • format_id
    • format_title

記事は複数のカテゴリに属する​​ことができますが、フォーマットは 1 つだけです。これは現実の状況の良い例だと思います。

すべての記事を一覧表示するカテゴリ ページ (format_title もプル) では、次のクエリを使用して簡単に実現できます。

SELECT articles.*, formats.format_title 
FROM articles 
INNER JOIN formats ON articles.article_format_id = formats.format_id 
INNER JOIN article_categories ON articles.article_id = article_categories.article_id 
WHERE article_categories.category_id = 2 
ORDER BY articles.article_date DESC

ただし、作成しようとしているスクリプトは、このクエリを受け取り、解析して、クエリを個別に実行します。

したがって、このカテゴリ ページの例では、スクリプトはこれを効果的に実行します (動的に実行されます)。

// Select article_categories
$sql = "SELECT * FROM article_categories WHERE category_id = 2";
$query = mysql_query($sql);
while ($row_article_categories = mysql_fetch_array($query, MYSQL_ASSOC)) {

    // Select articles
    $sql2 = "SELECT * FROM articles WHERE article_id = " . $row_article_categories['article_id'];
    $query2 = mysql_query($sql2);
    while ($row_articles = mysql_fetch_array($query2, MYSQL_ASSOC)) {

        // Select formats
        $sql3 = "SELECT * FROM formats WHERE format_id = " . $row_articles['article_format_id'];
        $query3 = mysql_query($sql3);
        $row_formats = mysql_fetch_array($query3, MYSQL_ASSOC);

        // Merge articles and formats
        $row_articles = array_merge($row_articles, $row_formats);

        // Add to array
        $out[] = $row_articles;
    }
}

// Sort articles by date
foreach ($out as $key => $row) {
    $arr[$key] = $row['article_date'];
}

array_multisort($arr, SORT_DESC, $out);

// Output articles - this would not be part of the script obviously it should just return the $out array
foreach ($out as $row) {
    echo '<p><a href="article.php?id='.$row['article_id'].'">'.$row['article_title'].'</a> <i>('.$row['format_title'].')</i><br />'.$row['article_body'].'<br /><span class="date">'.date("F jS Y", strtotime($row['article_date'])).'</span></p>';
}

これの課題は、SELECT および JOIN の列名をクエリ内の任意の順序で配置できるため、正しいクエリを正しい順序で実行すること (これは MySQL および他の SQL データベースが非常にうまく変換するものです) と、情報ロジックを実行することです。 PHPで。

私は現在、クエリを多次元配列に分割するのにうまく機能するSQL_Parserを使用してクエリを解析していますが、上記のものを解決することは頭痛の種です。

どんな助けや提案も大歓迎です。

4

4 に答える 4

13

私が収集したものから、変更できないサードパーティのフォーラム アプリケーション (おそらく難読化されたコード?) と MySQL の間にレイヤーを配置しようとしています。このレイヤーは、クエリをインターセプトし、個別に実行できるように書き直して、データベースに対して実行するための PHP コードを生成し、集計結果を返します。これは非常に悪い考えです。

コードを追加することは不可能であるとほのめかし、同時に追加するコードを生成することを提案するのは奇妙に思えます。コードを挿入するためにfuncallのようなものを使用する予定がないことを願っています。これは非常に悪い考えです。

最初のアプローチを避けてデータベースに集中するようにという他の人からの呼びかけは、非常に適切なアドバイスです。私は、うまくいけば成長するコーラスに私の声を加えます。

いくつかの制約があると仮定します:

  • MySQL 5.0 以降を実行している。
  • クエリは変更できません。
  • データベース テーブルは変更できません。
  • 問題のあるクエリが参照しているテーブルには、適切なインデックスが既に配置されています。
  • DB にヒットする遅いクエリをトリプルチェック(および EXPLAIN を実行) し、実行を高速化するのに役立つインデックスをセットアップしようとしました。
  • 内部結合が MySQL インストールに与える負荷は許容できません。

考えられる解決策は次の 3 つです。

  1. 現在のデータベースに投資することで、この問題に簡単に対処できます。それを実行するハードウェアを、より多くのコア、より多くの (余裕のある) RAM、およびより高速なディスクを備えたものにアップグレードします。お金に余裕があるなら、Fusion-io の製品はこの種の用途に強く推奨されます。これはおそらく、私が提供する 3 つのオプションの中で最も単純なものです。
  2. 2 つ目のマスター MySQL データベースをセットアップし、最初のデータベースとペアにします。AUTO_INCREMENT ID の変更を強制できることを確認してください (1 つの DB は偶数の ID を使用し、もう 1 つの DB は奇数を使用します)。これは永遠に拡張できるわけではありませんが、ハードウェアとラック スペースの価格に余裕ができます。繰り返しますが、ハードウェアを強化します。すでにこれを行っているかもしれませんが、そうでない場合は検討する価値があります。
  3. dbShardsのようなものを使用してください。これにはさらに多くのハードウェアを投入する必要がありますが、2 台のマシンを超えて拡張できるという追加の利点があり、時間の経過とともに低コストのハードウェアを購入できます。
于 2011-02-22T08:34:40.757 に答える
3

データベースのパフォーマンスを改善するには、通常、次の方法を探します。

  • データベース呼び出しの数を減らす
  • 各データベース呼び出しを可能な限り効率的にする (優れた設計による)
  • 転送するデータ量を減らす

...そして、あなたは正反対のことをしていますか?故意に?
何の根拠で?

申し訳ありませんが、これは完全に間違ったやり方であり、この道で遭遇するすべての問題はすべて、データベース エンジンの外部にデータベース エンジンを実装するという最初の決定の結果です。納期までずっと回避策を講じることを余儀なくされます。(そこに着いたら)。

また、フォーラムについて話しているのですか?つまり、さあ!ほとんどの「Web 規模の素晴らしいソース」フォーラムでさえ、平均で 100 tps 未満について話しているのですか? あなたのラップトップでそれを行うことができます!

私のアドバイスは、これらすべてを忘れて、可能な限り最も単純な方法で実装することです。次に、集計 (最新、人気、統計など) をアプリケーション層にキャッシュします。フォーラムの他のすべては、すでに主キーの検索です。

于 2011-02-21T13:48:48.707 に答える
0

SQLの書き換えの代わりに、非正規化された記事テーブルを作成し、記事の挿入/削除/更新ごとに変更する必要があると思います. それははるかに簡単で安価になります。

作成して入力します。

create table articles_denormalized
...

insert into articles_denormalized 
    SELECT articles.*, formats.format_title 
    FROM articles 
    INNER JOIN formats ON articles.article_format_id = formats.format_id 
    INNER JOIN article_categories ON articles.article_id = article_categories.article_id 

それに対して適切な記事の挿入/更新/削除を発行すると、非正規化されたテーブルがいつでも照会できるようになります。

于 2011-02-21T12:15:34.950 に答える
0

悪い選択のように聞こえるのは同意しますが、クエリを分割すると便利な状況がいくつか考えられます。

クエリを解析するために正規表現に大きく依存して、これと同様のことを試みます。非常に限られたケースで機能しますが、必要に応じてサポートを段階的に拡張できます。

<?php
/**
 * That's a weird problem, but an interesting challenge!
 * @link http://stackoverflow.com/questions/5019467/problem-writing-a-mysql-parser-to-split-joins-and-run-them-as-individual-query
 */

// Taken from the given example:
$sql = "SELECT articles.*, formats.format_title 
FROM articles 
INNER JOIN formats ON articles.article_format_id = formats.format_id 
INNER JOIN article_categories ON articles.article_id = article_categories.article_id 
WHERE article_categories.category_id = 2 
ORDER BY articles.article_date DESC";

// Parse query
// (Limited to the clauses that are present in the example...)
// Edit: Made WHERE optional
if(!preg_match('/^\s*'.
    'SELECT\s+(?P<select_rows>.*[^\s])'. 
    '\s+FROM\s+(?P<from>.*[^\s])'.
    '(?:\s+WHERE\s+(?P<where>.*[^\s]))?'.
    '(?:\s+ORDER\s+BY\s+(?P<order_by>.*[^\s]))?'.
    '(?:\s+(?P<desc>DESC))?'.
    '(.*)$/is',$sql,$query)
) {
    trigger_error('Error parsing SQL!',E_USER_ERROR);
    return false;
}

## Dump matches
#foreach($query as $key => $value) if(!is_int($key)) echo "\"$key\" => \"$value\"<br/>\n";

/* We get the following matches:
"select_rows" => "articles.*, formats.format_title"
"from" => "articles INNER JOIN formats ON articles.article_format_id = formats.format_id INNER JOIN article_categories ON articles.article_id = article_categories.article_id"
"where" => "article_categories.category_id = 2"
"order_by" => "articles.article_date"
"desc" => "DESC"
/**/

// Will only support WHERE conditions separated by AND that are to be
// tested on a single individual table.
if(@$query['where']) // Edit: Made WHERE optional
    $where_conditions = preg_split('/\s+AND\s+/is',$query['where']);

// Retrieve individual table information & data
$tables = array();
$from_conditions = array();
$from_tables = preg_split('/\s+INNER\s+JOIN\s+/is',$query['from']);

foreach($from_tables as $from_table) {

    if(!preg_match('/^(?P<table_name>[^\s]*)'.
        '(?P<on_clause>\s+ON\s+(?P<table_a>.*)\.(?P<column_a>.*)\s*'.
        '=\s*(?P<table_b>.*)\.(?P<column_b>.*))?$/im',$from_table,$matches)
    ) {
        trigger_error("Error parsing SQL! Unexpected format in FROM clause: $from_table", E_USER_ERROR);
        return false;
    }
    ## Dump matches
    #foreach($matches as $key => $value) if(!is_int($key)) echo "\"$key\" => \"$value\"<br/>\n";

    // Remember on_clause for later jointure
    // We do assume each INNER JOIN's ON clause compares left table to
    // right table. Forget about parsing more complex conditions in the
    // ON clause...
    if(@$matches['on_clause'])
        $from_conditions[$matches['table_name']] = array(
            'column_a' => $matches['column_a'],
            'column_b' => $matches['column_b']
        );

    // Match applicable WHERE conditions
    $where = array();
    if(@$query['where']) // Edit: Made WHERE optional
    foreach($where_conditions as $where_condition)
        if(preg_match("/^$matches[table_name]\.(.*)$/",$where_condition,$matched))
            $where[] = $matched[1];
    $where_clause = empty($where) ? null : implode(' AND ',$where);

    // We simply ignore $query[select_rows] and use '*' everywhere...
    $query = "SELECT * FROM $matches[table_name]".($where_clause? " WHERE $where_clause" : '');
    echo "$query<br/>\n";

    // Retrieve table's data
    // Fetching the entire table data right away avoids multiplying MySQL
    // queries exponentially...
    $table = array();
    if($results = mysql_query($table))
        while($row = mysql_fetch_array($results, MYSQL_ASSOC))
            $table[] = $row;

    // Sort table if applicable
    if(preg_match("/^$matches[table_name]\.(.*)$/",$query['order_by'],$matched)) {
        $sort_key = $matched[1];

        // @todo Do your bubble sort here!

        if(@$query['desc']) array_reverse($table);
    }

    $tables[$matches['table_name']] = $table;
}

// From here, all data is fetched.
// All left to do is the actual jointure.

/**
 * Equijoin/Theta-join.
 * Joins relation $R and $S where $a from $R compares to $b from $S.
 * @param array $R A relation (set of tuples).
 * @param array $S A relation (set of tuples).
 * @param string $a Attribute from $R to compare.
 * @param string $b Attribute from $S to compare.
 * @return array A relation resulting from the equijoin/theta-join.
 */
function equijoin($R,$S,$a,$b) {
    $T = array();
    if(empty($R) or empty($S)) return $T;
    foreach($R as $tupleR) foreach($S as $tupleS)
        if($tupleR[$a] == @$tupleS[$b])
            $T[] = array_merge($tupleR,$tupleS);
    return $T;
}

$jointure = array_shift($tables);
if(!empty($tables)) foreach($tables as $table_name => $table)
    $jointure = equijoin($jointure, $table,
        $from_conditions[$table_name]['column_a'],
        $from_conditions[$table_name]['column_b']);

return $jointure;

?>

おやすみなさい、そして頑張ってください!

于 2011-02-27T11:42:27.657 に答える