web-dev-qa-db-ja.com

データの送信時にクエリの状態がハングする

何らかの理由で、クエリの状態は(show processlist;を使用して) "Sending Data"でハングします。その理由はわかりません。これはテーブル作成スクリプトです:

CREATE TABLE IF NOT EXISTS `esp_game` (
  `gameID` int(10) unsigned NOT NULL,
  `gameType` tinyint(3) unsigned NOT NULL,
  `mapID` tinyint(3) unsigned NOT NULL,
  `createDate` int(11) NOT NULL,
  PRIMARY KEY (`gameID`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

CREATE TABLE IF NOT EXISTS `esp_gameplayer` (
  `gameID` int(11) unsigned NOT NULL,
  `summonerID` int(11) unsigned NOT NULL,
  `championID` int(11) NOT NULL,
  `teamID` int(11) NOT NULL,
  `isUpdated` tinyint(1) NOT NULL DEFAULT '0',
  PRIMARY KEY (`gameID`,`summonerID`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

CREATE TABLE IF NOT EXISTS `gameTypeMap` (
  `gameTypeID` tinyint(3) unsigned NOT NULL AUTO_INCREMENT,
  `gameTypeName` varchar(50) NOT NULL DEFAULT '',
  `gameTypePortugueseDesc` varchar(50) NOT NULL DEFAULT '',
  PRIMARY KEY (`gameTypeID`),
  UNIQUE KEY `gameTypeName` (`gameTypeName`)
) ENGINE=InnoDB  DEFAULT CHARSET=latin1 AUTO_INCREMENT=13 ;

これは行き詰まるクエリです:

SELECT g.gameID, g.gameType, g.mapID, g.createDate, gt.gameTypePortugueseDesc FROM esp_game g INNER JOIN esp_gameplayer gp ON g.gameID = gp.gameID and gp.summonerID=401129 INNER JOIN gameTypeMap gt ON g.gameType = gt.gameTypeID ORDER BY g.createDate DESC LIMIT 10;

追加情報:

esp_gameには約10Mエントリ(約1ギガ)、esp_gameplayerには約100M(約7ギガ)のエントリ、およびgameTypeMap 10がありました。

削除する前に説明してください:

EXPLAIN SELECT g.gameID, g.gameType, g.mapID, g.createDate, gt.gameTypePortugueseDesc FROM esp_game g INNER JOIN esp_gameplayer gp ON g.gameID = gp.gameID and gp.summonerID=401129 INNER JOIN gameTypeMap gt ON g.gameType = gt.gameTypeID ORDER BY g.createDate DESC LIMIT 10;
+----+-------------+-------+--------+---------------+---------+---------+----------------------+---------+----------------+
| id | select_type | table | type   | possible_keys | key     | key_len | ref                  | rows    | Extra          |
+----+-------------+-------+--------+---------------+---------+---------+----------------------+---------+----------------+
|  1 | SIMPLE      | g     | ALL    | PRIMARY       | NULL    | NULL    | NULL                 | 7706280 | Using filesort |
|  1 | SIMPLE      | gt    | eq_ref | PRIMARY       | PRIMARY | 1       | teemo.g.gameType     |       1 |                |
|  1 | SIMPLE      | gp    | eq_ref | PRIMARY       | PRIMARY | 8       | teemo.g.gameID,const |       1 | Using index    |
+----+-------------+-------+--------+---------------+---------+---------+----------------------+---------+----------------+
3 rows in set (0.00 sec)

サーバー構成:

[mysqld]
(...)
innodb_buffer_pool_instances = 4
innodb_buffer_pool_size = 1536M
innodb_lock_wait_timeout = 25

私がそれをうまく機能させる唯一の方法は、それらのテーブルからすべてを切り捨てることです-ありがたいことに、それを行うことができますが、すぐにすべてを切り捨てることはできなくなります!この問題を解決する方法はありますか?

2
Honux

まず、もう一度EXPLAINプランを確認します。

最初の行は、クエリオプティマイザーが次のことを行うことを示しています。

  • キーを使用せずに少なくとも7,706,280行のフルテーブルスキャン(したがって、Sending dataクエリ状態の根本的な原因)
  • esp_gameからパススルーされる各行は、esp_gameplayerおよびgameTypeMapへのルックアップを実行します

次のことを試してみてください。

クエリをリファクタリングする

元のクエリは次のとおりです。

SELECT
    g.gameID, g.gameType, g.mapID,
    g.createDate, gt.gameTypePortugueseDesc
FROM
    esp_game g
    INNER JOIN esp_gameplayer gp ON g.gameID = gp.gameID and gp.summonerID=401129
    INNER JOIN gameTypeMap gt ON g.gameType = gt.gameTypeID
ORDER BY g.createDate DESC LIMIT 10;

SummonerIDが取得するgameIDを制御する場合、クエリはesp_gameplayerをクエリのリードテーブルにする必要があります

SELECT
    g.gameID, g.gameType, g.mapID,
    g.createDate, gt.gameTypePortugueseDesc
FROM
    (SELECT gameID FROM esp_gameplayer WHERE summonerID=401129) gp
    INNER JOIN esp_game g ON g.gameID = gp.gameID
    INNER JOIN gameTypeMap gt ON g.gameType = gt.gameTypeID
ORDER BY g.createDate DESC LIMIT 10;

Esp_gameplayer PRIMARY KEYを変更する

CREATE TABLE IF NOT EXISTS `esp_gameplayer` (
  `gameID` int(11) unsigned NOT NULL,
  `summonerID` int(11) unsigned NOT NULL,
  `championID` int(11) NOT NULL,
  `teamID` int(11) NOT NULL,
  `isUpdated` tinyint(1) NOT NULL DEFAULT '0',
  PRIMARY KEY (`summonerID`,`gameID`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

PRIMARY KEYを逆にするには、次のようにします。

CREATE TABLE IF NOT EXISTS `esp_gameplayer_new` (
  `gameID` int(11) unsigned NOT NULL,
  `summonerID` int(11) unsigned NOT NULL,
  `championID` int(11) NOT NULL,
  `teamID` int(11) NOT NULL,
  `isUpdated` tinyint(1) NOT NULL DEFAULT '0',
  PRIMARY KEY (`summonerID`,`gameID`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
INSERT INTO esp_gameplayer_new SELECT * FROM esp_gameplayer;
ALTER TABLE esp_gameplayer RENAME esp_gameplayer_old;
ALTER TABLE esp_gameplayer_new RENAME esp_gameplayer;

特定のsummonerIDを要求したため、PRIMARY KEYを逆にすると、対応するすべてのgameIDがsummoner 401129でより速く収集されます。

変更を加え、EXPLAINプランを再実行します。それは大幅に改善するはずです。

試してみる !!!

3
RolandoMySQLDBA

以前は正常に機能し、基礎となるファイルシステムに損傷が見つかったクエリについても、同様の問題がありました。この問題が発生している場合は、問題がdbエンジン自体の外部にないことを確認するのが賢明です。

0
Martyn Smith

_SENDING DATA_は、実行時間の長いクエリを示しているため、非常に良くありません。

EXPLAINを使用して、データベースエンジンの機能と適切なインデックスを使用しない理由を確認してください。

存在しない場合は、クエリをモデル化したものを作成します。再度EXPLAINを使用して、エンジンがこのインデックスを使用しているかどうかを確認します。そうでない場合は、USE INDEX(myindex)またはFORCE INDEX (myindex)のようなヒントを使用できます。結果は素晴らしいはずです。

そうでなければ、あなたのインデックスはあなたが思うほど良くないかもしれません。プロセス全体を繰り返します。

ただし、このインデックスが適切でなくなる可能性がある場合にクエリを変更すると、繰り返します。

0
kklepper