私はDrupalインストールで大規模なユーザーデータベース(約20万行)にアクセスし、 "People Finder"機能はそれらのすべての行に(ランダムな順序で)アクセスする必要があります。)ないようです。 DrupalのUI内からLIMIT
とOFFSET
を使用できる(そして、Drupal.SEで Slow query with large dataset with Drupalビュー— SQLまたはPHPで処理する方が良いですか? 、この問題とこの問題の一部に対処します)しかしmySQL固有の質問次のとおりです:
別のテーブルのデータに基づいて一部の行を除外する必要があります(「ロールB、C、またはDに属していない、ロールAのすべてのユーザーを含める)。クエリDrupal is generated is
SELECT
users.uid AS uid,
/* some columns */,
Rand() AS random_field
FROM
users users
INNER JOIN users_roles users_roles ON users.uid = users_roles.uid
LEFT JOIN users_roles users_roles2
ON users.uid = users_roles2.uid
AND (users_roles2.rid = :views_join_condition_0
OR users_roles2.rid = :views_join_condition_1
OR users_roles2.rid = :views_join_condition_2)
WHERE
(( (users.status <> :db_condition_placeholder_3) -- Active users only
AND (users_roles.rid = :db_condition_placeholder_4) -- Must be in rôle A
AND (users_roles2.rid IS NULL) -- Must not be in rôles B, C, D
AND (users.uid != :users_uid OR users.uid IS NULL) )) -- Must not be current user
ORDER BY random_field ASC
(users.uid IS NULL
への参照は赤字です。決してそうであってはならず、このクエリに密接な関係はありません。)
Drupalのフィルター基準(DrupalのUIの制約内で)を手動でローリングすると役立つかもしれません。そして、ほとんどすべての:db_condition_placeholder
sをハードコードできます—パフォーマンスに大きな違いがあるかどうかわかりません次の2つのオプション:
FROM
句を
FROM users INNER JOIN users_roles
ON users.uid = users_roles.uid AND users_roles.rid NOT IN (6, 8, 9)
(そして、以前と同様にWHERE users_roles.rid = 5
を実行し、users_roles2
参照を削除するだけです);または
JOIN
を完全に削除し、WHERE
句を次のように変更します。
WHERE users.status = 1 -- Active users only
AND users.uid IN
(SELECT DISTINCT uid FROM users_roles WHERE rid = 5) -- Must be in rôle A
AND users.uid NOT IN
(SELECT DISTINCT uid FROM users_roles WHERE rid IN (6,8,9)) -- Not rôles B, C, D
AND users.uid != :users_uid -- Not current user
それが役立つ場合、mySQLのバージョン番号は5.1.50-enterprise-gpl-pro
であり、すべてのテーブルはInnoDBストレージエンジンを使用し、テーブルusers_roles
にはすでに2つの列にクラスター化された主キーがあります。
mysql> describe users_roles;
+-------+------------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+------------------+------+-----+---------+-------+
| uid | int(10) unsigned | NO | PRI | 0 | |
| rid | int(10) unsigned | NO | PRI | 0 | |
+-------+------------------+------+-----+---------+-------+
2 rows in set (0.00 sec)
私が目にしている実際のパフォーマンスの問題は2つあります。サーバーはRAMで限界に達しており、ここで説明するクエリの実行には2秒以上かかります。私はできないと思いますRAMをLIMIT
とOFFSET
を参照せずに処理しますが、このクエリの高速化は間違いなく良いスタートです。
mysql> describe users;
+------------------+------------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+------------------+------------------+------+-----+---------+-------+
| uid | int(10) unsigned | NO | PRI | 0 | |
| name | varchar(60) | NO | UNI | | |
| pass | varchar(128) | NO | | | |
| mail | varchar(254) | YES | MUL | | |
| theme | varchar(255) | NO | | | |
| signature | varchar(255) | NO | | | |
| signature_format | varchar(255) | YES | | NULL | |
| created | int(11) | NO | MUL | 0 | |
| access | int(11) | NO | MUL | 0 | |
| login | int(11) | NO | | 0 | |
| status | tinyint(4) | NO | | 0 | |
| timezone | varchar(32) | YES | | NULL | |
| language | varchar(12) | NO | | | |
| picture | int(11) | NO | | 0 | |
| init | varchar(254) | YES | | | |
| data | longblob | YES | | NULL | |
+------------------+------------------+------+-----+---------+-------+
16 rows in set (0.00 sec)
mysql> EXPLAIN EXTENDED SELECT
users.uid AS uid,
/* some columns */,
Rand() AS random_field
FROM
users users
INNER JOIN users_roles users_roles ON users.uid = users_roles.uid
LEFT JOIN users_roles users_roles2
ON users.uid = users_roles2.uid
AND (users_roles2.rid = 6 OR users_roles2.rid = 8 OR users_roles2.rid = 9)
WHERE
(( (users.status <> 0) -- Active users only
AND (users_roles.rid = 5) -- Must be in rôle A
AND (users_roles2.rid IS NULL) -- Not in rôles B, C, D
AND (users.uid != 35635 OR users.uid IS NULL) )) -- Not (random valid UID)
ORDER BY random_field ASC
+----+-------------+--------------+--------+---------------+---------+---------+------------------------+-------+-----------------------------------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+--------------+--------+---------------+---------+---------+------------------------+-------+-----------------------------------------------------------+
| 1 | SIMPLE | users_roles | ref | PRIMARY,rid | rid | 4 | const | 69985 | Using where; Using index; Using temporary; Using filesort |
| 1 | SIMPLE | users | eq_ref | PRIMARY | PRIMARY | 4 | dbname.users_roles.uid | 1 | Using where |
| 1 | SIMPLE | users_roles2 | ref | PRIMARY,rid | PRIMARY | 4 | dbname.users.uid | 1 | Using where; Using index; Not exists |
+----+-------------+--------------+--------+---------------+---------+---------+-----------------------+-------+-----------------------------------------------------------+
3 rows in set, 1 warning (0.01 sec)
users_roles (rid, uid)
にインデックスを追加します。 2つの列_(a,b)
_を持つ多対多のテーブルでは、ほとんどの場合、両方のインデックスが必要になります:どちらかのクエリで_(a,b)
_と_(b,a)
_。このインデックスはこのクエリに役立つと思います。
クエリとそれらが生成する_EXPLAIN EXTENDED
_をさまざまに書き換えてみてください。
あなたの提案について、最初は正しくありません(同じ結果は表示されません)。 2番目の提案:
_WHERE users.status = 1 -- Active users only
_
はい、それは_users.status <> 0
_よりも優れています。 users (status)
にインデックスがある場合(およびアクティブなユーザーが少ない場合はさらに)、この変更はより良い効果をもたらす可能性があります。ブール列(またはブールとして機能する列)を使用したクエリの最適化は、Bツリーでは簡単ではありません。
_ AND users.uid IN
(SELECT DISTINCT uid FROM users_roles WHERE rid = 5) -- Must be in rôle A
_
いいえ。MySQLはcolumn IN (SELECT ...)
に問題があることがわかっています。特に、外部テーブルが大きい場合(そして、200Kカラムなので、ダメ、良くありません)。
_ AND users.uid NOT IN
(SELECT DISTINCT uid FROM users_roles WHERE rid IN (6,8,9)) -- Not rôles B, C, D
_
はい、それは書き換えの1つの方法です。ただし、DISTINCT
は冗長です。
_ AND users.uid <> :users_uid -- Not current user
_
はい、_users.uid IS NOT NULL
_を削除しても問題は解決し、結果は変わりません。
_rid = 5
_条件をON
句に移動します。
_INNER JOIN users_roles users_roles
ON users.uid = users_roles.uid
AND users_roles.rid = 5
_
_NOT IN
_への(書き換え)は、_NOT EXISTS
_でも記述できます。
_ AND NOT EXISTS
( SELECT *
FROM users_roles ur
WHERE ur.uid = users.uid
AND ur.rid IN (6,8,9)
)
_