4

私は仕事用のカスタムヘルプデスクを作成しましたが、最近まで順調に稼働しています。1つのクエリが本当に遅くなりました。今は約14秒かかります!関連する表は次のとおりです。

CREATE TABLE `tickets` (
  `id` int(11) unsigned NOT NULL DEFAULT '0',
  `date_submitted` datetime DEFAULT NULL,
  `date_closed` datetime DEFAULT NULL,
  `first_name` varchar(50) DEFAULT NULL,
  `last_name` varchar(50) DEFAULT NULL,
  `email` varchar(50) DEFAULT NULL,
  `description` text,
  `agent_id` smallint(5) unsigned NOT NULL DEFAULT '1',
  `status` smallint(5) unsigned NOT NULL DEFAULT '1',
  `priority` tinyint(4) NOT NULL DEFAULT '0',
  PRIMARY KEY (`id`),
  KEY `date_closed` (`date_closed`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE `solutions` (
  `id` int(10) unsigned NOT NULL,
  `ticket_id` mediumint(8) unsigned DEFAULT NULL,
  `date` datetime DEFAULT NULL,
  `hours_spent` float DEFAULT NULL,
  `agent_id` smallint(5) unsigned DEFAULT NULL,
  `body` text,
  PRIMARY KEY (`id`),
  KEY `ticket_id` (`ticket_id`),
  KEY `date` (`date`),
  KEY `hours_spent` (`hours_spent`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

ユーザーがチケットを送信すると、「チケット」テーブルに移動します。次に、エージェントが問題を処理するときに、実行したアクションを記録します。各エントリは「ソリューション」テーブルに入ります。言い換えれば、チケットには多くの解決策があります。

速度が低下したクエリの目的は、「チケット」テーブルからすべてのフィールドを取得し、「ソリューション」テーブルから最新のエントリを取得することです。これは私が使用しているクエリです:

SELECT tickets.*,
    (SELECT CONCAT_WS(" * ", DATE_FORMAT(solutions.date, "%c/%e/%y"), solutions.hours_spent, CONCAT_WS(": ", solutions.agent_id, solutions.body))
    FROM solutions
    WHERE solutions.ticket_id = tickets.id
    ORDER BY solutions.date DESC, solutions.id DESC
    LIMIT 1
) AS latest_solution_entry
FROM tickets
WHERE tickets.date_closed IS NULL
OR tickets.date_closed >= '2012-06-20 00:00:00'
ORDER BY tickets.id DESC

「latest_solution_entry」フィールドの例を次に示します。

6/20/12 * 1337 * 1: I restarted the computer and that fixed the problem. Yes, I took an hour to do this.

PHPでは、「latest_solution_entry」フィールドを分割して正しくフォーマットしました。

クエリを実行するページの速度が大幅に低下していることに気付いたとき、サブクエリを使用せずにクエリを実行したところ、非常に高速でした。次にEXPLAIN、元のクエリでを実行して、これを取得しました。

+----+--------------------+-----------+-------+---------------+-----------+---------+---------------------+-------+-----------------------------+
| id | select_type        | table     | type  | possible_keys | key       | key_len | ref                 | rows  | Extra                       |
+----+--------------------+-----------+-------+---------------+-----------+---------+---------------------+-------+-----------------------------+
|  1 | PRIMARY            | tickets   | index | date_closed   | PRIMARY   | 4       | NULL                | 35804 | Using where                 |
|  2 | DEPENDENT SUBQUERY | solutions | ref   | ticket_id     | ticket_id | 4       | helpdesk.tickets.id |     1 | Using where; Using filesort |
+----+--------------------+-----------+-------+---------------+-----------+---------+---------------------+-------+-----------------------------+

そのため、クエリをより効率的にし、それでも同じ目標を達成する方法を探しています。何か案は?

4

4 に答える 4

19

私が理解したことを要約しましょう:あなたは各チケットとその最後の解決策を選択したいと思います。

この種の質問には次のパターンを使用するのが好きです。これは、サブクエリパターンを回避するため、パフォーマンスが必要な場合にかなり適しているためです。欠点は、理解するのが少し難しいことです。

SELECT
  t.*,
  s1.*
FROM tickets t
INNER JOIN solutions s1 ON t.id = s1.ticket_id
LEFT JOIN solutions s2 ON s1.ticket_id = s2.ticket_id AND s2.id > s1.id
WHERE s2.id IS NULL;

理解を深めるために、パターンの中心だけを書きました。

キーは次のとおりです。

  • テーブルのLEFTJOINは、solutionsそれ自体がs1.ticket_id = s2.ticket_id条件付きで、をエミュレートしGROUP BY ticket_idます。

  • 条件s2.id > s1.id:「最後のソリューションのみが必要」のSQLであり、をエミュレートしMAX()ます。あなたのモデルでは、the lastという意味with the greatest idだと思いましたが、ここでは日付の条件を使用できます。s2.id < s1.idそれがあなたに最初の解決策を与えることに注意してください。

  • WHERE句s2.id IS NULL:最も奇妙なものですが、絶対に必要です...必要なレコードのみを保持します。

試してみて、私に知らせてください:)

編集1: 2番目のポイントの仮定が問題を単純化しすぎていることに気づきました。それはそれをさらに面白くします:p私はこのパターンがあなたのdate, id注文でどのように機能するかを見ようとしています。

編集2:わかりました、少しひねりを加えてうまく機能します。LEFTJOINの条件は次のようになります。

LEFT JOIN solutions s2 ON s1.ticket_id = s2.ticket_id
  AND (s2.date > s1.date OR (s2.date = s1.date AND s2.id > s1.id))
于 2012-06-21T08:18:48.570 に答える
1

SELECT句にインラインビューがある場合は、すべての行に対してその選択を実行する必要があります。このような場合は、代わりにFROM句にインラインビューを配置して、選択を1回実行する方がよいと思います。

SELECT t.*, 
       Concat_ws(" * ", Date_format(s.date, "%c/%e/%y"), s.hours_spent, 
       Concat_ws(":", s.agent_id, s.body)) 
FROM   tickets t 
       INNER JOIN (SELECT solutions.ticket_id,
                          Max(solutions.date) maxdate 
                   FROM   solutions 
                   GROUP  BY solutions.ticket_id) last_solutions 
               ON t.id = last_solutions.ticket_id
       INNER JOIN (SELECT solutions.ticket_id,
                          solutions.date,
                          Max(solutions.id) maxid 
                   FROM   solutions 
                   GROUP  BY solutions.ticket_id,
                            solutions.date) last_solution
              ON last_solutions.ticket_id = last_solution.ticket_id 
                 and last_solutions.maxDate = last_solution.Date
       INNER JOIN solutions s 
               ON last_solution.maxid = s.id
WHERE  t.date_closed IS NULL 
        OR t.date_closed >= '2012-06-20 00:00:00' 
ORDER  BY t.id DESC 

注:必要に応じて、左結合にする必要がある場合があります

于 2012-06-21T03:55:47.697 に答える
1

これを試して:

SELECT *
FROM (
  -- for each ticket get the most recent solution date
  SELECT ticket_id, MAX(solutions.date) as date
  FROM solutions
  GROUP BY ticket_id
) t
JOIN tickets ON t.ticket_id = tickets.id
WHERE tickets.date_closed IS NULL OR tickets.date_closed >= '2012-06-20 00:00:00'
ORDER BY tickets.id DESC

同じ日付の2つのソリューションを含むチケットがある場合、結果セットに重複するレコードがあることに注意してください。これらの重複を削除するか、シリアル(主キーのインクリメント)のような絶対シーケンスを使用するには、別の結合が必要になります。

于 2012-06-21T04:06:18.107 に答える
1

目的に応じて、私はアイデアを出します:

SELECT DISTINCT s1.ticket_id, t.*,  s1.*
FROM tickets t
LEFT JOIN solutions s1 ON t.id = s1.ticket_id
于 2013-10-30T09:41:43.017 に答える