조인 대 하위 쿼리


837

나는 구식 MySQL 사용자이며 항상 JOIN하위 쿼리 보다 선호 했습니다. 그러나 요즘에는 모든 사람들이 하위 쿼리를 사용하고 있습니다. 이유를 모르겠습니다.

차이가 있는지 스스로 판단 할 이론적 지식이 부족합니다. 하위 쿼리는 a만큼 우수 JOIN하므로 걱정할 것이 없습니까?


23
하위 쿼리는 때때로 훌륭합니다. 그들은 MySQL에서 성능면에서 빠릅니다. 사용하지 마십시오.
runrig

8
하위 쿼리가 암시 적으로 특정 DB 기술에서 사용 가능한 경우 조인으로 실행되었다는 인상을 항상 받았습니다.
Kezzer

18
하위 쿼리는 꽤 큰 테이블과 조인 할 때 항상 빨라지는 것은 아닙니다. 선호되는 방법은 큰 테이블에서 하위 선택 (행 수 제한)을 수행 한 다음 조인하는 것입니다.
ovais.tariq

136
[표창장은 필요로했다] "요즘은 모두가 하위 쿼리를 사용"
Piskvor 건물 왼쪽

3
잠재적으로 관련되어 있음 (더 구체적이지만) : stackoverflow.com/questions/141278/subqueries-vs-joins/…
Leigh Brenecki

답변:


191

MySQL 매뉴얼 ( 13.2.10.11 서브 쿼리를 조인으로 재 작성 )에서 발췌 :

LEFT [OUTER] JOIN은 서버가 서버를 더 잘 최적화 할 수 있기 때문에 동등한 하위 쿼리보다 빠를 수 있습니다. 사실 MySQL 서버에만 국한된 것은 아닙니다.

따라서 하위 쿼리는보다 느릴 수 LEFT [OUTER] JOIN있지만 내 의견으로는 가독성이 약간 높습니다.


45
@ user1735921 IMO 의존성 ... 일반적으로 코드의 가독성은 매우 중요합니다. 나중에 코드를 관리하는 데 매우 중요하기 때문에 ... Donald Knuth의 유명한 진술을 기억해 봅시다. "조기 최적화는 모든 것의 근본입니다 프로그래밍에서 악 (또는 적어도 대부분) " . 그러나 자연스럽게 퍼포먼스가 가장 중요한 프로그래밍 영역이 있습니다. 이상적으로는 서로 조화를
이룰 수

30
더 복잡한 쿼리에서는 하위 쿼리보다 조인을 훨씬 쉽게 읽을 수 있습니다. 하위 쿼리는 내 머리 속에 국수 한 그릇으로 변합니다.
Zahra

6
@ user1735921 확실히, 특히 쿼리가 너무 복잡하여 잘못된 일을하고 하루를 고치는 데 평소와 같이 균형이 있습니다.
fabio.sussetto

6
@ user1735921 성능 향상이 미래에 필요한 유지 보수 시간을 늘릴 가치가있는 경우에만
Joshua Schlichting

3
내 의견 Joinsub query구문이 다르므로 비교할 수없는 가독성은 SQL 구문에 능숙하다면 가독성이 높습니다. 성능이 더 중요합니다.
Thavaprakash Swaminathan

840

하위 쿼리는 "A에서 팩트 가져 오기, B에서 팩트에 대한 조건부"형식의 문제를 해결하기위한 논리적으로 올바른 방법입니다. 이러한 경우 조인을 수행하는 것보다 하위 쿼리에 B를 사용하는 것이 더 논리적입니다. B와의 여러 경기로 인해 A에서 중복 사실을 얻는 것에 대해주의 할 필요가 없으므로 실제적인 의미에서 더 안전합니다.

그러나 실제로 대답은 대개 성능에 달려 있습니다. 일부 옵티마이 저는 조인과 하위 쿼리가 주어지면 레몬을 빨아 들이고, 다른 옵티마이 저는 다른 방법으로 레몬을 빨아 들이며, 이는 옵티 마이저, DBMS 버전 및 쿼리에 따라 다릅니다.

역사적으로 명시 적 조인은 일반적으로 승리하므로 조인이 더 좋아진다는 기존의 지혜가 있지만 옵티마이 저는 항상 더 좋아지고 있습니다. 따라서 논리적으로 일관된 방식으로 쿼리를 먼저 작성하고 성능 제약 조건이이를 보증하는 경우 재구성하는 것을 선호합니다.


105
좋은 대답입니다. 또한 개발자 (특히 아마추어 개발자)가 항상 SQL에 능숙하지는 않다고 덧붙입니다.
Álvaro González

4
+1 오랫동안이 이슈에 대한 논리적 설명을 찾고 있다면, 이것은 나에게 논리적으로 보이는 답일뿐입니다
Ali Umair

1
@Marcelo Cantos, "B와의 여러 경기로 인해 A로부터 중복 된 사실을 얻는 것에 대해주의 할 필요가 없기 때문에 실제적인 의미에서 더 안전합니다." 나는 이것이 매우 통찰력이 있지만 너무 추상적이라는 것을 알았습니다. 감사.
Jinghui Niu

6
@JinghuiNiu 비싼 물건을 구입 한 고객 : select custid from cust join bought using (custid) where price > 500. 고객이 값 비싼 품목을 여러 개 구입 한 경우 이중 상품이 제공됩니다. 이 문제를 해결하려면 select custid from cust where exists (select * from bought where custid = cust.custid and price > 500). select distinct …대신 사용할 수 있지만 최적화 프로그램이나 평가자에게 더 많은 작업이 필요한 경우가 많습니다.
Marcelo Cantos

1
@MatTheWhale 예, 나는 게으른 지나치게 단순화 된 답변을 사용했습니다. 실제 시나리오에서는 cust에서 custid보다 많은 열을 가져옵니다.
Marcelo Cantos

357

대부분의 경우 JOINs는 하위 쿼리보다 빠르며 하위 쿼리가 더 빠른 경우는 거의 없습니다.

에서 JOIN의 RDBMS는 쿼리에 대해 더 나은 실행 계획을 생성 할 수 있으며 모든 쿼리를 실행하고 처리 할 모든 데이터를로드 하위 쿼리는 달리, 데이터를 처리 할 수로드되어야 하는지를 예측하고 시간을 절약 할 수 있습니다 .

하위 쿼리에서 좋은 점은 JOINs 보다 읽기 쉽다는 것입니다 . 이것이 대부분의 새로운 SQL 사람들이 선호하는 이유입니다. 쉬운 방법입니다. 그러나 성능면에서 JOINS는 읽기가 쉽지 않지만 대부분의 경우 더 좋습니다.


14
따라서 대부분의 데이터베이스는 쿼리를 분석 할 때 하위 쿼리를 조인으로 변환하는 최적화 단계로 포함합니다.
Cine

16
이 답변은 묻는 질문에 비해 너무 단순화되었습니다. 당신이 말한대로 : 특정 하위 쿼리는 괜찮고 특정 하위 쿼리는 그렇지 않습니다. 대답은 실제로 두 가지를 구별하는 데 도움이되지 않습니다. (또한 '매우 드물게'는 실제로 데이터 / 앱에 따라 다릅니다).
미치게하다

21
문서 참조 또는 테스트 결과를 통해 요점을 증명할 수 있습니까?
Uğur Gümüşhan

62
상위 쿼리에 대한 역 참조를 포함하는 하위 쿼리, 특히 100,000보다 많은 행 수와 관련하여 매우 좋은 경험을했습니다. 메모리 사용과 스왑 파일에 대한 페이징 인 것 같습니다. 조인은 메모리에 맞지 않을 수 있고 스왑 파일로 페이징되어야하는 매우 많은 양의 데이터를 생성합니다. 이런 경우에는 작은 하위 선택과 같은 쿼리 시간이 select * from a where a.x = (select b.x form b where b.id = a.id)조인에 비해 매우 작습니다. 이것은 매우 특정한 문제이지만 경우에 따라 몇 시간에서 몇 분으로 안내합니다.
zuloo

13
필자는 Oracle에 경험이 있으며 필터링하거나 정렬하지 않으면 큰 테이블에서 하위 쿼리가 훨씬 좋습니다.
Amir Pashazadeh

130

EXPLAIN을 사용하여 데이터베이스가 데이터에서 쿼리를 실행하는 방법을 확인하십시오. 이 답변에는 거대한 "의존"이 있습니다 ...

PostgreSQL은 하위 쿼리가 하위 쿼리보다 빠르다고 생각 될 때 하위 쿼리를 조인에 조인하거나 하위 쿼리에 조인 할 수 있습니다. 그것은 모두 데이터, 인덱스, 상관 관계, 데이터 양, 쿼리 등에 따라 다릅니다.


6
이것이 바로 postgresql이 매우 유용하고 유용한 이유입니다. postgresql은 목표가 무엇인지 이해하고 더 나은 생각에 따라 쿼리를 수정하며 postgresql은 데이터를 보는 방법을 잘 알고 있습니다.
WojonsTech

휴. 나는 나를 위해 수많은 쿼리를 다시 작성할 필요가 없다고 생각합니다! 승리를위한 postgresql.
Daniel Shin

77

2010 년에 나는이 질문의 저자에 합류했고에 대해 강력하게 투표했을 JOIN것이지만 훨씬 더 많은 경험 (특히 MySQL에서)으로 다음과 같이 말할 수 있습니다. 여기에 여러 답변을 읽었습니다. 일부 언급 된 하위 쿼리는 더 빠르지 만 설명이 부족합니다. 나는이 (매우) 늦게 답변을 제공 할 수 있기를 바랍니다.

우선, 가장 중요한 말을하겠습니다 : 하위 쿼리에는 여러 가지 형태가 있습니다

그리고 두 번째 중요한 진술 : 크기 문제

하위 쿼리를 사용 하는 경우 DB 서버가 하위 쿼리를 실행하는 방법을 알고 있어야 합니다. 특히 하위 쿼리가 한 번 또는 모든 행에 대해 평가되는 경우! 반면에 최신 DB-Server는 많은 것을 최적화 할 수 있습니다. 하위 쿼리는 쿼리 최적화에 도움이되지만 최신 버전의 DB-Server는 최적화를 더 이상 사용하지 않을 수 있습니다.

선택 필드의 하위 쿼리

SELECT moo, (SELECT roger FROM wilco WHERE moo = me) AS bar FROM foo

의 모든 결과 행에 대해 하위 쿼리가 실행됩니다 foo.
가능하면 이것을 피하십시오. 거대한 데이터 세트에서 쿼리 속도가 크게 느려질 수 있습니다. 그러나 하위 쿼리에 대한 참조가 없으면 fooDB 서버에서 정적 컨텐츠로 최적화 할 수 있으며 한 번만 평가할 수 있습니다.

Where-statement의 하위 쿼리

SELECT moo FROM foo WHERE bar = (SELECT roger FROM wilco WHERE moo = me)

운이 좋으면 DB는이를 내부적으로로 최적화합니다 JOIN. 그렇지 않은 경우 쿼리는 fooselect-type과 같은 결과뿐만 아니라의 모든 행에 대해 하위 쿼리를 실행하기 때문에 대규모 데이터 세트에서 쿼리 속도가 매우 느려집니다 .

Join 문에서 하위 쿼리

SELECT moo, bar 
  FROM foo 
    LEFT JOIN (
      SELECT MIN(bar), me FROM wilco GROUP BY me
    ) ON moo = me

이것은 흥미 롭다. 우리는 JOIN하위 쿼리와 결합 합니다. 그리고 여기서 우리는 하위 쿼리의 진정한 강점을 얻습니다. 행의 수백만 데이터 집합 상상 wilco만 몇 별개을 me. 거대한 테이블에 대해 조인하는 대신 이제 더 작은 임시 테이블에 조인 할 수 있습니다. 데이터베이스 크기에 따라 쿼리 속도가 훨씬 빨라질 수 있습니다. CREATE TEMPORARY TABLE ...and와 같은 효과를 낼 수 INSERT INTO ... SELECT ...있으므로 매우 복잡한 쿼리에서 가독성이 향상되지만 반복 가능한 읽기 격리 수준에서 데이터 집합을 잠글 수 있습니다.

중첩 된 하위 쿼리

SELECT moo, bar
  FROM (
    SELECT moo, CONCAT(roger, wilco) AS bar
      FROM foo
      GROUP BY moo
      HAVING bar LIKE 'SpaceQ%'
  ) AS temp_foo
  ORDER BY bar

하위 쿼리를 여러 수준으로 중첩 할 수 있습니다. 결과를 그룹화하거나 정렬해야하는 경우 거대한 데이터 세트에 도움이 될 수 있습니다. 일반적으로 DB-Server는이를 위해 임시 테이블을 생성하지만 때로는 전체 테이블에서 정렬 할 필요가없고 결과 집합에서만 정렬 할 필요가 있습니다. 이는 테이블 크기에 따라 훨씬 더 나은 성능을 제공 할 수 있습니다.

결론

하위 쿼리는 a를 대체 JOIN하지 않으므로 이와 같이 사용해서는 안됩니다 (가능한 경우에도). 겸손한 의견으로는 하위 쿼리를 올바르게 사용하는 것이 빠른 대체로 사용됩니다 CREATE TEMPORARY TABLE .... 좋은 하위 쿼리는의 ON문 에서 수행 할 수없는 방식으로 데이터 집합을 줄 JOIN입니다. 하위 쿼리에 키워드 중 하나가 GROUP BY있거나 DISTINCT선택 필드 나 where 문에없는 경우 성능이 크게 향상 될 수 있습니다.


3
들면 Sub-queries in the Join-statement(1) 생성은 매우 긴 시간이 걸릴 수 부 조회 테이블 자체로부터 산출했다. (2) 결과 파생 테이블이 인덱싱되지 않습니다. 이 두 가지만으로도 SQL 속도가 크게 느려질 수 있습니다.
jxc

@jxc MySQL에 대해서만 말할 수 있습니다. (1) 조인과 유사한 임시 테이블이 있습니다. 시간은 데이터 양에 따라 다릅니다. 하위 쿼리로 데이터를 줄일 수 없으면 조인을 사용하십시오. (2) 맞습니다. 임시 테이블의 데이터를 줄일 수있는 요소에 따라 다릅니다. 실제 사례가 있었는데, 수백만에서 수백에 이르는 조인 크기를 줄이고 전체 쿼리 사용으로 쿼리 시간을 하위 쿼리를 사용하여 몇 초에서 1/4 초로 줄일 수 있습니다.
Trendfischer 2014 년

IMO : (1) 이러한 임시 테이블 (파생 된 테이블)은 구체화되지 않으므로 SQL을 실행할 때마다 임시 테이블을 다시 작성해야하므로 비용이 많이 들고 실제 병목 현상이 발생할 수 있습니다 (예 : 수백만 명씩 그룹 실행) (2) 임시 테이블의 크기를 10레코드로 줄일 수 있더라도 인덱스가 없기 때문에 다른 테이블에 참여할 때 임시 테이블이없는 것보다 9 배 더 많은 데이터 레코드를 쿼리 할 가능성이 있습니다. BTW 내 db (MySQL) 에서이 문제가 발생했습니다. 제 경우에는 하위 쿼리를 사용하는 SELECT list것이 훨씬 빠릅니다.
jxc

@jxc 하위 쿼리를 사용하는 것이 최적이 아닌 예제가 많이 있다는 것을 의심하지 않습니다. 모범 사례로서 EXPLAIN최적화하기 전에 쿼리에 사용해야합니다 . 이전 set profiling=1테이블을 사용하면 임시 테이블에 병목 현상이 있는지 쉽게 알 수 있습니다. 그리고 인덱스조차도 처리 시간이 필요하지만 B- 트리는 레코드 쿼리를 최적화하지만 10 레코드 테이블은 수백만 레코드의 인덱스보다 훨씬 빠를 수 있습니다. 그러나 필드 크기 및 유형과 같은 여러 요인에 따라 다릅니다.
Trendfischer 2014 년

1
나는 당신의 설명을 정말로 즐겼습니다. 감사합니다.
unpairestgood

43

우선, 두 가지를 먼저 비교하려면 쿼리를 하위 쿼리와 구별하여 다음을 수행해야합니다.

  1. 항상 조인으로 작성된 해당하는 동등한 쿼리가있는 서브 쿼리 클래스
  2. 조인을 사용하여 다시 작성할 수없는 하위 쿼리 클래스

첫 번째 쿼리 클래스 의 경우 우수한 RDBMS는 조인 및 하위 쿼리를 동등한 것으로 간주하고 동일한 쿼리 계획을 생성합니다.

요즘 mysql도 그렇게합니다.

그럼에도 불구하고 때로는 그렇지 않지만 이것이 조인이 항상 이길 것이라는 것을 의미하지는 않습니다-mysql에서 하위 쿼리를 사용할 때 성능이 향상되었습니다. (예를 들어 mysql 플래너가 비용을 정확하게 추정하지 못하게하는 것이 있고 플래너가 join-variant 및 subquery-variant를 동일하게 보지 못하면 하위 쿼리는 특정 경로를 강제하여 조인보다 성능이 뛰어납니다).

결론은 어느 것이 더 잘 수행되는지 확인하려면 조인 및 하위 쿼리 변형에 대해 쿼리를 테스트해야한다는 것입니다.

두 번째 클래스 의 경우 조인을 사용하여 해당 쿼리를 다시 작성할 수 없으므로 하위 쿼리는 필요한 작업을 수행하는 자연적인 방법이므로 차별하지 않아야합니다.


1
조인으로 변환 할 수없는 하위 쿼리를 사용하여 작성된 쿼리의 예를 제공 할 수 있습니까 (호출 할 때 두 번째 클래스)?
Zahra

24

인용 된 답변에서 강조되지 않은 것은 특정 (사용) 사례에서 발생할 수있는 중복 및 문제가있는 결과입니다.

(Marcelo Cantos가 언급했지만)

Stanford의 Lagunita 코스에서 SQL에 대한 예제를 인용하겠습니다.

학생 테이블

+------+--------+------+--------+
| sID  | sName  | GPA  | sizeHS |
+------+--------+------+--------+
|  123 | Amy    |  3.9 |   1000 |
|  234 | Bob    |  3.6 |   1500 |
|  345 | Craig  |  3.5 |    500 |
|  456 | Doris  |  3.9 |   1000 |
|  567 | Edward |  2.9 |   2000 |
|  678 | Fay    |  3.8 |    200 |
|  789 | Gary   |  3.4 |    800 |
|  987 | Helen  |  3.7 |    800 |
|  876 | Irene  |  3.9 |    400 |
|  765 | Jay    |  2.9 |   1500 |
|  654 | Amy    |  3.9 |   1000 |
|  543 | Craig  |  3.4 |   2000 |
+------+--------+------+--------+

테이블 적용

(특정 대학 및 전공에 적용)

+------+----------+----------------+----------+
| sID  | cName    | major          | decision |
+------+----------+----------------+----------+
|  123 | Stanford | CS             | Y        |
|  123 | Stanford | EE             | N        |
|  123 | Berkeley | CS             | Y        |
|  123 | Cornell  | EE             | Y        |
|  234 | Berkeley | biology        | N        |
|  345 | MIT      | bioengineering | Y        |
|  345 | Cornell  | bioengineering | N        |
|  345 | Cornell  | CS             | Y        |
|  345 | Cornell  | EE             | N        |
|  678 | Stanford | history        | Y        |
|  987 | Stanford | CS             | Y        |
|  987 | Berkeley | CS             | Y        |
|  876 | Stanford | CS             | N        |
|  876 | MIT      | biology        | Y        |
|  876 | MIT      | marine biology | N        |
|  765 | Stanford | history        | Y        |
|  765 | Cornell  | history        | N        |
|  765 | Cornell  | psychology     | Y        |
|  543 | MIT      | CS             | N        |
+------+----------+----------------+----------+

CS대학에 관계없이 전공 에 지원 한 학생들의 GPA 점수를 찾아 봅시다.

하위 쿼리 사용 :

select GPA from Student where sID in (select sID from Apply where major = 'CS');

+------+
| GPA  |
+------+
|  3.9 |
|  3.5 |
|  3.7 |
|  3.9 |
|  3.4 |
+------+

이 결과 집합의 평균값은 다음과 같습니다.

select avg(GPA) from Student where sID in (select sID from Apply where major = 'CS');

+--------------------+
| avg(GPA)           |
+--------------------+
| 3.6800000000000006 |
+--------------------+

조인 사용 :

select GPA from Student, Apply where Student.sID = Apply.sID and Apply.major = 'CS';

+------+
| GPA  |
+------+
|  3.9 |
|  3.9 |
|  3.5 |
|  3.7 |
|  3.7 |
|  3.9 |
|  3.4 |
+------+

이 결과 집합의 평균 값 :

select avg(GPA) from Student, Apply where Student.sID = Apply.sID and Apply.major = 'CS';

+-------------------+
| avg(GPA)          |
+-------------------+
| 3.714285714285714 |
+-------------------+

두 번째 시도는 평균값을 계산하기 위해 중복을 계산한다는 점에서 유스 케이스에서 잘못된 결과를 산출한다는 것이 분명합니다. 또한 distinctjoin-based 문 을 사용한 경우 에도 세 번의 점수 발생 중 하나를 잘못 유지한다는 점에서 문제가 해결 되지않습니다3.9 . 올바른 케이스에 대한 계정입니다 TWO (2) 의 발생 3.9우리가 실제로 가지고 주어진 점수 TWO (2) 우리의 쿼리 기준을 준수하는지 그 점수로 학생을.

경우에 따라 성능 문제 외에도 하위 쿼리가 가장 안전한 방법 인 것 같습니다.


나는 당신이 여기에서 하위 쿼리를 사용할 수 없다고 생각합니다. 논리적으로 사용할 수는 없지만 기술적 구현으로 인해 잘못된 답변을 제공하는 경우는 아닙니다. CS에 속하지 않은 학생은 IN 점수 목록에있는 3.9 점을받을 수 있기 때문에 하위 쿼리를 사용할 수없는 경우입니다. 하위 쿼리가 실행되면 CS의 컨텍스트가 손실됩니다. 논리적으로 원하는 것은 아닙니다. 따라서 이것은 어느 쪽이든 사용할 수있는 좋은 예가 아닙니다. 운좋게도 다른 데이터 세트에 대해 올바른 결과를 제공하더라도 하위 쿼리의 사용은이 유스 케이스에 대해 개념적으로 / 논리적으로 잘못되었습니다.
Saurabh Patil

22

SQL Server 용 MSDN 설명서에 따르면

하위 쿼리를 포함하는 많은 Transact-SQL 문을 조인으로 구성 할 수 있습니다. 다른 질문은 하위 쿼리로만 제기 할 수 있습니다. Transact-SQL에서는 일반적으로 하위 쿼리를 포함하는 문과 의미가 동등한 버전의 성능 차이가 없습니다. 그러나 존재 여부를 확인해야하는 경우 조인의 성능이 향상됩니다. 그렇지 않으면 외부 쿼리의 각 결과에 대해 중첩 쿼리를 처리하여 중복을 제거해야합니다. 이러한 경우 조인 방식으로 더 나은 결과를 얻을 수 있습니다.

그래서 당신이 같은 것이 필요하다면

select * from t1 where exists select * from t2 where t2.parent=t1.id

대신 join을 사용하십시오. 다른 경우에는 아무런 차이가 없습니다.

나는 말합니다 : 하위 쿼리에 대한 함수 를 생성 하면 클러스터의 문제가 제거되고 하위 쿼리에 대한 추가 논리를 구현할 수 있습니다. 따라서 가능하면 하위 쿼리를위한 함수를 만드는 것이 좋습니다.

코드의 혼란은 큰 문제이며 업계는 수십 년 동안이를 피하기 위해 노력해 왔습니다.


9
하위 쿼리를 함수로 바꾸는 것은 일부 RDBMS (예 : Oracle)에서 성능 측면에서 매우 좋지 않은 아이디어이므로 가능한 한 함수 대신 하위 쿼리 / 조인을 사용하는 것이 좋습니다.
Frank Schmitt

3
@FrankSchmitt는 참고로 당신의 주장을지지하십시오.
Uğur Gümüşhan

2
존재 여부를 확인하더라도 조인 대신 하위 쿼리를 사용해야하는 경우도 있습니다 NOT EXISTS. 성능 , 실패 안전 (널 (null) 열의 경우) 및 가독성과 같은 여러 가지 이유로 A가 NOT EXISTS이깁니다 LEFT OUTER JOIN. sqlperformance.com/2012/12/t-sql-queries/left-anti-semi-join
Tim Schmelter

16

오래된 Mambo CMS의 매우 큰 데이터베이스에서 실행하십시오.

SELECT id, alias
FROM
  mos_categories
WHERE
  id IN (
    SELECT
      DISTINCT catid
    FROM mos_content
  );

0 초

SELECT
  DISTINCT mos_content.catid,
  mos_categories.alias
FROM
  mos_content, mos_categories
WHERE
  mos_content.catid = mos_categories.id;

~ 3 초

EXPLAIN은 정확히 같은 수의 행을 검사하지만 1 초는 3 초가 걸리고 1은 거의 순간에 가까운 것으로 나타났습니다. 이야기의 교훈? 성능이 중요하지 않은 경우 (필요하지 않은 경우) 여러 가지 방법으로 시도하여 어느 것이 가장 빠른지 확인하십시오.

과...

SELECT
  DISTINCT mos_categories.id,
  mos_categories.alias
FROM
  mos_content, mos_categories
WHERE
  mos_content.catid = mos_categories.id;

0 초

다시, 동일한 결과, 동일한 수의 행이 검사되었습니다. 내 생각에 DISTINCT mos_content.catid가 DISTINCT mos_categories.id보다 알아내는 데 훨씬 오래 걸립니다.


1
마지막 줄에서 지적하고자하는 것에 대해 더 알고 싶습니다. "DISTINCT mos_content.catid가 DISTINCT mos_categories.id가하는 것보다 알아내는 데 시간이 오래 걸린다고 생각합니다." . ID는 이름 만 지정 id하고 이름은 지정해서는 안된다고 말하고 catid있습니까? 내 DB 액세스를 최적화하려고하면 학습이 도움이 될 수 있습니다.
bool.dev

2
이 경우 SQL IN을 사용하는 것은 나쁜 습관이며 아무것도 증명하지 못합니다.
우 그르 Gümüşhan

15

두 경우와 같이 내 관찰에 따르면 테이블에 100,000 개 미만의 레코드가 있으면 조인이 빠르게 작동합니다.

그러나 테이블에 100,000 개가 넘는 레코드가있는 경우 하위 쿼리가 가장 좋습니다.

아래 쿼리에서 생성 한 500,000 개의 레코드가있는 하나의 테이블이 있으며 결과 시간은

SELECT * 
FROM crv.workorder_details wd 
inner join  crv.workorder wr on wr.workorder_id = wd.workorder_id;

결과 : 13.3 초

select * 
from crv.workorder_details 
where workorder_id in (select workorder_id from crv.workorder)

결과 : 1.65 초


동의합니다. 때로는 쿼리를 깨는 것도 효과적입니다. 레코드가 백만 개일 때 조인은 영원히 사용되므로 사용하지 않으려는 것입니다. 오히려 코드에서 처리하고 코드에서 매핑하는 것이 좋습니다.
user1735921

1
조인이 충분히 빠르게 작동하지 않아 인덱스가 누락되었을 수 있습니다. 쿼리 분석기는 실제 성능을 비교하는 데 도움이 될 수 있습니다.
digital.aaron

나는 Ajay Gajera에 동의하며, 나는 이것을 직접 보았다.
user1735921

14
다른 결과를 반환하는 두 쿼리의 성능을 비교하는 것이 어떤 의미가 있습니까?
Paul Spiegel

예, 다른 쿼리이지만 동일한 결과를 반환합니다.
king neo

12

서브 쿼리는 일반적으로 단일 행을 원자 값으로 리턴하는 데 사용되지만 IN 키워드를 사용하여 여러 행과 값을 비교하는 데 사용될 수 있습니다. 대상 목록, WHERE 절 등을 포함하여 SQL 문의 거의 모든 의미있는 지점에서 허용됩니다. 간단한 하위 쿼리를 검색 조건으로 사용할 수 있습니다. 예를 들어, 한 쌍의 테이블 사이 :

   SELECT title FROM books WHERE author_id = (SELECT id FROM authors WHERE last_name = 'Bar' AND first_name = 'Foo');

하위 쿼리 결과에 일반 값 연산자를 사용하려면 하나의 필드 만 반환해야합니다. 다른 값 집합 내에 단일 값이 있는지 확인하려면 IN을 사용하십시오.

   SELECT title FROM books WHERE author_id IN (SELECT id FROM authors WHERE last_name ~ '^[A-E]');

이것은 조인 조건이 테이블 B 등에서 일치하는 레코드를 찾지 못하는 경우에도 테이블 A 및 B에서 물건을 조인하려는 LEFT-JOIN과는 분명히 다릅니다.

속도가 걱정된다면 데이터베이스를 확인하고 좋은 쿼리를 작성하고 성능에 큰 차이가 있는지 확인해야합니다.


11

MySQL 버전 : 5.5.28-0ubuntu0.12.04.2-log

또한 JOIN이 항상 MySQL의 하위 쿼리보다 낫다는 인상을 받았지만 EXPLAIN은 판단을 내리는 더 좋은 방법입니다. 다음은 JOIN보다 하위 쿼리가 더 잘 작동하는 예입니다.

다음은 3 개의 하위 쿼리가있는 쿼리입니다.

EXPLAIN SELECT vrl.list_id,vrl.ontology_id,vrl.position,l.name AS list_name, vrlih.position AS previous_position, vrl.moved_date 
FROM `vote-ranked-listory` vrl 
INNER JOIN lists l ON l.list_id = vrl.list_id 
INNER JOIN `vote-ranked-list-item-history` vrlih ON vrl.list_id = vrlih.list_id AND vrl.ontology_id=vrlih.ontology_id AND vrlih.type='PREVIOUS_POSITION' 
INNER JOIN list_burial_state lbs ON lbs.list_id = vrl.list_id AND lbs.burial_score < 0.5 
WHERE vrl.position <= 15 AND l.status='ACTIVE' AND l.is_public=1 AND vrl.ontology_id < 1000000000 
 AND (SELECT list_id FROM list_tag WHERE list_id=l.list_id AND tag_id=43) IS NULL 
 AND (SELECT list_id FROM list_tag WHERE list_id=l.list_id AND tag_id=55) IS NULL 
 AND (SELECT list_id FROM list_tag WHERE list_id=l.list_id AND tag_id=246403) IS NOT NULL 
ORDER BY vrl.moved_date DESC LIMIT 200;

설명 표시 :

+----+--------------------+----------+--------+-----------------------------------------------------+--------------+---------+-------------------------------------------------+------+--------------------------+
| id | select_type        | table    | type   | possible_keys                                       | key          | key_len | ref                                             | rows | Extra                    |
+----+--------------------+----------+--------+-----------------------------------------------------+--------------+---------+-------------------------------------------------+------+--------------------------+
|  1 | PRIMARY            | vrl      | index  | PRIMARY                                             | moved_date   | 8       | NULL                                            |  200 | Using where              |
|  1 | PRIMARY            | l        | eq_ref | PRIMARY,status,ispublic,idx_lookup,is_public_status | PRIMARY      | 4       | ranker.vrl.list_id                              |    1 | Using where              |
|  1 | PRIMARY            | vrlih    | eq_ref | PRIMARY                                             | PRIMARY      | 9       | ranker.vrl.list_id,ranker.vrl.ontology_id,const |    1 | Using where              |
|  1 | PRIMARY            | lbs      | eq_ref | PRIMARY,idx_list_burial_state,burial_score          | PRIMARY      | 4       | ranker.vrl.list_id                              |    1 | Using where              |
|  4 | DEPENDENT SUBQUERY | list_tag | ref    | list_tag_key,list_id,tag_id                         | list_tag_key | 9       | ranker.l.list_id,const                          |    1 | Using where; Using index |
|  3 | DEPENDENT SUBQUERY | list_tag | ref    | list_tag_key,list_id,tag_id                         | list_tag_key | 9       | ranker.l.list_id,const                          |    1 | Using where; Using index |
|  2 | DEPENDENT SUBQUERY | list_tag | ref    | list_tag_key,list_id,tag_id                         | list_tag_key | 9       | ranker.l.list_id,const                          |    1 | Using where; Using index |
+----+--------------------+----------+--------+-----------------------------------------------------+--------------+---------+-------------------------------------------------+------+--------------------------+

JOIN과 동일한 쿼리는 다음과 같습니다.

EXPLAIN SELECT vrl.list_id,vrl.ontology_id,vrl.position,l.name AS list_name, vrlih.position AS previous_position, vrl.moved_date 
FROM `vote-ranked-listory` vrl 
INNER JOIN lists l ON l.list_id = vrl.list_id 
INNER JOIN `vote-ranked-list-item-history` vrlih ON vrl.list_id = vrlih.list_id AND vrl.ontology_id=vrlih.ontology_id AND vrlih.type='PREVIOUS_POSITION' 
INNER JOIN list_burial_state lbs ON lbs.list_id = vrl.list_id AND lbs.burial_score < 0.5 
LEFT JOIN list_tag lt1 ON lt1.list_id = vrl.list_id AND lt1.tag_id = 43 
LEFT JOIN list_tag lt2 ON lt2.list_id = vrl.list_id AND lt2.tag_id = 55 
INNER JOIN list_tag lt3 ON lt3.list_id = vrl.list_id AND lt3.tag_id = 246403 
WHERE vrl.position <= 15 AND l.status='ACTIVE' AND l.is_public=1 AND vrl.ontology_id < 1000000000 
AND lt1.list_id IS NULL AND lt2.tag_id IS NULL 
ORDER BY vrl.moved_date DESC LIMIT 200;

출력은 다음과 같습니다.

+----+-------------+-------+--------+-----------------------------------------------------+--------------+---------+---------------------------------------------+------+----------------------------------------------+
| id | select_type | table | type   | possible_keys                                       | key          | key_len | ref                                         | rows | Extra                                        |
+----+-------------+-------+--------+-----------------------------------------------------+--------------+---------+---------------------------------------------+------+----------------------------------------------+
|  1 | SIMPLE      | lt3   | ref    | list_tag_key,list_id,tag_id                         | tag_id       | 5       | const                                       | 2386 | Using where; Using temporary; Using filesort |
|  1 | SIMPLE      | l     | eq_ref | PRIMARY,status,ispublic,idx_lookup,is_public_status | PRIMARY      | 4       | ranker.lt3.list_id                          |    1 | Using where                                  |
|  1 | SIMPLE      | vrlih | ref    | PRIMARY                                             | PRIMARY      | 4       | ranker.lt3.list_id                          |  103 | Using where                                  |
|  1 | SIMPLE      | vrl   | ref    | PRIMARY                                             | PRIMARY      | 8       | ranker.lt3.list_id,ranker.vrlih.ontology_id |   65 | Using where                                  |
|  1 | SIMPLE      | lt1   | ref    | list_tag_key,list_id,tag_id                         | list_tag_key | 9       | ranker.lt3.list_id,const                    |    1 | Using where; Using index; Not exists         |
|  1 | SIMPLE      | lbs   | eq_ref | PRIMARY,idx_list_burial_state,burial_score          | PRIMARY      | 4       | ranker.vrl.list_id                          |    1 | Using where                                  |
|  1 | SIMPLE      | lt2   | ref    | list_tag_key,list_id,tag_id                         | list_tag_key | 9       | ranker.lt3.list_id,const                    |    1 | Using where; Using index                     |
+----+-------------+-------+--------+-----------------------------------------------------+--------------+---------+---------------------------------------------+------+----------------------------------------------+

rows열을 비교 하면 차이점을 알 수 있으며 JOIN을 사용한 쿼리는을 사용 Using temporary; Using filesort합니다.

물론 두 쿼리를 모두 실행하면 첫 번째 쿼리는 0.02 초 후에 수행되고 두 번째 쿼리는 1 분 후에도 완료되지 않으므로 EXPLAIN은 이러한 쿼리를 올바르게 설명했습니다.

list_tag테이블 에 INNER JOIN이없는 경우, 즉 제거하는 경우

AND (SELECT list_id FROM list_tag WHERE list_id=l.list_id AND tag_id=246403) IS NOT NULL  

첫 번째 쿼리에서

INNER JOIN list_tag lt3 ON lt3.list_id = vrl.list_id AND lt3.tag_id = 246403

두 번째 쿼리에서 EXPLAIN은 두 쿼리에 대해 동일한 수의 행을 반환하고 두 쿼리 모두 똑같이 빠르게 실행됩니다.


나는 비슷한 상황이지만 당신보다 더 많은 조인으로 한 번 설명하려고 시도 할 것입니다
pahnin

오라클이나 PostgreSQL을 나는 시도했을 : 존재하지 (list_tag에서 1을 SELECT WHERE list_id로 = l.list_id AND (43, 55, 246403)의 tag_id)
데이비드 드리지

11

서브 쿼리는 즉시 집계 함수를 계산할 수 있습니다. 예 : 책의 최소 가격을 찾고이 가격으로 판매 된 모든 책을 얻으십시오. 1) 서브 쿼리 사용 :

SELECT titles, price
FROM Books, Orders
WHERE price = 
(SELECT MIN(price)
 FROM Orders) AND (Books.ID=Orders.ID);

2) JOIN 사용

SELECT MIN(price)
     FROM Orders;
-----------------
2.99

SELECT titles, price
FROM Books b
INNER JOIN  Orders o
ON b.ID = o.ID
WHERE o.price = 2.99;

다른 경우 : GROUP BY다른 테이블이있는 여러 개 : stackoverflow.com/questions/11415284/… 하위 쿼리는 엄격하게 더 일반적 인 것으로 보입니다. MySQL man : dev.mysql.com/doc/refman/5.7/en/optimizing-subqueries.html |을 참조하십시오. dev.mysql.com/doc/refman/5.7/en/rewriting-subqueries.html
치로 틸리冠状病毒审查六四事件法轮功

6
-1 하위 쿼리를 사용하고 두 예제 모두에서 결합 할 때 잘못된 결과입니다. 데이터베이스가 정확히 동일한 작업을 수행하므로 하위 쿼리를 두 번째 쿼리로 가져 와서 최저 주문 가격을 결정하는 것은 효과가 없습니다. 또한 하위 쿼리를 사용하여 조인을 다시 작성하지 않습니다. 두 쿼리 모두 조인을 사용합니다. 당신이 있는 하위 쿼리가 집계 함수를 허용하지만,이 예제는 그 사실을 증명하지 않는 올바른.
David Harkness

나는 David에게 동의하며, 당신은 최소 가격을 얻기 위해 group by를 사용할 수 있습니다.
user1735921

9
  • 일반적인 규칙은 대부분의 경우 조인 이 더 빠르다는 것입니다 (99 %).
  • 데이터 테이블이 많을수록 하위 쿼리 가 느려집니다.
  • 데이터 테이블이 적을수록 서브 쿼리조인 과 동일한 속도를 갖습니다 .
  • 서브 쿼리는 , 간단하게 이해하기 쉽게하고 쉽게 읽을 수 있습니다.
  • 웹 및 응용 프로그램 프레임 워크와 그들의 "ORM"와 "활동 기록"들과 질의 생성의 대부분의 하위 쿼리를 함께하기 때문에, 서브 쿼리 분할 책임 쉽게 코드를 유지, 등
  • 작은 웹 사이트 또는 앱의 경우 하위 쿼리는 OK, 그러나 더 큰 웹 사이트와 앱을 자주하기 위해 생성 쿼리를 다시 작성해야합니다 가입 쿼리를 쿼리가 많은 특별한 사용하는 경우 하위 쿼리를 쿼리에서.

어떤 사람들은 "일부 RDBMS는 다시 쓸 수 있다고 하위 쿼리를 A는에 가입 하거나이 조인 A와 하위 쿼리 . 그것은 한 빨리 다른 것보다 생각하는 경우"하지만,이 문으로 복잡한 쿼리하지 확실히, 간단한 경우에 적용 하위 쿼리 하는 실제 원인 성능 문제.


> 그러나이 문장은 간단한 경우에 적용됩니다. RDBMS에서 "JOIN"으로 다시 쓸 수있는 간단한 경우이거나 하위 쿼리가 여기에 적합한 복잡한 경우라는 것을 알고 있습니다. :-) ORM에 대한 좋은 지적. 이것이 가장 큰 영향을 미친다고 생각합니다.
pilat

4

차이점은 두 번째 조인 테이블에 기본 테이블보다 훨씬 많은 데이터가있는 경우에만 나타납니다. 나는 아래와 같은 경험을했다 ...

우리는 10 만 항목의 사용자 테이블과 약 3 십만 항목의 회원 데이터 (우정)를 가졌습니다. 친구와 그들의 데이터를 가져 오기 위해 조인 진술 이었지만 크게 지연되었습니다. 그러나 멤버십 테이블에 적은 양의 데이터 만 있으면 잘 작동했습니다. 하위 쿼리를 사용하도록 변경하면 정상적으로 작동합니다.

그러나 그 동안 조인 쿼리는 기본 테이블보다 적은 수의 항목을 가진 다른 테이블과 작업하고 있습니다.

따라서 조인 및 하위 쿼리 문이 제대로 작동하고 데이터와 상황에 따라 다릅니다.


3

요즘 많은 db가 하위 쿼리 및 조인을 최적화 할 수 있습니다. 따라서, Explain을 사용하여 쿼리를 검사하고 어느 것이 더 빠른지 확인해야합니다. 성능에 큰 차이가 없다면 단순하고 이해하기 쉬운 하위 쿼리를 사용하는 것이 좋습니다.


1

나는 단지 같은 문제에 대해 생각하고 있지만 FROM 부분에서 하위 쿼리를 사용하고 있습니다. 큰 테이블의 연결 및 쿼리가 필요합니다. "슬레이브"테이블에는 2 천 8 백만 개의 레코드가 있지만 결과는 128 개의 작은 결과 빅 데이터입니다! MAX () 함수를 사용하고 있습니다.

먼저 올바른 방법이라고 생각하기 때문에 LEFT JOIN을 사용하고 있습니다. mysql은 최적화 할 수 있습니다. 테스트를 위해 두 번째로 JOIN에 대해 하위 선택을 다시 작성합니다.

왼쪽 조인 런타임 : 1.12 초 하위 선택 런타임 : 0.06 초

조인보다 하위 선택이 18 배 빠릅니다! 초 키토 전진에서. subselect는 끔찍하지만 결과는 ...


-1

join을 사용하여 쿼리 속도를 높이려면

"내부 조인 / 가입"의 경우 조건을 대신 "ON"상태로 사용하지 마십시오. 예 :

     select id,name from table1 a  
   join table2 b on a.name=b.name
   where id='123'

 Try,

    select id,name from table1 a  
   join table2 b on a.name=b.name and a.id='123'

"왼쪽 / 오른쪽 조인"의 경우 "ON"조건에서는 사용하지 마십시오. 왼쪽 / 오른쪽 조인을 사용하면 하나의 테이블에 대한 모든 행을 가져 오므로 "온"에서 사용하지 않습니다. "Where"조건을 사용하십시오


이는 SQL 서버와 쿼리의 복잡성에 따라 다릅니다. 많은 SQL 구현은 최상의 성능을 위해 이와 같은 간단한 쿼리를 최적화합니다. 아마도이 동작이 응답을 향상시키는 서버 이름과 버전의 예를 제공합니까?
Trendfischer
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.