2

データベース内のすべてのユーザーを選択するページがあります。1000か2しかありません。大きな問題ではない。

ただし、それを選択している間、そのクエリの uid を使用して、約 25,000 エントリの別のテーブルをチェックしています。

SELECT COUNT(id)
FROM logs
WHERE time+date > {$timeNow} AND uid={$row['id']}

これは、ユーザー エントリごとに行われます。ご想像のとおり、これはかなりのリソースを消費します。

上記のWHERE条項は、おそらく最終日のエントリ、最大で 500 ~ 1000 件にのみ適用されます。ただし、それ以上の効果が期待できます。

WHERE1 日に 1 回か 2 回、句に一致しないすべてのエントリを別のテーブルにエクスポートするように cronjob を設定できると考えていました。私はそれが劇的に助けになり、問題をある程度効率的な方法で解決することさえ知っています. ただし、同じ (相対的な) 目的で 2 つのテーブルを使用するのはあまり好きではありません。

私ができるより良い方法はありますか?しばらく検索しましたが、何も見つかりませんでしたが、同じ問題に遭遇し、それを解決するための独自の方法を見つけた場合に備えて、皆さんに尋ねたいと思いました.

ブレンダン・ロングの編集:私の新しいクエリ:

$SQL = "SELECT u.id, COUNT(l.id) " .
       "FROM users u " .
       "INNER JOIN logs l " .
       "ON l.uid = u.id " .
       "WHERE l.time+l.date > {$timeNow} " .
       "GROUP BY u.id";

また、PDO がないことで私を非難しないでください。これをまだ変換する時間がありません。私は自分がひどい人であることを知っています。

4

2 に答える 2

4

JOINを使用して、データベースが1つのクエリとして最適化できるようにします。

SELECT u.uid, COUNT(l.id)
FROM Users u -- or whatever your users table is named
LEFT JOIN logs l
ON l.uid = u.uid AND l.time + l.date > $timeNow
GROUP BY u.uid

英語では、これはデータベースに「ユーザーIDとそれに関連付けられたログの数のリストを取得します。ここで、time + dateは後になります$timeNow」と通知します。これは、データベースにすべての作業を一度に提供するため、非常に効率的です。したがって、一度に1つずつ取得するのではなく、すべての情報を取得するための最適な方法を見つけることができます。

参加する

LEFT JOIN、usersテーブルとlogsテーブルが同じであるレコードを検索することにより、ユーザーとログを照合するようにデータベースに指示しますuidLEFTinは、ユーザー(結合の右側)にログが関連付けられていない場合でも、ユーザー(結合の左側)のLEFT JOIN結果を返すようにデータベースに指示します。ユーザーのログがない場合の結果を表示したくない場合は、を実行できます。これにより、結合の両側(ユーザーと少なくとも1つのログメッセージの両方)に一致する結果のみが表示されます。INNER JOIN

グループ化

結果をユーザーIDでグループ化する必要があります。そうしないと、任意のGROUP BYユーザーに関連付けられたログメッセージの総数を取得するだけで、おそらくは役に立たない可能性があります。SELECT COUNT(*) FROM logs

私はテーブルエイリアスを使用してクエリを短くしています。これは私がいつも使用しているスタイルだからですが、テーブルのフルネーム(logs.uidなど)を簡単に入力できます。テーブル名を含めなくても回避できる場合もありますが、クエリで複数のテーブルに存在する列を参照するとデータベースが混乱するため、どの列を常に明示するのが最も簡単だと思います。話し直します。

インデックス

この新しいクエリは、データベースがめちゃくちゃ大きい場合を除いて、すぐに終了するはずです。そうでない場合は、@ charlyのアドバイスを受けて、いくつかのインデックスを試してください。残念ながら、l.time + l.date値を使用する前に追加します。MySQLでインデックスを作成できるとは思いませんが、最初l.time + l.dateにフィルタリングすることで適切な結果を得ることができる場合がありますl.date(これはインデックス可能です)。

ON l.uid = u.uid AND l.date > $timeNow AND l.time + l.date > $timeNow

これは繰り返しのように見えますが、次のことができるため、データベースでの作業が増えます。

  1. インデックスを使用したl.date後の結果を取得します。$timeNow
  2. その(できれば小さい)結果のセットを。でフィルタリングしl.time + l.date > $timeNowます。

それ以外の:

  1. テーブル内のすべてのレコードに対して、を追加しl.time + l.dateます。
  2. その結果が後かどうかを確認します$timeNow

PHP

PHPでこれを行うには、次のようなことを行います。

$sql = // that query above
$result = mysql_query($sql);
while($row = mysql_fetch_array($result)) {
    echo "User " . $row[0] . " posted " . $row[1] . " times.";
}

または、これをより複雑な方法で使用する必要がある場合は、すべてを事前に取得してください。

$counts = array();
$sql = // that query above
$result = mysql_query($sql);
while($row = mysql_fetch_array($result)) {
    $counts[$row[0]] = $row[1];
}

// later
$user = 5; // some user we care about
echo "User " . $user . " posted " . $counts[$user] . " times.";

「すべてを前もってフェッチする」方法で行う場合は、INNER JOIN含まれていないユーザー$countsのカウントが0であるという知識を持ったバージョンのクエリを使用して、少し最適化することもできます。

私の構文が間違っている場合は申し訳ありませんが、これはアイデアを示していると思います。

セキュリティノート

マイナーな接線で:変数をクエリに直接ドロップしているように見えますが、これは一般的に悪い考えです。非常に複雑なソリューションは数多くありますが、最も簡単なのは、パラメーター化されたクエリを使用し、変数をSQLに直接配置しないことです。

于 2012-10-03T20:57:05.280 に答える
0

よくわかりませんが、uid列にBTREEインデックスを追加している可能性があります。そうすれば、指定されたuid以外のすべてのログをスキャンしないため、クエリははるかに効率的になります。

100%確信はありませんが

于 2012-10-03T20:58:51.180 に答える