조인 된 테이블의 열을 기준으로 정렬을 최적화하는 방법이 있습니까?


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 문제를 볼 수 있습니까? 세션에서 값을 수정하고 쿼리를 실행하여 값이 향상되는지 확인할 수 있습니다.
Brian Efting

에 색인을 추가해 보셨습니까 (inflow, product_id)?
ypercubeᵀᴹ

괜찮은지 확인하십시오 innodb_buffer_pool_size. 일반적으로 사용 가능한 RAM의 약 70 %가 좋습니다.
Rick James

답변:


6

테이블 정의를 검토하면 관련된 테이블에서 일치하는 인덱스가 있음이 표시됩니다. 이로 인해 MySQL's조인 논리 한계 내에서 조인이 가능한 한 빨리 발생해야합니다 .

그러나 여러 테이블에서 정렬 하는 것이 더 복잡합니다.

2007 년 Sergey Petrunia는 다음에 MySQL대한 속도 순서로 3 가지 정렬 알고리즘을 설명 MySQL했습니다. http://s.petrunia.net/blog/?m=201407

  1. 순서화 된 출력을 생성하는 인덱스 기반 액세스 방법 사용
  2. 사용 filesort()1 일정하지 않은 테이블에
  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사용하는 트리거가 유지 구체화보기를 , 당신은 또한 초기 구축하는 프로세스, 스크립트, 또는 저장 프로 시저가 필요합니다 구체화보기 .

그러나 데이터를 관리하는 기본 테이블에 대한 각 업데이트 후에 실행하기에는 프로세스가 너무 무겁습니다. 변경 사항이있을 때 데이터를 최신 상태로 유지하기 위해 트리거가 작동하는 지점입니다. 이 방법은 각각 insert, updatedelete, 변경 사항을 전달받는 사람, 당신의 트리거를 사용합니다 구체화보기 .

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 / etc)를 소비 하므로 인덱스 데이터가 최신 상태이며 쉽게 사용할 수 있습니다. 따라서 다음과 같은 이유로 선택이 더 빨라집니다.

  1. SELECT를 위해 데이터를 준비하기 위해 이미 증분 CPU 및 IO를 사용했습니다.
  2. 온 인덱스 구체화보기 사용할 수있는 가장 빠른 정렬 방법 , MySQL을 사용할 수를, 즉 출력을 주문 생산하고 인덱스 기반 액세스 방법을 사용합니다 .

상황과 전반적인 프로세스에 대한 느낌에 따라 느린 밤 동안 매일 밤 구체화 된 뷰 를 다시 빌드 할 수 있습니다 .

참고 : 에서 Microsoft SQL Server 구체화 된 뷰가 참조하는 인덱싱 된 뷰 자동 기반으로 업데이트됩니다 인덱싱 된 뷰의 메타 데이터.


6

여기서 할 일이 많지는 않지만 가장 큰 문제는 매번 디스크에 상당히 큰 임시 테이블과 정렬 파일을 만드는 것입니다. 이유는 다음과 같습니다.

  1. UTF8을 사용하고 있습니다
  2. 정렬에 큰 varchar (255) 필드를 사용하고 있습니다

즉, 임시 테이블을 작성할 때 필드가 MAX 길이로 작성되고 레코드 정렬이 모두 MAX 길이 (및 UTF8이 문자 당 3 바이트) 인 것처럼 임시 테이블 및 정렬 파일이 상당히 클 수 있습니다. 또한 메모리 내 임시 테이블 사용을 배제 할 수 있습니다. 자세한 정보는 내부 임시 테이블 세부 사항을 참조하십시오 .

LIMIT는 또한 우리가 처음 3 행이 무엇인지 알기 전에 전체 결과 세트를 구체화하고 주문해야하기 때문에 여기서 우리에게 좋지 않습니다.

tmpdirtmpfs 파일 시스템으로 옮기려고 했습니까 ? / tmp가 아직 tmpfs를 사용하지 않는 경우 (MySQL은 tmpdir=/tmp기본적으로 * nix에서 사용) / dev / shm을 직접 사용할 수 있습니다. my.cnf 파일에서 :

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

그런 다음 mysqld를 다시 시작해야합니다.

그것은 차이를 만들 수 있습니다. 시스템에서 메모리 부족이 발생할 가능성이 있다면 메모리 세그먼트를 디스크로 스왑하거나 심지어는 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로 옮기면 성능이 향상됩니다.
Stanislav Gamayunov
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.