クエリを最適化したいので、_mysql-slow.log
_を調べます。
私の遅いクエリのほとんどにはORDER BY Rand()
が含まれています。この問題を解決する実際の解決策が見つかりません。 MySQLPerformanceBlog に可能な解決策がありますが、これで十分ではないと思います。最適化が不十分な(または頻繁に更新される、ユーザー管理の)テーブルでは機能しないか、PHP
で生成されたランダム行を選択する前に2つ以上のクエリを実行する必要があります。
この問題の解決策はありますか?
ダミーの例:
_SELECT accomodation.ac_id,
accomodation.ac_status,
accomodation.ac_name,
accomodation.ac_status,
accomodation.ac_images
FROM accomodation, accomodation_category
WHERE accomodation.ac_status != 'draft'
AND accomodation.ac_category = accomodation_category.acat_id
AND accomodation_category.acat_slug != 'vendeglatohely'
AND ac_images != 'b:0;'
ORDER BY
Rand()
LIMIT 1
_
これを試して:
SELECT *
FROM (
SELECT @cnt := COUNT(*) + 1,
@lim := 10
FROM t_random
) vars
STRAIGHT_JOIN
(
SELECT r.*,
@lim := @lim - 1
FROM t_random r
WHERE (@cnt := @cnt - 1)
AND Rand(20090301) < @lim / @cnt
) i
これはMyISAM
で特に効率的です(COUNT(*)
がインスタントであるため)が、InnoDB
でもORDER BY Rand()
の10
倍の効率です。
ここでの主なアイデアは、ソートせず、代わりに2つの変数を保持して、現在のステップで選択される行のrunning probability
を計算することです。
詳細については、私のブログのこの記事を参照してください。
更新:
ランダムレコードを1つだけ選択する必要がある場合は、これを試してください。
SELECT aco.*
FROM (
SELECT minid + FLOOR((maxid - minid) * Rand()) AS randid
FROM (
SELECT MAX(ac_id) AS maxid, MIN(ac_id) AS minid
FROM accomodation
) q
) q2
JOIN accomodation aco
ON aco.ac_id =
COALESCE
(
(
SELECT accomodation.ac_id
FROM accomodation
WHERE ac_id > randid
AND ac_status != 'draft'
AND ac_images != 'b:0;'
AND NOT EXISTS
(
SELECT NULL
FROM accomodation_category
WHERE acat_id = ac_category
AND acat_slug = 'vendeglatohely'
)
ORDER BY
ac_id
LIMIT 1
),
(
SELECT accomodation.ac_id
FROM accomodation
WHERE ac_status != 'draft'
AND ac_images != 'b:0;'
AND NOT EXISTS
(
SELECT NULL
FROM accomodation_category
WHERE acat_id = ac_category
AND acat_slug = 'vendeglatohely'
)
ORDER BY
ac_id
LIMIT 1
)
)
これは、ac_id
がほぼ均等に配布されることを前提としています。
それは、あなたがどれほどランダムである必要があるかに依存します。リンクしたソリューションは、IMOで非常にうまく機能します。 IDフィールドに大きなギャップがない限り、それはまだかなりランダムです。
ただし、これを使用して1つのクエリで実行できるはずです(単一の値を選択するため)。
SELECT [fields] FROM [table] WHERE id >= FLOOR(Rand()*MAX(id)) LIMIT 1
その他の解決策:
random
という名前の永続的なfloatフィールドをテーブルに追加し、乱数を入力します。次に、PHPで乱数を生成し、"SELECT ... WHERE rnd > $random"
これが私がそれをする方法です:
SET @r := (SELECT ROUND(Rand() * (SELECT COUNT(*)
FROM accomodation a
JOIN accomodation_category c
ON (a.ac_category = c.acat_id)
WHERE a.ac_status != 'draft'
AND c.acat_slug != 'vendeglatohely'
AND a.ac_images != 'b:0;';
SET @sql := CONCAT('
SELECT a.ac_id,
a.ac_status,
a.ac_name,
a.ac_status,
a.ac_images
FROM accomodation a
JOIN accomodation_category c
ON (a.ac_category = c.acat_id)
WHERE a.ac_status != ''draft''
AND c.acat_slug != ''vendeglatohely''
AND a.ac_images != ''b:0;''
LIMIT ', @r, ', 1');
PREPARE stmt1 FROM @sql;
EXECUTE stmt1;
これにより、インデックスを使用してランダムIDを取得する単一のサブクエリが提供され、他のクエリが結合テーブルの取得を開始します。
SELECT accomodation.ac_id,
accomodation.ac_status,
accomodation.ac_name,
accomodation.ac_status,
accomodation.ac_images
FROM accomodation, accomodation_category
WHERE accomodation.ac_status != 'draft'
AND accomodation.ac_category = accomodation_category.acat_id
AND accomodation_category.acat_slug != 'vendeglatohely'
AND ac_images != 'b:0;'
AND accomodation.ac_id IS IN (
SELECT accomodation.ac_id FROM accomodation ORDER BY Rand() LIMIT 1
)
(ええ、私はここに十分な肉がないためにうんざりしますが、あなたは一日ビーガンになれませんか?)
ケース:ギャップのない連続AUTO_INCREMENT、1行が返されました
ケース:隙間のない連続したAUTO_INCREMENT、10行
ケース:ギャップのあるAUTO_INCREMENT、1行が返されました
ケース:ランダム化のための追加のFLOAT列
ケース:UUIDまたはMD5列
これらの5つのケースは、大きなテーブルに対して非常に効率的にできます。詳細については my blog を参照してください。
ダミーの例の解決策は次のとおりです。
_SELECT accomodation.ac_id,
accomodation.ac_status,
accomodation.ac_name,
accomodation.ac_status,
accomodation.ac_images
FROM accomodation,
JOIN
accomodation_category
ON accomodation.ac_category = accomodation_category.acat_id
JOIN
(
SELECT CEIL(Rand()*(SELECT MAX(ac_id) FROM accomodation)) AS ac_id
) AS Choices
USING (ac_id)
WHERE accomodation.ac_id >= Choices.ac_id
AND accomodation.ac_status != 'draft'
AND accomodation_category.acat_slug != 'vendeglatohely'
AND ac_images != 'b:0;'
LIMIT 1
_
ORDER BY Rand()
の代替についての詳細を読むには、 この記事 を読む必要があります。
私は自分のプロジェクトで多くの既存のクエリを最適化しています。 Quassnoiのソリューションは、クエリの高速化に大いに役立ちました!ただし、特に複数の大きなテーブルで多くのサブクエリを含む複雑なクエリの場合、上記のソリューションをすべてのクエリに組み込むことは困難です。
だから私はあまり最適化されていないソリューションを使用しています。基本的には、Quassnoiのソリューションと同じように機能します。
SELECT accomodation.ac_id,
accomodation.ac_status,
accomodation.ac_name,
accomodation.ac_status,
accomodation.ac_images
FROM accomodation, accomodation_category
WHERE accomodation.ac_status != 'draft'
AND accomodation.ac_category = accomodation_category.acat_id
AND accomodation_category.acat_slug != 'vendeglatohely'
AND ac_images != 'b:0;'
AND Rand() <= $size * $factor / [accomodation_table_row_count]
LIMIT $size
$size * $factor / [accomodation_table_row_count]
は、ランダムな行を選択する確率を計算します。 Rand()は乱数を生成します。 Rand()が確率以下の場合、行が選択されます。これにより、テーブルサイズを制限するためにランダム選択が効果的に実行されます。定義された制限カウントよりも少ない値を返す可能性があるため、十分な行を選択していることを確認する確率を高める必要があります。したがって、$ sizeに$ factorを乗算します(通常は$ factor = 2に設定しますが、ほとんどの場合に機能します)。最後に、limit $size
現在、問題はaccomodation_table_row_countを解決しています。テーブルのサイズがわかっている場合は、テーブルのサイズをハードコーディングできます。これは最速で実行されますが、明らかにこれは理想的ではありません。 Myisamを使用している場合、テーブルカウントの取得は非常に効率的です。私はinnodbを使用しているため、単純なcount + selectionを実行しています。あなたの場合、次のようになります。
SELECT accomodation.ac_id,
accomodation.ac_status,
accomodation.ac_name,
accomodation.ac_status,
accomodation.ac_images
FROM accomodation, accomodation_category
WHERE accomodation.ac_status != 'draft'
AND accomodation.ac_category = accomodation_category.acat_id
AND accomodation_category.acat_slug != 'vendeglatohely'
AND ac_images != 'b:0;'
AND Rand() <= $size * $factor / (select (SELECT count(*) FROM `accomodation`) * (SELECT count(*) FROM `accomodation_category`))
LIMIT $size
トリッキーな部分は、正しい確率を計算することです。ご覧のとおり、次のコードは実際には大まかな一時テーブルのサイズのみを計算します(実際、あまりにも大雑把です!):(select (SELECT count(*) FROM accomodation) * (SELECT count(*) FROM accomodation_category))
ただし、このロジックを改良して、テーブルサイズの近似値を近づけることができます。 行をアンダーセレクトするよりもオーバーセレクトする方が良いことに注意してください。つまり、確率の設定が低すぎると、十分な行を選択できないリスクがあります
このソリューションは、テーブルサイズを再計算する必要があるため、Quassnoiのソリューションよりも実行が遅くなります。ただし、このコーディングははるかに管理しやすいと思います。これは、精度+パフォーマンスとコーディングの複雑さの間のトレードオフです。ただし、大きなテーブルでは、これはOrder by Rand()よりもはるかに高速です。
注:クエリロジックが許可する場合は、結合操作の前にできるだけ早くランダム選択を実行します。