[mysql] (큰?) 수의 값에 대한 MySQL “IN”연산자 성능

최근에 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)

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



답변

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

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

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

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

또는 그 간격이 무엇이든.


답변

나는 몇 가지 테스트를 해왔고 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


답변

임의의 수의 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);


답변

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

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

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

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


답변

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

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

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

DB 엔진에 관한 한.


답변

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

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


답변