Hiệu suất toán tử MySQL “IN” trên số lượng giá trị (lớn?)


94

Gần đây, tôi đã thử nghiệm Redis và MongoDB và có vẻ như thường có những trường hợp bạn lưu trữ một mảng id trong MongoDB hoặc Redis. Tôi sẽ gắn bó với Redis cho câu hỏi này vì tôi đang hỏi về toán tử MySQL IN .

Tôi đã tự hỏi nó hoạt động như thế nào để liệt kê một số lượng lớn (300-3000) id bên trong toán tử IN, trông giống như sau:

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

Hãy tưởng tượng một cái gì đó đơn giản như một bảng sản phẩmdanh mục mà bạn thường có thể THAM GIA cùng nhau để lấy các sản phẩm từ một danh mục nhất định . Trong ví dụ trên, bạn có thể thấy rằng trong một danh mục nhất định trong Redis ( category:4:product_ids), tôi trả về tất cả các id sản phẩm từ danh mục có id 4 và đặt chúng vào SELECTtruy vấn ở trên bên trong INtoán tử.

Làm thế nào là hiệu suất này là?

Đây có phải là một tình huống "nó phụ thuộc"? Hoặc có cụ thể "điều này là (không) chấp nhận được" hoặc "nhanh" hoặc "chậm" hay tôi nên thêm một LIMIT 25, hoặc điều đó không giúp được gì?

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

Hay tôi nên cắt bớt mảng id sản phẩm do Redis trả về để giới hạn nó ở 25 và chỉ thêm 25 id vào truy vấn thay vì 3000 và LIMIT-ing nó thành 25 từ bên trong truy vấn?

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

Bất kỳ đề xuất / phản hồi được đánh giá cao!


Tôi không chắc chính xác những gì bạn đang hỏi? Một truy vấn với "id IN (1,2,3, ... 3000))" nhanh hơn 3000 truy vấn với "id = value". Nhưng một phép nối với "category = 4" sẽ nhanh hơn cả hai cách trên.
Ronnis

Đúng, mặc dù vì một sản phẩm có thể thuộc nhiều danh mục nên tôi không thể làm "danh mục = 4". Sử dụng Redis, tôi sẽ lưu trữ tất cả id của các sản phẩm thuộc một danh mục nhất định và sau đó truy vấn trên đó. Tôi đoán câu hỏi thực sự là, hiệu suất sẽ như thế nào id IN (1,2,3 ... 3000)so với bảng JOIN của products_categories. Hay đó là những gì bạn đang nói?
Michael van Rooijen

Chỉ cần cẩn thận khỏi lỗi đó trong MySql stackoverflow.com/questions/3417074/…
Itay Moav -Malimovka

Tất nhiên không có lý do gì khiến điều này không hiệu quả như bất kỳ phương pháp nào khác để truy xuất các hàng được lập chỉ mục; nó chỉ phụ thuộc vào việc các tác giả cơ sở dữ liệu đã thử nghiệm và tối ưu hóa cho nó hay chưa. Về độ phức tạp tính toán, chúng tôi sẽ thực hiện tệ nhất là sắp xếp O (n log N) trên INmệnh đề (điều này thậm chí có thể tuyến tính trên một danh sách được sắp xếp như bạn hiển thị, tùy thuộc vào thuật toán), và sau đó là giao tuyến / tìm kiếm tuyến tính .
jberryman

Câu trả lời:


40

Nói chung, nếu INdanh sách quá lớn (đối với một số giá trị không được xác định là 'quá lớn' thường nằm trong vùng 100 hoặc nhỏ hơn), thì việc sử dụng phép nối, tạo một bảng tạm thời nếu cần thiết sẽ trở nên hiệu quả hơn. để nắm giữ các con số.

Nếu các con số là một tập hợp dày đặc (không có khoảng trống - mà dữ liệu mẫu gợi ý), thì bạn có thể làm tốt hơn với WHERE id BETWEEN 300 AND 3000.

Tuy nhiên, có lẽ là có khoảng trống trong tập hợp, tại thời điểm đó, tốt hơn là nên đi cùng với danh sách các giá trị hợp lệ (trừ khi các khoảng trống là tương đối ít về số lượng, trong trường hợp đó bạn có thể sử dụng:

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

Hoặc bất cứ khoảng trống nào.


46
Bạn có thể vui lòng cho một ví dụ về "sử dụng một phép nối, tạo một bảng tạm thời"?
Jake

nếu tập dữ liệu đến từ một giao diện (phần tử nhiều lựa chọn) và có khoảng trống trong dữ liệu đã chọn và khoảng trống này không phải là khoảng trống tuần tự (thiếu: 457, 490, 658, ..) thì AND id NOT BETWEEN XXX AND XXXsẽ không hoạt động và tốt hơn là gắn bó với tương đương (x = 1 OR x = 2 OR x = 3 ... OR x = 99)như @David Fells đã viết.
deepcell

theo kinh nghiệm của tôi - làm việc trên các trang web thương mại điện tử, chúng tôi phải hiển thị kết quả tìm kiếm của ~ 50 ID sản phẩm không liên quan, chúng tôi đã có kết quả tốt hơn với "1. 50 truy vấn riêng biệt", so với "2. một truy vấn có nhiều giá trị trong" IN mệnh đề "". Tôi không có bất kỳ cách nào để chứng minh điều đó vào lúc này, ngoại trừ việc truy vấn số 2 sẽ luôn hiển thị dưới dạng truy vấn chậm trong hệ thống giám sát của chúng tôi, trong khi số 1 sẽ không bao giờ hiển thị, bất kể số lượng thực thi đang ở hàng triệu ... có ai có cùng kinh nghiệm không? (chúng tôi có thể có thể liên hệ nó với bộ nhớ đệm tốt hơn, hoặc cho phép truy vấn khác để interlace giữa các truy vấn ...)
Chaim Klar

24

Tôi đã thực hiện một số bài kiểm tra và như David Fells đã nói trong câu trả lời của mình , nó được tối ưu hóa khá tốt. Để tham khảo, tôi đã tạo một bảng InnoDB với 1.000.000 đăng ký và thực hiện lựa chọn với toán tử "IN" với 500.000 số ngẫu nhiên, chỉ mất 2,5 giây trên MAC của tôi; chỉ chọn các thanh ghi chẵn mất 0,5 giây.

Vấn đề duy nhất mà tôi gặp phải là tôi phải tăng max_allowed_packettham số từ my.cnftệp. Nếu không, lỗi "MYSQL đã biến mất" bí ẩn sẽ được tạo ra.

Đây là mã PHP mà tôi sử dụng để thực hiện bài kiểm tra:

$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>";

Và kết quả:

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

Vì lợi ích của những người khác, tôi sẽ thêm rằng đang chạy trong VirtualBox (CentOS) trên MBP cuối năm 2013 của tôi với i7, dòng thứ ba (dòng liên quan đến câu hỏi) của đầu ra là: Lựa chọn ngẫu nhiên = 500744 Thời gian thực thi = 53.458173036575 giây .. 53 giây có thể chịu được tùy thuộc vào ứng dụng của bạn. Đối với việc sử dụng của tôi, không thực sự. Ngoài ra, hãy lưu ý rằng bài kiểm tra số chẵn không phù hợp với câu hỏi hiện tại vì nó sử dụng toán tử modulo ( %) với toán tử bằng ( =) thay vì IN().
rinogo

Nó có liên quan vì nó là một cách để so sánh một truy vấn với toán tử IN với một truy vấn tương tự không có chức năng này. Có thể là thời gian cao hơn bạn nhận được vì đó là thời gian tải xuống, vì máy của bạn đang hoán đổi hoặc đang hoạt động trong một máy ảo khác.
jbaylina

14

Bạn có thể tạo một bảng tạm thời, nơi bạn có thể đặt bất kỳ số ID nào và chạy truy vấn lồng nhau Ví dụ:

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

và chọn:

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

6
nó tốt hơn để tham gia vào bảng temp của bạn thay vì sử dụng một subquery
scharette

3
@loopkin bạn có thể giải thích cách bạn làm điều này với tham gia hay truy vấn con không?
Jeff Solomon

3
@jeffSolomon CHỌN products.id, tên, giá TỪ các sản phẩm THAM GIA tmp_IDs trên products.id = tmp_IDs.ID;
scharette

CÂU TRẢ LỜI NÀY! là những gì tôi đang tìm kiếm, rất rất nhanh cho Văn phòng đăng ký dài
Damián Rafael Lattenero

Cảm ơn bạn rất nhiều. Nó hoạt động cực kỳ nhanh chóng.
mrHalfer

4

Việc sử dụng INvới một tham số lớn được thiết lập trên một danh sách lớn các bản ghi trên thực tế sẽ chậm.

Trong trường hợp mà tôi đã giải quyết gần đây, tôi có hai mệnh đề where, một mệnh đề có 2.50 tham số và mệnh đề còn lại có 3.500 tham số, truy vấn một bảng gồm 40 Triệu bản ghi.

Truy vấn của tôi mất 5 phút bằng cách sử dụng tiêu chuẩn WHERE IN. Thay vào đó, bằng cách sử dụng truy vấn con cho câu lệnh IN (đặt các tham số vào bảng được lập chỉ mục của riêng chúng), tôi đã nhận được truy vấn xuống còn HAI giây.

Làm việc cho cả MySQL và Oracle theo kinh nghiệm của tôi.


1
Tôi không hiểu ý bạn ở "Thay vào đó, hãy sử dụng truy vấn con cho câu lệnh IN (đặt các tham số trong bảng được lập chỉ mục của riêng chúng)". Ý của bạn là thay vì sử dụng "WHERE ID IN (1,2,3)", chúng ta nên sử dụng "WHERE ID IN (SELECT id FROM xxx)"?
Istiyak Tailor

4

INlà tốt và được tối ưu hóa tốt. Đảm bảo rằng bạn sử dụng nó trên một trường được lập chỉ mục và bạn ổn.

Về mặt chức năng, nó tương đương với:

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

Theo như động cơ DB có liên quan.


1
Không hẳn. Tôi sử dụng IN clouse để tìm nạp 5k bản ghi từ DB. IN clouse chứa danh sách PK để cột liên quan được lập chỉ mục và đảm bảo là duy nhất. EXPLAIN cho biết, quá trình quét toàn bộ bảng được thực hiện dựa trên việc sử dụng tra cứu PK theo kiểu "năm mươi hàng đợi giống nhau".
Antoniossss

Trên MySQL, tôi không tin rằng chúng "tương đương về mặt chức năng" . INsử dụng tối ưu hóa để có hiệu suất tốt hơn.
Joshua Pinter

1
Josh, câu trả lời là từ năm 2011 - Tôi chắc chắn rằng mọi thứ đã thay đổi kể từ đó, nhưng trở lại ngày IN đã được chuyển đổi thành một loạt các câu lệnh OR.
David Fells

1
Câu trả lời này không đúng. Từ MySQL Hiệu suất Cao : Không phải như vậy trong MySQL, nó sắp xếp các giá trị trong danh sách IN () và sử dụng tìm kiếm nhị phân nhanh để xem liệu một giá trị có trong danh sách hay không. Đây là O (log n) trong kích thước của danh sách, trong khi một chuỗi các mệnh đề OR tương đương là O (n) trong kích thước của danh sách (nghĩa là chậm hơn nhiều đối với danh sách lớn).
Bert

Bert - vâng. Câu trả lời này đã lỗi thời. Hãy đề xuất một bản chỉnh sửa.
David Fells

-2

Khi bạn cung cấp nhiều giá trị cho INtoán tử, trước tiên nó phải sắp xếp nó để loại bỏ các bản sao. Ít nhất thì tôi nghi ngờ điều đó. Vì vậy, sẽ không tốt nếu cung cấp quá nhiều giá trị, vì việc sắp xếp mất N log N thời gian.

Kinh nghiệm của tôi đã chứng minh rằng việc cắt tập hợp các giá trị thành các tập con nhỏ hơn và kết hợp kết quả của tất cả các truy vấn trong ứng dụng sẽ cho hiệu suất tốt nhất. Tôi thừa nhận rằng tôi đã thu thập kinh nghiệm trên một cơ sở dữ liệu khác (Pervasive), nhưng điều tương tự có thể áp dụng cho tất cả các công cụ. Số lượng giá trị trên mỗi bộ của tôi là 500-1000. Ít nhiều đã chậm hơn đáng kể.


Tôi biết đây là 7 năm trôi qua, nhưng vấn đề với câu trả lời này chỉ đơn giản là đó là một nhận xét dựa trên một phỏng đoán có học.
Giacomo1968
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.