結合されたテーブルの列によるソートを最適化する方法はありますか?


10

これは私の遅いクエリです:

SELECT `products_counts`.`cid`
FROM
  `products_counts` `products_counts`

  LEFT OUTER JOIN `products` `products` ON (
  `products_counts`.`product_id` = `products`.`id`
  )
  LEFT OUTER JOIN `trademarks` `trademark` ON (
  `products`.`trademark_id` = `trademark`.`id`
  )
  LEFT OUTER JOIN `suppliers` `supplier` ON (
  `products_counts`.`supplier_id` = `supplier`.`id`
  )
WHERE
  `products_counts`.product_id IN
  (159, 572, 1075, 1102, 1145, 1162, 1660, 2355, 2356, 2357, 3236, 6471, 6472, 6473, 8779, 9043, 9095, 9336, 9337, 9338, 9445, 10198, 10966, 10967, 10974, 11124, 11168, 16387, 16689, 16827, 17689, 17920, 17938, 17946, 17957, 21341, 21352, 21420, 21421, 21429, 21544, 27944, 27988, 30194, 30196, 30230, 30278, 30699, 31306, 31340, 32625, 34021, 34047, 38043, 43743, 48639, 48720, 52453, 55667, 56847, 57478, 58034, 61477, 62301, 65983, 66013, 66181, 66197, 66204, 66407, 66844, 66879, 67308, 68637, 73944, 74037, 74060, 77502, 90963, 101630, 101900, 101977, 101985, 101987, 105906, 108112, 123839, 126316, 135156, 135184, 138903, 142755, 143046, 143193, 143247, 144054, 150164, 150406, 154001, 154546, 157998, 159896, 161695, 163367, 170173, 172257, 172732, 173581, 174001, 175126, 181900, 182168, 182342, 182858, 182976, 183706, 183902, 183936, 184939, 185744, 287831, 362832, 363923, 7083107, 7173092, 7342593, 7342594, 7342595, 7728766)
ORDER BY
  products_counts.inflow ASC,
  supplier.delivery_period ASC,
  trademark.sort DESC,
  trademark.name ASC
LIMIT
  0, 3;

データセットの平均クエリ時間は4.5秒で、これは許容できません。

私が見る解決策:

order句のすべての列をproducts_countsテーブルに追加します。しかし、アプリケーションには10個までの注文タイプがあるため、多くの列とインデックスを作成する必要があります。さらに、products_counts更新/挿入/削除が非常に集中的に行われるため、(トリガーを使用して)すべての注文関連の列をすぐに更新する必要があります。

他の解決策はありますか?

説明:

+----+-------------+-----------------+--------+---------------------------------------------+------------------------+---------+----------------------------------+------+----------------------------------------------+
| id | select_type | table           | type   | possible_keys                               | key                    | key_len | ref                              | rows | Extra                                        |
+----+-------------+-----------------+--------+---------------------------------------------+------------------------+---------+----------------------------------+------+----------------------------------------------+
|  1 | SIMPLE      | products_counts | range  | product_id_supplier_id,product_id,pid_count | product_id_supplier_id | 4       | NULL                             |  227 | Using where; Using temporary; Using filesort |
|  1 | SIMPLE      | products        | eq_ref | PRIMARY                                     | PRIMARY                | 4       | uaot.products_counts.product_id  |    1 |                                              |
|  1 | SIMPLE      | trademark       | eq_ref | PRIMARY                                     | PRIMARY                | 4       | uaot.products.trademark_id       |    1 |                                              |
|  1 | SIMPLE      | supplier        | eq_ref | PRIMARY                                     | PRIMARY                | 4       | uaot.products_counts.supplier_id |    1 |                                              |
+----+-------------+-----------------+--------+---------------------------------------------+------------------------+---------+----------------------------------+------+----------------------------------------------+

テーブル構造:

CREATE TABLE `products_counts` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `product_id` int(11) unsigned NOT NULL,
  `supplier_id` int(11) unsigned NOT NULL,
  `count` int(11) unsigned NOT NULL,
  `cid` varchar(64) NOT NULL,
  `inflow` varchar(10) NOT NULL,
  `for_delete` tinyint(1) unsigned NOT NULL DEFAULT '0',
  PRIMARY KEY (`id`),
  UNIQUE KEY `cid` (`cid`),
  UNIQUE KEY `product_id_supplier_id` (`product_id`,`supplier_id`),
  KEY `product_id` (`product_id`),
  KEY `count` (`count`),
  KEY `pid_count` (`product_id`,`count`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE `products` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `external_id` varchar(36) NOT NULL,
  `name` varchar(255) NOT NULL,
  `category_id` int(11) unsigned NOT NULL,
  `trademark_id` int(11) unsigned NOT NULL,
  `photo` varchar(255) NOT NULL,
  `sort` int(11) unsigned NOT NULL,
  `otech` tinyint(1) unsigned NOT NULL,
  `not_liquid` tinyint(1) unsigned NOT NULL DEFAULT '0',
  `applicable` varchar(255) NOT NULL,
  `code_main` varchar(64) NOT NULL,
  `code_searchable` varchar(128) NOT NULL,
  `total` int(11) unsigned NOT NULL,
  `slider` int(11) unsigned NOT NULL,
  `slider_title` varchar(255) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `external_id` (`external_id`),
  KEY `category_id` (`category_id`),
  KEY `trademark_id` (`trademark_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE `trademarks` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `external_id` varchar(36) NOT NULL,
  `name` varchar(255) NOT NULL,
  `country_id` int(11) NOT NULL,
  `sort` int(11) unsigned NOT NULL DEFAULT '0',
  `sort_list` int(10) unsigned NOT NULL DEFAULT '0',
  `is_featured` tinyint(1) unsigned NOT NULL,
  `is_direct` tinyint(1) unsigned NOT NULL DEFAULT '0',
  PRIMARY KEY (`id`),
  UNIQUE KEY `external_id` (`external_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE `suppliers` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `external_id` varchar(36) NOT NULL,
  `code` varchar(64) NOT NULL,
  `name` varchar(255) NOT NULL,
  `delivery_period` tinyint(1) unsigned NOT NULL,
  `is_default` tinyint(1) unsigned NOT NULL,
  PRIMARY KEY (`id`),
  KEY `external_id` (`external_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

MySQLサーバー情報:

mysqld  Ver 5.5.45-1+deb.sury.org~trusty+1 for debian-linux-gnu on i686 ((Ubuntu))

3
SQL Fiddleにインデックス、テーブルスキーマ、およびテストデータを提供できますか?また、あなたの目標時間は何ですか?あなたはそれを3秒、1秒、50ミリ秒で完了することを目指していますか?さまざまなテーブル1k、100k、100Mにいくつのレコードがありますか?
Erik

並べ替えに使用しているフィールドにインデックスが付けられておらず、データセットが本当に大きい場合、sort_buffer_sizeの問題を確認できますか?セッションの値を変更してクエリを実行し、改善するかどうかを確認できます。
ブライアン・エフティング、2015年

にインデックスを追加してみました(inflow, product_id)か?
ypercubeᵀᴹ

まともなことを確認してくださいinnodb_buffer_pool_size。通常、使用可能なRAMの約70%が適切です。
リックジェームズ

回答:


6

テーブル定義を確認すると、関係するテーブル間で一致するインデックスがあることがわかります。これにより、MySQL's結合ロジックの制限内で可能な限り迅速に結合が行われます。

ただし、複数のテーブルからのソートはより複雑です。

2007年、Sergey Petruniaは3つのMySQL並べ替えアルゴリズムを速度の順に説明しMySQLました:http : //s.petrunia.net/blog/?m=201407

  1. 順序付けられた出力を生成するインデックスベースのアクセス方法を使用する
  2. 使用filesort()第一非定数テーブルの上に
  3. 結合結果を一時テーブルに入れて使用filesort()します

上記のテーブル定義と結合から、最速の並べ替えが得られないことがわかります。つまりfilesort()、使用しているソート基準に依存することになります。

ただし、マテリアライズドビューを設計して使用すると、最速のソートアルゴリズムを使用できます。

MySQL 5.5ソート方法について定義された詳細を確認するには、http//dev.mysql.com/doc/refman/5.5/en/order-by-optimization.htmlを参照してください。

以下のためにMySQL 5.5(この例では)増加しORDER BY、あなたが得ることができない場合は速度をMySQL、余分なソート段階ではなく、インデックスを使用して、以下の戦略を試してみてください。

sort_buffer_size変数の値を増やします。

read_rnd_buffer_size変数の値を増やします。

•実際の値を格納するために必要なだけの列を宣言することにより、行ごとに使用するRAMを減らします。[例:varchar(256)をvarchar(ActualLongestString)に削減]

tmpdir大量の空き容量がある専用のファイルシステムを指すようにシステム変数を変更します。(その他の詳細は上記のリンクで提供されています。)

速度MySQL 5.7を上げるためにドキュメントに詳細が記載されていますがORDER、その一部はわずかにアップグレードされた動作である可能性があります

http://dev.mysql.com/doc/refman/5.7/en/order-by-optimization.html

マテリアライズドビュー -結合されたテーブルをソートするための別のアプローチ

トリガーの使用に関する質問でマテリアライズドビューをほのめかしました。MySQLには、マテリアライズドビューを作成するための組み込み機能はありませんが、必要なツールはあります。トリガーを使用して負荷を分散することにより、その時点までマテリアライズドビューを維持できます。

マテリアライズド・ビューは、実際にテーブルに移入され、手続きコードをビルドまたはリビルドマテリアライズド・ビューをし、トリガーによって維持され、最新のデータを保持します。

あなたが構築しているので、テーブルがありますインデックスは、マテリアライズド・ビューを問い合わせ対象を使用することができたときに最速のソート方法注文した出力を生成使うインデックスベースのアクセス方法を

以来MySQL 5.5用途トリガーは維持するために、マテリアライズド・ビューを、あなたはまた、初期構築するためのプロセス、スクリプト、またはストアドプロシージャが必要になりますマテリアライズド・ビューを

しかし、それは明らかに、データを管理するベーステーブルを更新するたびに実行するにはプロセスが重すぎる。変更が加えられたときにデータを最新に保つためにトリガーが機能します。この方法で、それぞれinsertupdate、およびdeleteに、あなたのトリガを使用して、その変更を伝播しますマテリアライズド・ビュー

http://www.fromdual.com/のFROMDUAL組織には、マテリアライズドビューを維持するためのサンプルコードがあります。だから、私は自分のサンプルを書くのではなく、それらのサンプルを紹介します:

http://www.fromdual.com/mysql-materialized-views

例1:マテリアライズドビューの構築

DROP TABLE sales_mv;
CREATE TABLE sales_mv (
    product_name VARCHAR(128)  NOT NULL
  , price_sum    DECIMAL(10,2) NOT NULL
  , amount_sum   INT           NOT NULL
  , price_avg    FLOAT         NOT NULL
  , amount_avg   FLOAT         NOT NULL
  , sales_cnt    INT           NOT NULL
  , UNIQUE INDEX product (product_name)
);

INSERT INTO sales_mv
SELECT product_name
    , SUM(product_price), SUM(product_amount)
    , AVG(product_price), AVG(product_amount)
    , COUNT(*)
  FROM sales
GROUP BY product_name;

これにより、更新時にマテリアライズドビューが提供されます。ただし、データベースが高速で移動しているため、このビューを可能な限り最新の状態に保つ必要もあります。

したがって、影響を受けるベースデータテーブルには、ベーステーブルからマテリアライズドビューテーブルに変更を伝達するためのトリガーが必要です。一例として:

例2:マテリアライズドビューへの新しいデータの挿入

DELIMITER $$

CREATE TRIGGER sales_ins
AFTER INSERT ON sales
FOR EACH ROW
BEGIN

  SET @old_price_sum = 0;
  SET @old_amount_sum = 0;
  SET @old_price_avg = 0;
  SET @old_amount_avg = 0;
  SET @old_sales_cnt = 0;

  SELECT IFNULL(price_sum, 0), IFNULL(amount_sum, 0), IFNULL(price_avg, 0)
       , IFNULL(amount_avg, 0), IFNULL(sales_cnt, 0)
    FROM sales_mv
   WHERE product_name = NEW.product_name
    INTO @old_price_sum, @old_amount_sum, @old_price_avg
       , @old_amount_avg, @old_sales_cnt
  ;

  SET @new_price_sum = @old_price_sum + NEW.product_price;
  SET @new_amount_sum = @old_amount_sum + NEW.product_amount;
  SET @new_sales_cnt = @old_sales_cnt + 1;
  SET @new_price_avg = @new_price_sum / @new_sales_cnt;
  SET @new_amount_avg = @new_amount_sum / @new_sales_cnt;

  REPLACE INTO sales_mv
  VALUES(NEW.product_name, @new_price_sum, @new_amount_sum, @new_price_avg
       , @new_amount_avg, @new_sales_cnt)
  ;

END;
$$
DELIMITER ;

もちろん、マテリアライズドビューからのデータの削除マテリアライズドビューのデータの更新を維持するためのトリガーも必要です。これらのトリガーのサンプルも用意されています。

最後に:結合されたテーブルのソートをどのように速くするのですか?

マテリアライズド・ビューは更新がそれになされるよう、絶えず建設されています。したがって、あなたが定義することができますインデックス(またはインデックスを使用すると、データの並べ替えに使用すること)、マテリアライズド・ビューまたは表を

データを維持するオーバーヘッドが重すぎない場合、関連する各データ変更にリソース(CPU / IOなど)を費やしてマテリアライズドビューを維持しているため、インデックスデータは最新であり、すぐに利用できます。したがって、次の理由により、選択はより高速になります。

  1. SELECTのデータを準備するために、増分CPUとIOをすでに費やしています。
  2. 上のインデックスマテリアライズド・ビューを使用することができます最速のソート方法のMySQLに利用できる、すなわち命じた出力を生成使うインデックスベースのアクセス方法を

状況や全体的なプロセスについてどのように感じているかに応じて、ゆっくりとした期間に毎晩マテリアライズドビューを再構築したい場合があります。

注:ではMicrosoft SQL Server マテリアライズド・ビューが参照されるインデックス付きビューと自動的に基づいて更新されているインデックス付きビューのメタデータ。


6

ここで進めることはそれほど多くありませんが、主な問題は、ディスク上にかなり大きな一時テーブルとソートファイルを毎回作成していることだと思います。理由は:

  1. あなたはUTF8を使用しています
  2. 並べ替えにいくつかの大きなvarchar(255)フィールドを使用しています

これは、一時テーブルとソートファイルがかなり大きくなる可能性があることを意味します。一時テーブルを作成するとき、フィールドは最大長で作成され、レコードをソートするときはすべて最大長です(UTF8は1文字あたり3バイトです)。これらは、メモリ内の一時テーブルの使用を妨げている可能性もあります。詳しくは、内部一時テーブルの詳細をご覧ください

最初の3行が何であるかがわかる前に、結果セット全体を具体化して順序付ける必要があるため、LIMITもここでは役に立ちません。

tmpdirtmpfsファイルシステムに移動してみましたか?/ tmpがまだtmpfsを使用していない場合(MySQLはtmpdir=/tmpデフォルトで* nixを使用します)、/ dev / shmを直接使用できます。my.cnfファイルで:

[mysqld]
...
tmpdir=/dev/shm  

次に、mysqldを再起動する必要があります。

それは大きな違いを生む可能性があります。システムのメモリが不足する可能性がある場合は、メモリセグメントがディスクにスワップアウトされないようにするために、サイズを制限する必要があります(通常、Linuxディストリビューションはtmpfsを合計RAMの50%に設定します)。OOMの状況はさらに悪化します。これは、次の行を編集して行うことができます/etc/fstab

tmpfs                   /dev/shm                tmpfs   rw,size=2G,noexec,nodev,noatime,nodiratime        0 0

「オンライン」でもサイズを変更できます。例えば:

mount -o remount,size=2G,noexec,nodev,noatime,nodiratime /dev/shm

MySQL 5.6(パフォーマンスの高いサブクエリと派生テーブルを含む)にアップグレードして、クエリをもう少し試すこともできます。私が見る限りでは、そのルートで大きな勝利が見られるとは思いません。

幸運を!


ご回答有難うございます。tmpdirをtmpfsに移動すると、パフォーマンスが向上しました。
スタニスラフガマユノフ2015年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.