web-dev-qa-db-ja.com

innodb DEPENDENT SUBQUERYが非常に遅い

ここにいくつかのinnodbテーブルがあります。非常に頻繁に_insert,update,query_です。

tnは製品IDです

colは製品のサブIDです

tindexは製品の説明です。(ワードごとに保存)

dateは、strototime('now')によって保存された製品追加日です

_CREATE TABLE IF NOT EXISTS `mytable` (
  `tn` varchar(15) NOT NULL,
  `col` smallint(2) NOT NULL,
  `tindex` varchar(30) NOT NULL,
  `date` int(10) NOT NULL,
  KEY `date` (`date`),
  KEY `tn` (`tn`),
  KEY `tindex` (`tindex`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
_

サブIDに「防食剤」と「防水剤」という単語が含まれている一部の製品をクエリする方法

_SELECT * 
FROM mytable a
WHERE tindex =  'anticorrosive'
AND EXISTS (
SELECT 1 
FROM mytable
WHERE tn = a.tn
AND col = a.col
AND tindex =  'waterproof'
)
ORDER BY DATE
LIMIT 10
_

テーブル全体が本来_12,700,000_行、_1.1GB_、コスト__4.8356s_、explianクエリget

_id  select_type table   type    possible_keys   key key_len ref rows    Extra
1   PRIMARY a   ref tn,tindex   tindex  92  const   1173    Using where; Using filesort
2   DEPENDENT SUBQUERY  mytable ref tn,tindex   tn  47  production.a.tn 161 Using where
_

そして、これが_my.ini_です。

_[mysqld]
character_set_server=utf8
skip-external-locking
skip-networking
key_buffer = 256M
tmp_table_size = 128M
max_connections = 1024
wait_timeout=10
back_log = 2048
key_buffer_size = 256M
max_allowed_packet = 2M
table_cache = 2048
table_open_cache = 1024
sort_buffer_size = 8M
read_buffer_size = 4M
net_buffer_length = 92K
read_rnd_buffer_size = 8M
myisam_sort_buffer_size = 256M
thread_cache = 512
query_cache_size= 256M
bulk_insert_buffer_size = 192M
ft_min_Word_len=2
innodb_buffer_pool_size = 512M
innodb_flush_log_at_trx_commit = 2
_

クエリを高速化するには?インデックスをやり直しますか?クエリコード?または_my.ini_の値を変更します

PDATE 1インデックスキーを変更しようとしましたが、テーブルはまだ大きくなりましたが、今では_20mln_行_2.2GB_が必要です

_ALTER TABLE mytable ADD UNIQUE INDEX (tn,col,tindex);
ALTER TABLE mytable ADD INDEX (date);
_

クエリ1

_SELECT A.* FROM 
(SELECT * FROM mytable WHERE tindex='words1') A
INNER JOIN
(SELECT tn,col FROM mytable WHERE tindex='words2') B
USING (tn,col) order by date limit 10
_

29.6278を使用

_id  select_type table   type    possible_keys   key key_len ref rows    Extra
1   PRIMARY <derived2>  ALL NULL    NULL    NULL    NULL    15674   Using temporary; Using filesort
1   PRIMARY <derived3>  ALL NULL    NULL    NULL    NULL    1716    Using where; Using join buffer
3   DERIVED mytable index   NULL    date    5   NULL    13918039    Using where; Using index
2   DERIVED mytable index   NULL    date    5   NULL    13918039    Using where; Using index
_

クエリ2

_SELECT * FROM mytable
WHERE tindex IN ('words1','words2')
GROUP BY tn,col HAVING COUNT(1)=2 order by date limit 10;
_

23.8711を使用

_id  select_type table   type    possible_keys   key key_len ref rows    Extra
1   SIMPLE  mytable index   NULL    date    5   NULL    19359399    Using where; Using index; Using temporary
_
4
cj333

いくつかのメモ:

  • テーブルにPRIMARY KEYがないのはなぜですか?
  • dateのタイプがintであり、dateまたはdatetimeまたはtimestampではないのはなぜですか?
  • 日付と時刻を保存するときにdateと呼ばれるのはなぜですか?

実際の質問、クエリの効率、(tindex, tn, col, date)のインデックスは、他の提案よりもはるかに役立つと思います。また、主キーがなく、(tindex, tn, col)が一意である(別の一意のインデックスを追加した)ため、私の提案は次のとおりです。

  • (オプションで)その一意のインデックスを削除します)
  • 主キーを(tindex, tn, col)として定義します。

    ALTER TABLE mytable
      DROP INDEX __the_name_of_the_unique_index,  -- this is optional
      ADD PRIMARY KEY (tindex, tn, col) ;
    

これには時間がかかります(そのため、両方の操作を1つのパスで実行することをお勧めします。)

次に、クエリとクエリの推奨されるすべての書き換えを測定できます(プライマリインデックスが使用されているかどうかを確認します)。

  • なぜこれがより良いインデックスなのですか?

tindex列はインデックスの最初の列であるため、tindex='anticorrosive'のすべての行はインデックスの連続したページにあります(tindex='corrosive'のすべての行は別の部分にあります)したがって、インデックスのこれらの2つの部分を読み取る(スキャンする)は、テーブル全体をスキャンするか、インデックス全体を2回スキャンする(mysqlが(tn, col, tindex)インデックス。)

このインデックスを主キーにするもう1つの利点は、InnoDBが追加し、テーブルのクラスター化インデックスとして使用していた(非表示の)6バイトの列を削除できることです(主または一意の制約/インデックスを提供しなかったため) )そのため、テーブルの幅が狭くなりました。 (tindex, tn, col)は、これからテーブルのクラスター化インデックスになります。これは、インデックスの2つの部分がスキャンされた後、date値がクエリで使用できることも意味します。

4
ypercubeᵀᴹ

JOINを使用する

_SELECT * 
FROM mytable a
JOIN
mytable b
ON b.tn = a.tn
AND b.col = a.col
WHERE b.tindex =  'waterproof'
AND a.tindex =  'anticorrosive'
ORDER BY a.DATE
LIMIT 10
_

また、コンポジットindex(tn,col,tindex)も役立ちます。

3
Mihai

2つのアプローチがあります

アプローチ#1:DITCH CORRELATED SUBQUERY

相関サブクエリを破棄して、2つのサブクエリのJOINに置き換える必要があります

SELECT A.* FROM 
(SELECT * FROM mytable  WHERE tindex='anticorrosive') A
INNER JOIN
(SELECT tn,col FROM mytable  WHERE tindex='waterproof') B
USING (tn,col);

製品の数によっては、いずれにしてもこれは長いクエリになります。

アプローチ#2:DITCH JOINS ALTOGETHER

SELECT tn,col FROM mytable
WHERE tindex IN ('anticorrosive','waterproof')
GROUP BY tn,col HAVING COUNT(1)=2;

これはもっと似ているはずです(オーバーヘッドが少ない)

警告

このインデックスを追加してください

ALTER TABLE mytable ADD UNIQUE INDEX (tn,col,tindex);

これにより、すべての(tn、col)に一意のtindex値のみが含まれるようになります。

試してみる !!!

1
RolandoMySQLDBA

rowsカラムはmysqlがどの行(1173&47)を処理する必要があるかを正確に取得することを意味するため、あなたのクエリはすでにmysqlによって実行されていると思います。

あなたの問題はORDER BYです:

それを修正するには、sort_buffer_sizeから1024M

ORDER BY最適化

一意の別のインデックスも試してください:(tindex、date)または(date、tindex)

0
Aurel