문제

다음과 같이 간단한 태그-레코드 구조와 유사한 두 개의 테이블이 있습니다(실제로는 훨씬 더 복잡하지만 이것이 문제의 본질입니다).

tag (A.a) | recordId (A.b)
1         | 1
2         | 1
2         | 2
3         | 2
....

그리고

recordId (B.b) | recordData (B.c)
1              | 123
2              | 666
3              | 1246

문제는 특정 태그가 있는 정렬된 레코드를 가져오는 것입니다.이를 수행하는 확실한 방법은 다음과 같이 (PK)(A.a, A.b), (A.b), (PK)(B.b), (B.b,B.c)에 대한 간단한 조인 및 인덱스를 사용하는 것입니다.

select A.a, A.b, B.c from A join B on A.b = B.b where a = 44 order by c;

그러나 이는 파일 정렬의 불쾌한 결과를 가져옵니다.

+----+-------------+-------+------+---------------+---------+---------+-----------+------+----------------------------------------------+
| id | select_type | table | type | possible_keys | key     | key_len | ref       | rows | Extra                                        |
+----+-------------+-------+------+---------------+---------+---------+-----------+------+----------------------------------------------+
|  1 | SIMPLE      | A     | ref  | PRIMARY,b     | PRIMARY | 4       | const     |   94 | Using index; Using temporary; Using filesort | 
|  1 | SIMPLE      | B     | ref  | PRIMARY,b     | b       | 4       | booli.A.b |    1 | Using index                                  | 
+----+-------------+-------+------+---------------+---------+---------+-----------+------+----------------------------------------------+

거대하고 매우 중복된 "구체화된 뷰"를 사용하면 꽤 괜찮은 성능을 얻을 수 있지만 이는 비즈니스 논리를 복잡하게 만드는 대가로 피하고 싶은 것입니다. 특히 A와 B 테이블이 이미 MV:s이기 때문에(그리고 다른 쿼리에 필요하며 실제로 UNION을 사용하여 동일한 쿼리를 수행합니다.

create temporary table C engine=innodb as (select A.a, A.b, B.c from A join B on A.b = B.b);
explain select a, b, c from C where a = 44 order by c;

상황을 더욱 복잡하게 만드는 것은 범위 필터와 같은 B-테이블에 조건이 있다는 사실입니다.

select A.a, A.b, B.c from A join B on A.b = B.b where a = 44 AND B.c > 678 order by c;

하지만 파일 정렬 문제가 사라지면 이 문제를 처리할 수 있다고 확신합니다.

위의 코드 블록 3의 단순 조인이 정렬을 위해 인덱스를 사용하지 않는 이유와 새 MV를 생성하지 않고도 어떤 방식으로 문제를 해결할 수 있는지 아는 사람이 있습니까?

다음은 테스트에 사용하는 전체 SQL 목록입니다.

DROP TABLE IF EXISTS A;
DROP TABLE IF EXISTS B;
DROP TABLE IF EXISTS C;
CREATE TEMPORARY TABLE A (a INT NOT NULL, b INT NOT NULL, PRIMARY KEY(a, b), INDEX idx_A_b (b)) ENGINE=INNODB;
CREATE TEMPORARY TABLE B (b INT NOT NULL, c INT NOT NULL, d VARCHAR(5000) NOT NULL DEFAULT '', PRIMARY KEY(b), INDEX idx_B_c (c), INDEX idx_B_b (b, c)) ENGINE=INNODB;

DELIMITER $$
CREATE PROCEDURE prc_filler(cnt INT)
BEGIN
        DECLARE _cnt INT;
        SET _cnt = 1;
        WHILE _cnt <= cnt DO
                INSERT IGNORE INTO A SELECT RAND()*100, RAND()*10000;
                INSERT IGNORE INTO B SELECT RAND()*10000, RAND()*1000, '';
                SET _cnt = _cnt + 1;
        END WHILE;
END
$$
DELIMITER ;

START TRANSACTION;
CALL prc_filler(100000);
COMMIT;
DROP PROCEDURE prc_filler;

CREATE TEMPORARY TABLE C ENGINE=INNODB AS (SELECT A.a, A.b, B.c FROM A JOIN B ON A.b = B.b);
ALTER TABLE C ADD (PRIMARY KEY(a, b), INDEX idx_C_a_c (a, c));

EXPLAIN EXTENDED SELECT A.a, A.b, B.c FROM A JOIN B ON A.b = B.b WHERE A.a = 44;
EXPLAIN EXTENDED SELECT A.a, A.b, B.c FROM A JOIN B ON A.b = B.b WHERE 1 ORDER BY B.c;
EXPLAIN EXTENDED SELECT A.a, A.b, B.c FROM A JOIN B ON A.b = B.b where A.a = 44 ORDER BY B.c;
EXPLAIN EXTENDED SELECT a, b, c FROM C WHERE a = 44 ORDER BY c;
-- Added after Quassnois comments
EXPLAIN EXTENDED SELECT A.a, A.b, B.c FROM  B FORCE INDEX (idx_B_c) JOIN A ON A.b = B.b WHERE A.a = 44 ORDER BY B.c;
EXPLAIN EXTENDED SELECT A.a, A.b, B.c FROM A JOIN B ON A.b = B.b WHERE A.a = 44 ORDER BY B.c LIMIT 10;
EXPLAIN EXTENDED SELECT A.a, A.b, B.c FROM  B FORCE INDEX (idx_B_c) JOIN A ON A.b = B.b WHERE A.a = 44 ORDER BY B.c LIMIT 10;
도움이 되었습니까?

해결책

귀하의 스크립트를 사용하여 이 쿼리를 재현하려고 하면 다음과 같습니다.

SELECT  A.a, A.b, B.c
FROM    A
JOIN    B
ON      A.b = B.b
WHERE   a = 44
ORDER BY
        c

, 에 완료됩니다. 0.0043 seconds (즉시), 반환 930 행을 작성하고 다음 계획을 산출합니다.

1, 'SIMPLE', 'A', 'ref', 'PRIMARY', 'PRIMARY', '4', 'const', 1610, 'Using index; Using temporary; Using filesort'
1, 'SIMPLE', 'B', 'eq_ref', 'PRIMARY', 'PRIMARY', '4', 'test.A.b', 1, ''

이러한 쿼리에는 매우 효율적입니다.

이러한 쿼리의 경우 필터링과 정렬 모두에 단일 인덱스를 사용할 수 없습니다.

자세한 설명은 내 블로그의 이 기사를 참조하세요.

쿼리에서 반환되는 레코드 수가 적을 것으로 예상되는 경우 인덱스를 사용해야 합니다. A 필터링한 다음 filesort를 사용하여 정렬합니다(위의 쿼리와 유사).

많은 레코드를 반환할 것으로 예상되는 경우(그리고 LIMIT ), 정렬을 위해 인덱스를 사용한 다음 필터링해야 합니다.

CREATE INDEX ix_a_b ON a (b);
CREATE INDEX ix_b_c ON b (c)

SELECT  *
FROM    B FORCE INDEX (ix_b_c)
JOIN    A
ON      A.b = B.b
ORDER BY
        b.c
LIMIT 10;

1, 'SIMPLE', 'B', 'index', '', 'ix_b_c', '4', '', 2, 'Using index'
1, 'SIMPLE', 'A', 'ref', 'ix_a_b', 'ix_a_b', '4', 'test.B.b', 4, 'Using index'

다른 팁

select A.a, A.b, B.c from A join B on A.b = B.b where a = 44 order by c;

열에 별칭을 지정하면 도움이 됩니까?예:

 SELECT 
 T1.a AS colA, 
 T2.b AS colB, 
 T2.c AS colC 
 FROM A AS T1 
 JOIN B AS T2 
 ON (T1.b = T2.b) 
 WHERE 
 T1.a = 44 
 ORDER BY colC;

내가 변경한 유일한 내용은 다음과 같습니다.

  • 괄호 안에 조인 조건을 넣었습니다
  • 조인 조건 및 조건이 테이블 열을 기반으로 하는 위치
  • ORDER BY 조건은 결과 테이블 열을 기반으로 합니다.
  • 결과 테이블 열과 쿼리된 테이블에 별칭을 지정하여 둘 중 하나를 사용할 때 더 명확하게 하고 서버에서도 더 명확하게 만들 수 있기를 바랍니다.원래 쿼리의 두 위치에서 열을 참조하는 것을 무시했습니다.

귀하의 실제 데이터가 더 복잡하다는 것을 알고 있지만 문제가 그렇게 간단한 수준에 있기 때문에 귀하가 간단한 버전의 쿼리를 제공했다고 가정합니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top