私は何時間もグーグルで独学で解決策を探していましたが、運がありませんでした。私はここでいくつかの同様の質問を見つけましたが、このケースではありません。
私のテーブル:
状況:一部の場所(_person_id
_)からすべての個人ID(_location.attribute_value BETWEEN 3000 AND 7000
_)を選択し、性別(_gender.attribute_value = 1
_)、数年で生まれ(_bornyear.attribute_value BETWEEN 1980 AND 2000
_)、いくつかの目の色(eyecolor.attribute_value IN (2,3)
)。
これは私の魔女がかかったクエリです3〜4分と私は最適化したいと思います:
_SELECT person_id
FROM person
LEFT JOIN attribute location ON location.attribute_type_id = 1 AND location.person_id = person.person_id
LEFT JOIN attribute gender ON gender.attribute_type_id = 2 AND gender.person_id = person.person_id
LEFT JOIN attribute bornyear ON bornyear.attribute_type_id = 3 AND bornyear.person_id = person.person_id
LEFT JOIN attribute eyecolor ON eyecolor.attribute_type_id = 4 AND eyecolor.person_id = person.person_id
WHERE 1
AND location.attribute_value BETWEEN 3000 AND 7000
AND gender.attribute_value = 1
AND bornyear.attribute_value BETWEEN 1980 AND 2000
AND eyecolor.attribute_value IN (2,3)
LIMIT 100000;
_
結果:
_+-----------+
| person_id |
+-----------+
| 233 |
| 605 |
| ... |
| 8702599 |
| 8703617 |
+-----------+
100000 rows in set (3 min 42.77 sec)
_
拡張の説明:
_+----+-------------+----------+--------+---------------------------------------------+-----------------+---------+--------------------------+---------+----------+--------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+----------+--------+---------------------------------------------+-----------------+---------+--------------------------+---------+----------+--------------------------+
| 1 | SIMPLE | bornyear | range | attribute_type_id,attribute_value,person_id | attribute_value | 5 | NULL | 1265229 | 100.00 | Using where |
| 1 | SIMPLE | location | ref | attribute_type_id,attribute_value,person_id | person_id | 5 | test1.bornyear.person_id | 4 | 100.00 | Using where |
| 1 | SIMPLE | eyecolor | ref | attribute_type_id,attribute_value,person_id | person_id | 5 | test1.bornyear.person_id | 4 | 100.00 | Using where |
| 1 | SIMPLE | gender | ref | attribute_type_id,attribute_value,person_id | person_id | 5 | test1.eyecolor.person_id | 4 | 100.00 | Using where |
| 1 | SIMPLE | person | eq_ref | PRIMARY | PRIMARY | 4 | test1.location.person_id | 1 | 100.00 | Using where; Using index |
+----+-------------+----------+--------+---------------------------------------------+-----------------+---------+--------------------------+---------+----------+--------------------------+
5 rows in set, 1 warning (0.02 sec)
_
プロファイリング:
_+------------------------------+-----------+
| Status | Duration |
+------------------------------+-----------+
| Sending data | 3.069452 |
| Waiting for query cache lock | 0.000017 |
| Sending data | 2.968915 |
| Waiting for query cache lock | 0.000019 |
| Sending data | 3.042468 |
| Waiting for query cache lock | 0.000043 |
| Sending data | 3.264984 |
| Waiting for query cache lock | 0.000017 |
| Sending data | 2.823919 |
| Waiting for query cache lock | 0.000038 |
| Sending data | 2.863903 |
| Waiting for query cache lock | 0.000014 |
| Sending data | 2.971079 |
| Waiting for query cache lock | 0.000020 |
| Sending data | 3.053197 |
| Waiting for query cache lock | 0.000087 |
| Sending data | 3.099053 |
| Waiting for query cache lock | 0.000035 |
| Sending data | 3.064186 |
| Waiting for query cache lock | 0.000017 |
| Sending data | 2.939404 |
| Waiting for query cache lock | 0.000018 |
| Sending data | 3.440288 |
| Waiting for query cache lock | 0.000086 |
| Sending data | 3.115798 |
| Waiting for query cache lock | 0.000068 |
| Sending data | 3.075427 |
| Waiting for query cache lock | 0.000072 |
| Sending data | 3.658319 |
| Waiting for query cache lock | 0.000061 |
| Sending data | 3.335427 |
| Waiting for query cache lock | 0.000049 |
| Sending data | 3.319430 |
| Waiting for query cache lock | 0.000061 |
| Sending data | 3.496563 |
| Waiting for query cache lock | 0.000029 |
| Sending data | 3.017041 |
| Waiting for query cache lock | 0.000032 |
| Sending data | 3.132841 |
| Waiting for query cache lock | 0.000050 |
| Sending data | 2.901310 |
| Waiting for query cache lock | 0.000016 |
| Sending data | 3.107269 |
| Waiting for query cache lock | 0.000062 |
| Sending data | 2.937373 |
| Waiting for query cache lock | 0.000016 |
| Sending data | 3.097082 |
| Waiting for query cache lock | 0.000261 |
| Sending data | 3.026108 |
| Waiting for query cache lock | 0.000026 |
| Sending data | 3.089760 |
| Waiting for query cache lock | 0.000041 |
| Sending data | 3.012763 |
| Waiting for query cache lock | 0.000021 |
| Sending data | 3.069694 |
| Waiting for query cache lock | 0.000046 |
| Sending data | 3.591908 |
| Waiting for query cache lock | 0.000060 |
| Sending data | 3.526693 |
| Waiting for query cache lock | 0.000076 |
| Sending data | 3.772659 |
| Waiting for query cache lock | 0.000069 |
| Sending data | 3.346089 |
| Waiting for query cache lock | 0.000245 |
| Sending data | 3.300460 |
| Waiting for query cache lock | 0.000019 |
| Sending data | 3.135361 |
| Waiting for query cache lock | 0.000021 |
| Sending data | 2.909447 |
| Waiting for query cache lock | 0.000039 |
| Sending data | 3.337561 |
| Waiting for query cache lock | 0.000140 |
| Sending data | 3.138180 |
| Waiting for query cache lock | 0.000090 |
| Sending data | 3.060687 |
| Waiting for query cache lock | 0.000085 |
| Sending data | 2.938677 |
| Waiting for query cache lock | 0.000041 |
| Sending data | 2.977974 |
| Waiting for query cache lock | 0.000872 |
| Sending data | 2.918640 |
| Waiting for query cache lock | 0.000036 |
| Sending data | 2.975842 |
| Waiting for query cache lock | 0.000051 |
| Sending data | 2.918988 |
| Waiting for query cache lock | 0.000021 |
| Sending data | 2.943810 |
| Waiting for query cache lock | 0.000061 |
| Sending data | 3.330211 |
| Waiting for query cache lock | 0.000025 |
| Sending data | 3.411236 |
| Waiting for query cache lock | 0.000023 |
| Sending data | 23.339035 |
| end | 0.000807 |
| query end | 0.000023 |
| closing tables | 0.000325 |
| freeing items | 0.001217 |
| logging slow query | 0.000007 |
| logging slow query | 0.000011 |
| cleaning up | 0.000104 |
+------------------------------+-----------+
100 rows in set (0.00 sec)
_
テーブルの構造:
_CREATE TABLE `attribute` (
`attribute_id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`attribute_type_id` int(11) unsigned DEFAULT NULL,
`attribute_value` int(6) DEFAULT NULL,
`person_id` int(11) unsigned DEFAULT NULL,
PRIMARY KEY (`attribute_id`),
KEY `attribute_type_id` (`attribute_type_id`),
KEY `attribute_value` (`attribute_value`),
KEY `person_id` (`person_id`)
) ENGINE=MyISAM AUTO_INCREMENT=40000001 DEFAULT CHARSET=utf8;
CREATE TABLE `person` (
`person_id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`person_name` text CHARACTER SET latin1,
PRIMARY KEY (`person_id`)
) ENGINE=MyISAM AUTO_INCREMENT=20000001 DEFAULT CHARSET=utf8;
_
SSDと1GBのRAMを備えたDigitalOcean仮想サーバーでクエリが実行されました。
データベース設計に問題があると思います。この状況をよりよく設計するための提案はありますか?または、上記の選択を調整するだけですか?
十分な解決策が見つかれば幸いです。 この記事 から着想を得ています。
ft_min_Word_len=1
_セクションで_[mysqld]
_(MyISAMの場合)および_innodb_ft_min_token_size=1
_ファイルで_my.cnf
_(InnoDbの場合)を設定することが重要です。mysqlサービスを再起動します。SELECT * FROM person_index WHERE MATCH(attribute_1) AGAINST("123 456 789" IN BOOLEAN MODE) LIMIT 1000
where _123
_、_456
_ a _789
_は、個人が_attribute_1
_で関連付けるべきIDです。このクエリの所要時間は1秒未満でした。ステップ1。全文索引を持つテーブルを作成します。 InnoDbはMySQL 5.7のフルテキストインデックスをサポートしているため、5.5または5.6を使用する場合はMyISAMを使用する必要があります。 InnoDbよりもFT検索の方が高速な場合があります。
_CREATE TABLE `person_attribute_ft` (
`person_id` int(11) NOT NULL,
`attr_1` text,
`attr_2` text,
`attr_3` text,
`attr_4` text,
PRIMARY KEY (`person_id`),
FULLTEXT KEY `attr_1` (`attr_1`),
FULLTEXT KEY `attr_2` (`attr_2`),
FULLTEXT KEY `attr_3` (`attr_3`),
FULLTEXT KEY `attr_4` (`attr_4`),
FULLTEXT KEY `attr_12` (`attr_1`,`attr_2`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8
_
ステップ2。EAV(entity-attribute-value)テーブルからデータを挿入します。たとえば、問題のある例は、1つの単純なSQLで実行できます。
_INSERT IGNORE INTO `person_attribute_ft`
SELECT
p.person_id,
(SELECT GROUP_CONCAT(a.attribute_value SEPARATOR ' ') FROM attribute a WHERE a.attribute_type_id = 1 AND a.person_id = p.person_id LIMIT 10) attr_1,
(SELECT GROUP_CONCAT(a.attribute_value SEPARATOR ' ') FROM attribute a WHERE a.attribute_type_id = 2 AND a.person_id = p.person_id LIMIT 10) attr_2,
(SELECT GROUP_CONCAT(a.attribute_value SEPARATOR ' ') FROM attribute a WHERE a.attribute_type_id = 3 AND a.person_id = p.person_id LIMIT 10) attr_3,
(SELECT GROUP_CONCAT(a.attribute_value SEPARATOR ' ') FROM attribute a WHERE a.attribute_type_id = 4 AND a.person_id = p.person_id LIMIT 10) attr_4
FROM person p
_
結果は次のようになります。
_mysql> select * from person_attribute_ft limit 10;
+-----------+--------+--------+--------+--------+
| person_id | attr_1 | attr_2 | attr_3 | attr_4 |
+-----------+--------+--------+--------+--------+
| 1 | 541 | 2 | 1927 | 3 |
| 2 | 2862 | 2 | 1939 | 4 |
| 3 | 6573 | 2 | 1904 | 2 |
| 4 | 2432 | 1 | 2005 | 2 |
| 5 | 2208 | 1 | 1995 | 4 |
| 6 | 8388 | 2 | 1973 | 1 |
| 7 | 107 | 2 | 1909 | 4 |
| 8 | 5161 | 1 | 2005 | 1 |
| 9 | 8022 | 2 | 1953 | 4 |
| 10 | 4801 | 2 | 1900 | 3 |
+-----------+--------+--------+--------+--------+
10 rows in set (0.00 sec)
_
ステップ3。次のようなクエリでテーブルから選択します。
_mysql> SELECT SQL_NO_CACHE *
-> FROM `person_attribute_ft`
-> WHERE 1 AND MATCH(attr_1) AGAINST ("3000 3001 3002 3003 3004 3005 3006 3007" IN BOOLEAN MODE)
-> AND MATCH(attr_2) AGAINST ("1" IN BOOLEAN MODE)
-> AND MATCH(attr_3) AGAINST ("1980 1981 1982 1983 1984" IN BOOLEAN MODE)
-> AND MATCH(attr_4) AGAINST ("2,3" IN BOOLEAN MODE)
-> LIMIT 10000;
+-----------+--------+--------+--------+--------+
| person_id | attr_1 | attr_2 | attr_3 | attr_4 |
+-----------+--------+--------+--------+--------+
| 12131 | 3002 | 1 | 1982 | 2 |
| 51315 | 3007 | 1 | 1984 | 2 |
| 147283 | 3001 | 1 | 1984 | 2 |
| 350086 | 3005 | 1 | 1982 | 3 |
| 423907 | 3004 | 1 | 1982 | 3 |
... many rows ...
| 9423907 | 3004 | 1 | 1982 | 3 |
| 9461892 | 3007 | 1 | 1982 | 2 |
| 9516361 | 3006 | 1 | 1980 | 2 |
| 9813933 | 3005 | 1 | 1982 | 2 |
| 9986892 | 3003 | 1 | 1981 | 2 |
+-----------+--------+--------+--------+--------+
90 rows in set (0.17 sec)
_
クエリはすべての行を選択します。
attr_1
_のこれらのIDの少なくとも1つに一致:_3000, 3001, 3002, 3003, 3004, 3005, 3006 or 3007
_1
_の_attr_2
_に一致する(この列はgenderを表すため、このソリューションがカスタマイズされている場合、smallint(1)
単純なインデックスなどで...)1980, 1981, 1982, 1983 or 1984
_の_attr_3
_の少なくとも1つに一致2
_または_3
_の_attr_4
_に一致結論:
このソリューションは完璧ではなく、多くの状況で理想的ではありませんが、EAVテーブル設計の優れた代替策として使用できます。
誰かのお役に立てば幸いです。
person
に含めるfew属性を選択します。いくつかの組み合わせでインデックスを作成します。単一列のインデックスではなく、複合インデックスを使用します。
これは、基本的に、EAVのパフォーマンス低下を回避する唯一の方法です。
詳細については、以下をご覧ください。 http://mysql.rjweb.org/doc.php/eav Key-Valueテーブルの代わりにJSONを使用することを提案します。
データベースの設計に問題があると思います。
いわゆるEntity-Attribute-Value設計を使用していますが、これは多くの場合、設計上、パフォーマンスが低下します。
この状況をより良く設計するための提案はありますか?
これを設計する古典的なリレーショナルな方法は、属性ごとに個別のテーブルを作成することです。一般的には、location
、gender
、bornyear
、eyecolor
という個別のテーブルを作成できます。
以下は、特定の属性が常に個人に対して定義されているかどうかによって異なります。そして、人が属性の値を1つだけ持つことができるかどうか。たとえば、通常、その人の性別は1つだけです。現在のデザインでは、性別の値が異なる同じ人物に3つの行を追加することを妨げるものはありません。性別の値を1や2ではなく、987のように意味のない数値に設定することもできます。データベースには、それを妨げる制約がありません。しかし、これはEAV設計でデータの整合性を維持するための別の問題です。
常にその人の性別を知っている場合は、別のテーブルに入れることはほとんど意味がありません。GenderID
テーブルにnull以外の列person
を含める方が良いでしょう。すべての可能な性別とその名前のリストを含むルックアップテーブルへの外部キーである。常にではないがほとんどの場合にその人の性別を知っている場合は、この列をNULL可能にして、情報が利用できないときにNULL
に設定できます。ほとんどの場合、その人の性別がわからない場合は、gender
1:1にリンクし、既知の人のみが行を含む個別のテーブルperson
を用意することをお勧めします性別。
同様の考慮事項がeyecolor
とbornyear
にも当てはまります-個人がeyecolor
またはbornyear
の2つの値を持つことはほとんどありません。
人が属性に対して複数の値を持つことが可能である場合、それを確実に別のテーブルに配置します。たとえば、人が複数の住所(自宅、職場、郵便、休日など)を持っていることは珍しいことではないので、それらをすべてlocation
のテーブルにリストします。テーブルperson
およびlocation
は1:Mでリンクされます。
または、上記の選択を調整するだけですか?
EAV設計を使用している場合は、少なくとも次のことを行います。
attribute_type_id
、attribute_value
、person_id
〜NOT NULL
。attribute.person_id
とperson.person_id
。(attribute_type_id, attribute_value, person_id)
。ここでは列の順序が重要です。このようなクエリを作成します。 INNER
結合の代わりにLEFT
を使用し、オプティマイザにインデックスを使用するすべての機会を与えるために、各属性のサブクエリを明示的に記述します。
SELECT person.person_id
FROM
person
INNER JOIN
(
SELECT attribute.person_id
FROM attribute
WHERE attribute_type_id = 1
AND location.attribute_value BETWEEN 3000 AND 7000
) AS location ON location.person_id = person.person_id
INNER JOIN
(
SELECT attribute.person_id
FROM attribute
WHERE attribute_type_id = 2
AND location.attribute_value = 1
) AS gender ON gender.person_id = person.person_id
INNER JOIN
(
SELECT attribute.person_id
FROM attribute
WHERE attribute_type_id = 3
AND location.attribute_value BETWEEN 1980 AND 2000
) AS bornyear ON bornyear.person_id = person.person_id
INNER JOIN
(
SELECT attribute.person_id
FROM attribute
WHERE attribute_type_id = 4
AND location.attribute_value IN (2, 3)
) AS eyecolor ON eyecolor.person_id = person.person_id
LIMIT 100000;
また、価値があるかもしれません パーティショニングattribute
テーブルattribute_type_id
。
attribute
にインデックスを追加:
(person_id, attribute_type_id, attribute_value)
および(attribute_type_id, attribute_value, person_id)
説明
現在の設計では、EXPLAIN
はクエリがattribute
の1,265,229 * 4 * 4 * 4 = 80,974,656
行を検査することを期待しています。この数を減らすには、(person_id, attribute_type_id)
のattribute
に 複合インデックス を追加します。このインデックスを使用すると、クエリは、location
、eyecolor
、gender
のそれぞれについて、4行ではなく1行のみを調べます。
そのインデックスを拡張して、attribute_type_value
も含めることができます:(person_id, attribute_type_id, attribute_value)
。これにより、このクエリのこのインデックスが カバーするインデックス に変わり、パフォーマンスも向上するはずです。
さらに、(attribute_type_id, attribute_value, person_id)
にインデックスを追加すると(これもperson_id
を含めることでカバーするインデックスが追加されます)、さらに多くの行を調べる必要があるattribute_value
にインデックスを使用する場合よりもパフォーマンスが向上します。この場合、explainの最初のステップであるbornyear
から範囲を選択することで固定されます。
これら2つのインデックスを使用すると、私のシステムでのクエリの実行時間が〜2.0秒から〜0.2秒に短縮され、explain出力は次のようになります。
+----+-------------+----------+--------+-------------------------------------+-------------------+---------+--------------------------------+---------+----------+--------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+----------+--------+-------------------------------------+-------------------+---------+--------------------------------+---------+----------+--------------------------+
| 1 | SIMPLE | bornyear | range | person_type_value,type_value_person | type_value_person | 9 | | 1861881 | 100.00 | Using where; Using index |
| 1 | SIMPLE | location | ref | person_type_value,type_value_person | person_type_value | 8 | bornyear.person_id,const | 1 | 100.00 | Using where; Using index |
| 1 | SIMPLE | eyecolor | ref | person_type_value,type_value_person | person_type_value | 8 | bornyear.person_id,const | 1 | 100.00 | Using where; Using index |
| 1 | SIMPLE | gender | ref | person_type_value,type_value_person | person_type_value | 13 | bornyear.person_id,const,const | 1 | 100.00 | Using index |
| 1 | SIMPLE | person | eq_ref | PRIMARY | PRIMARY | 4 | bornyear.person_id | 1 | 100.00 | Using index |
+----+-------------+----------+--------+-------------------------------------+-------------------+---------+--------------------------------+---------+----------+--------------------------+
適切に見えるクエリインデックスヒントを使用してみてください