10

参加する回数が増えるほど、指数関数的に遅くなる連絡先メッセージングシステムのクエリがあります。

テーブル構造は、基本的に連絡先テーブルと連絡先フィールドテーブルです。

クエリは連絡先フィールドテーブルに何度も結合します。結合するたびに、2倍の時間がかかります。

これがクエリです。

SELECT  SQL_CALC_FOUND_ROWS
    `contact_data`.`id`,
    `contact_data`.`name`,
    `fields0`.`value` AS `fields0`,
    `fields1`.`value` AS `fields1`,
    `fields2`.`value` AS `fields2`,
    ...etc...
    CONTACT_DATA_TAGS(
        GROUP_CONCAT(DISTINCT `contact_data_tags`.`name`),
        GROUP_CONCAT(DISTINCT `contact_data_assignment`.`user`),
        GROUP_CONCAT(DISTINCT `contact_data_read`.`user`)
    ) AS `tags`,
    GROUP_CONCAT(DISTINCT `contact_data_assignment`.`user`) AS `assignments`,
    `contact_data`.`updated`,
    `contact_data`.`created`
FROM
    `contact_data`
LEFT JOIN contact_data_tags ON contact_data.`id` = contact_data_tags.`data`
LEFT JOIN contact_data_assignment ON contact_data.`id` = contact_data_assignment.`data`
LEFT JOIN contact_data_read ON contact_data.`id` = contact_data_read.`data`
LEFT JOIN contact_data_fields AS fields0 ON contact_data.`id` = fields0.`contact_data_id` AND fields0.`key` = :field1
LEFT JOIN contact_data_fields AS fields1 ON contact_data.`id` = fields1.`contact_data_id` AND fields1.`key` = :field2
LEFT JOIN contact_data_fields AS fields2 ON contact_data.`id` = fields2.`contact_data_id` AND fields2.`key` = :field3
...etc...
GROUP BY contact_data.`id`
ORDER BY `id` DESC

これはテーブル構造です:

CREATE TABLE IF NOT EXISTS `contact_data` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(200) NOT NULL,
  `format` varchar(50) NOT NULL,
  `fields` longtext NOT NULL,
  `url` varchar(2000) NOT NULL,
  `referer` varchar(2000) DEFAULT NULL,
  `ip` varchar(40) NOT NULL,
  `agent` varchar(1000) DEFAULT NULL,
  `created` datetime NOT NULL,
  `updated` datetime NOT NULL,
  `updater` int(10) unsigned DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `name` (`name`),
  KEY `url` (`url`(333)),
  KEY `ip` (`ip`),
  KEY `created` (`created`),
  KEY `updated` (`updated`),
  KEY `updater` (`updater`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8;

CREATE TABLE IF NOT EXISTS `contact_data_assignment` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `user` int(10) unsigned NOT NULL,
  `data` int(10) unsigned NOT NULL,
  `created` datetime NOT NULL,
  `updater` int(10) unsigned DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `unique_assignment` (`user`,`data`),
  KEY `user` (`user`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8;

CREATE TABLE IF NOT EXISTS `contact_data_fields` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `contact_data_id` int(10) unsigned NOT NULL,
  `key` varchar(200) NOT NULL,
  `value` text NOT NULL,
  `updated` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  KEY `contact_data_id` (`contact_data_id`),
  KEY `key` (`key`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8;

CREATE TABLE IF NOT EXISTS `contact_data_read` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `user` int(10) unsigned NOT NULL,
  `data` int(10) unsigned NOT NULL,
  `type` enum('admin','email') NOT NULL,
  `created` datetime NOT NULL,
  PRIMARY KEY (`id`),
  KEY `user` (`user`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8;

CREATE TABLE IF NOT EXISTS `contact_data_tags` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(200) NOT NULL,
  `data` int(10) unsigned NOT NULL,
  `created` datetime NOT NULL,
  `updater` int(10) unsigned DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `unique_tag` (`name`,`data`),
  KEY `name` (`name`),
  KEY `data` (`data`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8;

DELIMITER $$
CREATE FUNCTION `contact_data_tags`(`tags` TEXT, `assigned` BOOL, `read` BOOL) RETURNS text CHARSET latin1
BEGIN
    RETURN CONCAT(
        ',',
        IFNULL(`tags`, ''),
        ',',
        IF(`tags` IS NULL OR FIND_IN_SET('Closed', `tags`) = 0, 'Open', ''),
        ',',
        IF(`assigned` IS NULL, 'Unassigned', ''),
        ',',
        IF(`read` IS NULL, 'New', ''),
        ','
    );
END$$

DELIMITER ;

なぜそれがとても遅いのか誰もが知っていますか?それを速くするために私は何ができますか?クエリを調整する必要がありますか(構造を調整したくない)?それをスピードアップするために設定できる構成オプションはありますか?

また、奇妙なことに、私のWindows開発マシンでは、Debain本番サーバーと比較して(30秒以上と比較してほぼ瞬時に)動作するようです。

ただし、WindowsマシンはDebainサーバー(8コアXeon、32GB RAM)よりもはるかに強力ではありません。

MySQL 5.1.49をDebian(更新できません)で実行し、5.5.28をWindowsで実行します。

したがって、EAVがRDBMSで(または少なくとも私の場合は)うまく機能しないことを読んで、これをより速く実行するために増やすことができる構成オプションはありますか(つまり、RAMを増やすことができますか)?

4

4 に答える 4

5

クエリを高速化する1つの方法は、(on )にcontact_data_fields 1回だけリンクし、フィールドの列を式に変更することです。contact_data.id = contact_data_fields.contact_data_idmax

SELECT  SQL_CALC_FOUND_ROWS
    `contact_data`.`id`,
    `contact_data`.`name`,
    MAX(CASE WHEN fields.`key` = :field1 THEN fields.`value` END) AS `fields0`,
    MAX(CASE WHEN fields.`key` = :field2 THEN fields.`value` END) AS `fields1`,
    MAX(CASE WHEN fields.`key` = :field3 THEN fields.`value` END) AS `fields2`,
    ...etc...
    CONTACT_DATA_TAGS(
        GROUP_CONCAT(DISTINCT `contact_data_tags`.`name`),
        GROUP_CONCAT(DISTINCT `contact_data_assignment`.`user`),
        GROUP_CONCAT(DISTINCT `contact_data_read`.`user`)
    ) AS `tags`,
    GROUP_CONCAT(DISTINCT `contact_data_assignment`.`user`) AS `assignments`,
    `contact_data`.`updated`,
    `contact_data`.`created`
FROM
    `contact_data`
LEFT JOIN contact_data_tags ON contact_data.`id` = contact_data_tags.`data`
LEFT JOIN contact_data_assignment ON contact_data.`id` = contact_data_assignment.`data`
LEFT JOIN contact_data_read ON contact_data.`id` = contact_data_read.`data`
LEFT JOIN contact_data_fields AS fields
       ON contact_data.`id` = fields.`contact_data_id` 
...etc...
GROUP BY contact_data.`id`
ORDER BY `id` DESC
于 2013-01-21T13:18:37.253 に答える
3

残念ながら、クエリには多くの非効率性があります。いくつかのパラメータを調整し、RAMを追加するだけで、問題を解決できるとは思いません。

  • まず、テーブルのサイズがわかりません。なぜテーブル全体をダンプする必要があるのでしょうかcontact_data。追加の条件や制限はありません(通常は重要です)。
  • 特定のに対して同じ(contact_data_id、key)を持つ複数のレコードが存在する可能性があるかどうかもわかりませんcontact_data.id。{0、1}レコードになる可能性があると思いますが、対応する一意のインデックス(クエリを効率化するためのインデックスとして最終的に必要になる)があれば、これをより明確にすることができます。
  • SQL_CALC_FOUND_ROWSは追加のキラーです(LIMITを使用する場合)。これは、MySQLが結果全体を計算してスキャンし、行をカウントするためです(裸のIDをフェッチする別のクエリで行をカウントし、その結果をキャッシュします) 。テーブルがあまり頻繁に変更されない場合は、MySQL独自のクエリキャッシュで十分な場合があります)

にインデックスを追加するとすぐに(contact_data_id, key)、グループ化と並べ替えをサブクエリに分離し、次にcontact_data_fields(並べ替えなしで)左結合します。contact_data現在のクエリでは、グループ化される前にcontact_data_tags、、、、contact_data_assignmentの積の各行に対して同じLEFT JOIN比較が行われcontact_data_readます(サーバーが中間結果全体を保存してから、everithingがグループ化され、重複データが破棄されることは言うまでもありません)。

于 2013-01-21T13:06:38.800 に答える
2

これらすべての興味深いコメントに、Entity-Attribute-Value-ModelクエリとMySQLに関する私自身の経験を追加します。

まず、MySQLには61の結合の数に下限があることを忘れないでください。最初は大きな数字のようです。しかし、このモデルでは、クエリを簡単にクラッシュさせる可能性がありますSQLSTATE[HY000]: General error: 1116

私はこれらの指数関数的な減速も経験しました。50 000行のテーブルで50の結合があるクエリで最初に20を超えると、クエリオプティマイザでこれらの15の14.5が失われることがわかりました-これらの50の結合の最適な結合順序を推測しようとしていたようです- 。STRAIGHT_JOINしたがって、キーワードの直後にキーワードを追加するだけでSELECT、通常の時間に戻りました。もちろん、それを行うには、優れたインデックススキームを取得し、巧妙な結合順序でクエリを作成する必要があります(最適なインデックスと最適な人口削減を備えたテーブルが最初に表示されます)。

SELECT STRAIGHT_JOIN (...)

このキーワードは、JOIN構文でも使用できることに注意してください。

STRAIGHT_JOINは、オプティマイザがFROM句にリストされている順序でテーブルを結合するように強制します。オプティマイザが最適でない順序でテーブルを結合する場合、これを使用してクエリを高速化できます。

「または、この順序を推測するのにクエリ時間の95%かかる場合」を追加します:-)

クエリで直接実行される他のクエリオプティマイザ設定については、このページも確認してください。

次に、 5.1と5.5の間に違いがあります...これらのバージョンの間には非常に多くの違いがあります。これは、2つの異なるデータベースサーバーで作業しているようなものです。速度の向上(Perconaもチェック)だけでなく、トランザクションとロックの向上にも5.5を本番環境で使用することを検討する必要があります。理由が1つだけ必要な場合は、本番環境で開発にないバグが発生する可能性があります。

多くの結合を含むこれらのクエリは、定義上、サーバーにストレスを与えます。サーバーの動作を制御するには、my.cnfファイルを微調整する必要があります。たとえば、一時テーブルの作成を回避するようにしてください(クエリのexplain出力を確認してください)。2秒のクエリは120秒のクエリになる可能性があります。これは、制限に達し、一時ファイルに移動して20または30の結合を管理し、並べ替えてグループ化するためです。ディスクにデータを置くことは、メモリ作業に比べて本当に遅いです。これは特に次の2つの設定によって制御されます。

tmp_table_size = 1024M
max_heap_table_size = 1024M

ここでは、「RAMが1Go未満の場合は、要求のためにメモリ作業を維持する」と言います。もちろん、これを行う場合は、500の並列スクリプトでこれらのリクエストを実行しないようにします。多くの並列リクエストで定期的に必要な場合は、このデータスキームを避けることを検討してください。

これも1つの重要なポイントにつながります。1つのリクエストの複雑さのフロンティアに到達しています。SQLサーバーは通常、アプリケーションよりも高速で、1つの結果にデータを集約します。しかし、データのサイズが大きく、クエリに多くのインデックスを追加し(結合ごとに少なくとも1つ)、group_contactを使用して結果を並べ替え、グループ化し、さらには集計する場合...MySQLは確実に一時ファイルを使用しますそしてそれは遅くなります。いくつかの短いクエリ(たとえば、group byを使用しないメインクエリと、group_contactフィールドのコンテンツを取得するための10または200クエリ)を使用すると、一時ファイルの使用を回避することで高速化できます。

于 2013-01-25T00:57:06.553 に答える
1

Mark Ba​​nnistersクエリに基づいて、次のようなものを使用して、フィールド/値の詳細を区切りリストとして返すことができます。-

SELECT  SQL_CALC_FOUND_ROWS
    `contact_data`.`id`,
    `contact_data`.`name`,
    GROUP_CONCAT(CONCAT_WS(',', contact_data_fields.`key`, contact_data_fields.`value`)),
    CONTACT_DATA_TAGS(
        GROUP_CONCAT(DISTINCT `contact_data_tags`.`name`),
        GROUP_CONCAT(DISTINCT `contact_data_assignment`.`user`),
        GROUP_CONCAT(DISTINCT `contact_data_read`.`user`)
    ) AS `tags`,
    GROUP_CONCAT(DISTINCT `contact_data_assignment`.`user`) AS `assignments`,
    `contact_data`.`updated`,
    `contact_data`.`created`
FROM
    `contact_data`
LEFT JOIN contact_data_tags ON contact_data.`id` = contact_data_tags.`data`
LEFT JOIN contact_data_assignment ON contact_data.`id` = contact_data_assignment.`data`
LEFT JOIN contact_data_read ON contact_data.`id` = contact_data_read.`data`
LEFT JOIN contact_data_fields ON contact_data.`id` = contact_data_fields.`contact_data_id` 
WHERE contact_data_fields.`key` IN (:field1, :field2, :field3, etc)
GROUP BY contact_data.`id`
ORDER BY `id` DESC

contact_data_tags、contact_data_assignment、contact_data_readテーブルの一致する行の数(および各contact_data.idの中間行の数)によっては、副選択から連絡先のキー/値の詳細を取得する方が速い場合があります。

SELECT  SQL_CALC_FOUND_ROWS
    `contact_data`.`id`,
    `contact_data`.`name`,
    Sub1.ContactKeyValue,
    CONTACT_DATA_TAGS(
        GROUP_CONCAT(DISTINCT `contact_data_tags`.`name`),
        GROUP_CONCAT(DISTINCT `contact_data_assignment`.`user`),
        GROUP_CONCAT(DISTINCT `contact_data_read`.`user`)
    ) AS `tags`,
    GROUP_CONCAT(DISTINCT `contact_data_assignment`.`user`) AS `assignments`,
    `contact_data`.`updated`,
    `contact_data`.`created`
FROM
    `contact_data`
LEFT JOIN contact_data_tags ON contact_data.id = contact_data_tags.`data`
LEFT JOIN contact_data_assignment ON contact_data.id = contact_data_assignment.`data`
LEFT JOIN contact_data_read ON contact_data.id = contact_data_read.`data`
LEFT JOIN (SELECT contact_data_id, GROUP_CONCAT(CONCAT_WS(',', contact_data_fields.`key`, contact_data_fields.`value`)) AS ContactKeyValue FROM contact_data_fields 
WHERE fields.`key` IN (:field1, :field2, :field3, etc) GROUP BY contact_data_id) Sub1 ON contact_data.id = Sub1.contact_data_id
GROUP BY contact_data.id
ORDER BY `id` DESC
于 2013-01-23T12:05:14.527 に答える