(큰?) 수의 값에 대한 MySQL "IN"연산자 성능


94

최근에 Redis와 MongoDB를 실험 해 왔으며 MongoDB 또는 Redis 에 ID 배열을 저장하는 경우가 종종있는 것 같습니다 . MySQL IN 연산자 에 대해 묻고 있으므로이 질문에 대해 Redis를 계속 사용하겠습니다 .

IN 연산자 내부에 많은 수 (300-3000)의 ID 를 나열하는 것이 얼마나 성능이 좋은지 궁금합니다 .

SELECT id, name, price
FROM products
WHERE id IN (1, 2, 3, 4, ...... 3000)

특정 범주제품 을 가져 오기 위해 일반적으로 함께 조인 할 수 있는 제품범주 테이블 처럼 간단한 것을 상상해보십시오 . 위의 예에서 Redis ( ) 의 지정된 카테고리 아래에서 ID가 4 인 카테고리의 모든 제품 ID를 반환 하고 연산자 내부의 위 쿼리에 배치하는 것을 볼 수 있습니다.category:4:product_idsSELECTIN

얼마나 성능이 좋은가요?

이것이 "의존적"상황입니까? 또는 구체적인 "허용되지 않음"또는 "빠름"또는 "느림"이 LIMIT 25있습니까? 아니면을 추가해야합니까 , 아니면 도움이되지 않습니까?

SELECT id, name, price
FROM products
WHERE id IN (1, 2, 3, 4, ...... 3000)
LIMIT 25

아니면 Redis에서 반환 한 제품 ID 배열을 잘라서 25 개로 제한하고 쿼리에 3000 개가 아닌 25 개 ID 만 추가하고 쿼리 LIMIT내부에서 25 개로 지정해야 합니까?

SELECT id, name, price
FROM products
WHERE id IN (1, 2, 3, 4, ...... 25)

모든 제안 / 피드백은 대단히 감사합니다!


정확히 무엇을 요구 하시는지 잘 모르겠습니까? "id IN (1,2,3, ... 3000))"이있는 하나의 쿼리는 "id = value"인 3000 개의 쿼리보다 빠릅니다. 그러나 "category = 4"를 사용하는 조인은 위의 두 가지 모두보다 빠릅니다.
Ronnis 2010

맞습니다.하지만 한 제품이 여러 카테고리에 속할 수 있기 때문에 "카테고리 = 4"는 할 수 없습니다. Redis를 사용하여 특정 카테고리에 속하는 제품의 모든 ID를 저장 한 다음 쿼리합니다. 진짜 질문은 id IN (1,2,3 ... 3000)JOIN 테이블과 비교 하여 성능이 어떻 습니까 products_categories? 아니면 당신이 말한 것입니까?
Michael van Rooijen


물론 이것이 인덱스 된 행을 검색하는 다른 방법만큼 효율적이지 않아야하는 이유는 없습니다. 데이터베이스 작성자가 테스트하고 최적화했는지 여부에 따라 다릅니다. 계산 복잡성 측면에서 최악의 경우 IN절 에서 O (n log N) 정렬 (알고리즘에 따라 표시 한 것처럼 정렬 된 목록에서 선형 일 수 있음)을 수행 한 다음 선형 교차 / 조회를 수행합니다. .
jberryman

답변:


40

일반적으로 IN목록이 너무 커지면 (일반적으로 100 이하의 영역에있는 일부 잘못 정의 된 '너무 큰'값의 경우) 조인을 사용하는 것이 더 효율적이되어 필요한 경우 임시 테이블을 생성합니다. 숫자를 저장합니다.

숫자가 조밀 한 집합 (간격 없음-샘플 데이터가 제안하는 것)이면 WHERE id BETWEEN 300 AND 3000.

그러나 아마도 세트에 간격이있을 수 있으며,이 시점에서 결국 유효한 값 목록을 사용하는 것이 더 나을 수 있습니다 (간격이 상대적으로 적은 경우가 아니면 다음을 사용할 수 있습니다.)

WHERE id BETWEEN 300 AND 3000 AND id NOT BETWEEN 742 AND 836

또는 그 간격이 무엇이든.


46
"조인 사용, 임시 테이블 생성"의 예를 들어 주시겠습니까?
제이크

데이터 세트가 인터페이스 (다중 선택 요소)에서 왔고 선택한 데이터에 간격이 있고이 간격이 순차적 간격이 아닌 경우 (누락 : 457, 490, 658, ..) AND id NOT BETWEEN XXX AND XXX작동하지 않으며 다음 을 수행하는 것이 좋습니다. (x = 1 OR x = 2 OR x = 3 ... OR x = 99)@David Fells가 쓴 것과 동등한 것을 고수하십시오 .
deepcell

1
내 경험상-전자 상거래 웹 사이트에서 작업 할 때 관련없는 제품 ID가 ~ 50 개의 검색 결과를 표시해야합니다. "1. 50 개의 개별 쿼리"로 더 나은 결과를 얻었습니다. "2."IN 절"". 현재로서는이를 증명할 방법이 없습니다. 단, 쿼리 # 2는 모니터링 시스템에서 항상 느린 쿼리로 표시되는 반면, # 1은 실행 횟수에 관계없이 표시되지 않습니다. 수백만 ... 같은 경험이있는 사람 있나요? (더 나은 캐싱과 관련이 있거나 다른 쿼리가 쿼리간에 인터레이스되도록 허용 할 수 있습니다 ...)
Chaim Klar

1
@Chaim, 물론 별도의 쿼리는 느리지 않습니다. 각각 하나의 레코드 만 가져 오면됩니다. 프로파일 러는 일련의 쿼리가 관련되어 있고 비교를 위해 집계해야한다는 것을 알지 못합니다.
Daniel

24

나는 몇 가지 테스트를 해왔고 David Fells가 그의 대답에서 말했듯 이 꽤 잘 최적화되어 있습니다. 참고로, 저는 1,000,000 개의 레지스터가있는 InnoDB 테이블을 만들고 500,000 개의 난수를 가진 "IN"연산자로 선택을 수행했습니다. MAC에서는 2.5 초 밖에 걸리지 않습니다. 짝수 레지스터 만 선택하는 데는 0.5 초가 걸립니다.

내가 가진 유일한 문제 max_allowed_packetmy.cnf파일 에서 매개 변수 를 늘려야한다는 것 입니다. 그렇지 않으면 신비한 "MYSQL이 사라졌습니다"오류가 생성됩니다.

테스트를 위해 사용하는 PHP 코드는 다음과 같습니다.

$NROWS =1000000;
$SELECTED = 50;
$NROWSINSERT =15000;

$dsn="mysql:host=localhost;port=8889;dbname=testschema";
$pdo = new PDO($dsn, "root", "root");
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

$pdo->exec("drop table if exists `uniclau`.`testtable`");
$pdo->exec("CREATE  TABLE `testtable` (
        `id` INT NOT NULL ,
        `text` VARCHAR(45) NULL ,
        PRIMARY KEY (`id`) )");

$before = microtime(true);

$Values='';
$SelValues='(';
$c=0;
for ($i=0; $i<$NROWS; $i++) {
    $r = rand(0,99);
    if ($c>0) $Values .= ",";
    $Values .= "( $i , 'This is value $i and r= $r')";
    if ($r<$SELECTED) {
        if ($SelValues!="(") $SelValues .= ",";
        $SelValues .= $i;
    }
    $c++;

    if (($c==100)||(($i==$NROWS-1)&&($c>0))) {
        $pdo->exec("INSERT INTO `testtable` VALUES $Values");
        $Values = "";
        $c=0;
    }
}
$SelValues .=')';
echo "<br>";


$after = microtime(true);
echo "Insert execution time =" . ($after-$before) . "s<br>";

$before = microtime(true);  
$sql = "SELECT count(*) FROM `testtable` WHERE id IN $SelValues";
$result = $pdo->prepare($sql);  
$after = microtime(true);
echo "Prepare execution time =" . ($after-$before) . "s<br>";

$before = microtime(true);

$result->execute();
$c = $result->fetchColumn();

$after = microtime(true);
echo "Random selection = $c Time execution time =" . ($after-$before) . "s<br>";



$before = microtime(true);

$sql = "SELECT count(*) FROM `testtable` WHERE id %2 = 1";
$result = $pdo->prepare($sql);
$result->execute();
$c = $result->fetchColumn();

$after = microtime(true);
echo "Pairs = $c Exdcution time=" . ($after-$before) . "s<br>";

결과 :

Insert execution time =35.2927210331s
Prepare execution time =0.0161771774292s
Random selection = 499102 Time execution time =2.40285992622s
Pairs = 500000 Exdcution time=0.465420007706s

다른 사람들을 위해 2013 년 후반 MBP에서 i7을 사용하는 VirtualBox (CentOS)에서 실행되는 것을 추가하겠습니다. 출력의 세 번째 줄 (질문과 관련된 것)은 다음과 같습니다. Random selection = 500744 Time execution time = 53.458173036575s .. 53 초는 애플리케이션에 따라 허용 될 수 있습니다. 내 용도로는 그렇지 않습니다. 또한 짝수 테스트는 대신 %등호 연산자 ( =) 와 함께 모듈로 연산자 ( )를 사용하기 때문에 당면한 질문과 관련이 없습니다 IN().
rinogo

이 기능이없는 유사한 쿼리를 사용하여 IN 연산자가있는 쿼리를 비교하는 방법이므로 관련성이 있습니다. 컴퓨터가 swapipng이거나 다른 가상 컴퓨터에서 작동하기 때문에 다운로드 시간이기 때문에 더 많은 시간을 얻을 수 있습니다.
jbaylina

14

임의의 수의 ID를 입력하고 중첩 된 쿼리를 실행할 수있는 임시 테이블을 만들 수 있습니다. 예 :

CREATE [TEMPORARY] TABLE tmp_IDs (`ID` INT NOT NULL,PRIMARY KEY (`ID`));

선택 :

SELECT id, name, price
FROM products
WHERE id IN (SELECT ID FROM tmp_IDs);

6
그것은 하위 쿼리 사용하는 대신 임시 테이블에 가입하는 것이 좋습니다
scharette

3
@loopkin 조인 대 하위 쿼리로 이것을 어떻게 수행하는지 설명해 주시겠습니까?
Jeff Solomon

3
@jeffSolomon SELECT products.id, name, price from products JOIN tmp_IDs on products.id = tmp_IDs.ID;
scharette

이 대답! 나는 긴 레지스트리 매우 매우 빠르고, 무엇을 찾고 있었다이다
의 Damián 라파엘 Lattenero

정말 고마워요. 엄청나게 빠르게 작동합니다.
mrHalfer

4

IN많은 레코드 목록에서 큰 매개 변수 세트와 함께 사용하면 실제로 속도가 느립니다.

최근에 해결 한 경우에는 두 개의 where 절이 있는데 하나는 2,50 개의 매개 변수가 있고 다른 하나는 3,500 개의 매개 변수를 사용하여 4 천만 개의 레코드 테이블을 쿼리했습니다.

내 쿼리는 표준 WHERE IN. 대신 IN 문에 대한 하위 쿼리를 사용하여 (자체 인덱싱 된 테이블에 매개 변수를 넣음) 쿼리를 2 초로 줄였습니다.

내 경험으로 MySQL과 Oracle 모두에서 일했습니다.


1
나는 "대신 IN 문에 대한 하위 쿼리를 사용하여 (자신의 인덱싱 된 테이블에 매개 변수를 넣음)"에서 귀하의 요점을 얻지 못했습니다. "WHERE ID IN (1,2,3)"대신 "WHERE ID IN (SELECT id FROM xxx)"를 사용해야한다는 뜻입니까?
Istiyak Tailor

4

IN괜찮고 최적화되어 있습니다. 인덱싱 된 필드에서 사용하는지 확인하면 괜찮습니다.

기능적으로 다음과 같습니다.

(x = 1 OR x = 2 OR x = 3 ... OR x = 99)

DB 엔진에 관한 한.


1
사실이 아닙니다. IN clouse를 사용하여 DB에서 5k 레코드를 가져옵니다. IN clouse에는 PK 목록이 포함되어 있으므로 관련 열이 인덱싱되고 고유함이 보장됩니다. EXPLAIN에 따르면 전체 테이블 스캔은 "fifo-queue-alike"스타일의 PK 조회를 사용하여 수행됩니다.
Antoniossss

MySQL에서는 "기능적으로 동등" 하다고 생각하지 않습니다 . IN더 나은 성능을 위해 최적화를 사용합니다.
Joshua Pinter

1
Josh, 대답은 2011 년부터였습니다. 그 이후로 상황이 바뀌었을 것입니다.하지만 예전에는 IN이 일련의 OR 문으로 변환되었습니다.
David Fells

1
이 대답은 정확하지 않습니다. 에서 고성능 MySQL을 그리 MySQL의에서의 IN () 목록에있는 값을 정렬하고 값이 목록에 있는지 여부를 확인하기 위해 빠른 이진 검색을 사용합니다. 이것은 목록의 크기에서 O (log n) 인 반면, 동등한 일련의 OR 절은 목록 크기에서 O (n)입니다 (즉, 큰 목록의 경우 훨씬 느림).
Bert

버트-네. 이 답변은 구식입니다. 편집을 제안하십시오.
David Fells

-2

IN연산자에 많은 값을 제공하는 경우 먼저 중복을 제거하기 위해 정렬해야합니다. 적어도 나는 그것을 의심합니다. 따라서 정렬에는 N log N 시간이 걸리므로 너무 많은 값을 제공하는 것은 좋지 않습니다.

내 경험에 따르면 값 집합을 더 작은 하위 집합으로 분할하고 응용 프로그램의 모든 쿼리 결과를 결합하면 최상의 성능을 얻을 수 있습니다. 다른 데이터베이스 (Pervasive)에서 경험을 수집했음을 인정하지만 모든 엔진에 동일하게 적용될 수 있습니다. 세트당 내 값 수는 500-1000이었습니다. 다소 느 렸습니다.


1
나는 이것이 7 년이라는 것을 알고 있지만,이 대답의 문제는 단순히 교육받은 추측에 근거한 의견이라는 것입니다.
Giacomo1968
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.