В последнее время я экспериментировал с Redis и MongoDB, и может показаться, что часто бывают случаи, когда вы сохраняете массив идентификаторов в MongoDB или Redis. Я буду придерживаться Redis для этого вопроса, так как я спрашиваю об операторе MySQL IN .
Мне было интересно, насколько производительно перечислить большое количество (300-3000) идентификаторов внутри оператора IN, что будет выглядеть примерно так:
SELECT id, name, price
FROM products
WHERE id IN (1, 2, 3, 4, ...... 3000)
Представьте себе что-то столь же простое, как таблица продуктов и категорий, которую вы обычно можете ОБЪЕДИНЯТЬ вместе, чтобы получить продукты из определенной категории . В приведенном выше примере вы можете видеть, что в данной категории в Redis ( category:4:product_ids
) я возвращаю все идентификаторы продуктов из категории с идентификатором 4 и помещаю их в указанный выше SELECT
запрос внутри IN
оператора.
Насколько это эффективно?
Это ситуация "в зависимости от обстоятельств"? Или есть конкретное «это (не) приемлемо», «быстро» или «медленно», или я должен добавить LIMIT 25
, или это не помогает?
SELECT id, name, price
FROM products
WHERE id IN (1, 2, 3, 4, ...... 3000)
LIMIT 25
Или мне следует обрезать массив идентификаторов продукта, возвращаемых Redis, чтобы ограничить его до 25 и добавить только 25 идентификаторов в запрос, а не 3000, и LIMIT
увеличить его до 25 изнутри запроса?
SELECT id, name, price
FROM products
WHERE id IN (1, 2, 3, 4, ...... 25)
Любые предложения / отзывы очень ценятся!
источник
id IN (1,2,3 ... 3000)
работать по сравнению с таблицей JOINproducts_categories
. Или это то, что вы говорили?IN
предложения (это может быть даже линейно в отсортированном списке, как вы показываете, в зависимости от алгоритма), а затем линейное пересечение / поиск .Ответы:
Вообще говоря, если
IN
список становится слишком большим (для некоторого нечетко определенного значения «слишком большой», которое обычно находится в районе 100 или меньше), становится более эффективным использовать соединение, создавая временную таблицу, если это необходимо. держать числа.Если числа представляют собой плотный набор (без пробелов - о чем свидетельствуют образцы данных), тогда вы можете добиться большего успеха
WHERE id BETWEEN 300 AND 3000
.Однако, по-видимому, в наборе есть пробелы, и в этот момент может быть лучше пойти со списком допустимых значений в конце концов (если только пробелов относительно мало, и в этом случае вы можете использовать:
Или какие бы там ни были пробелы.
источник
AND id NOT BETWEEN XXX AND XXX
не будет работать, и лучше придерживайтесь эквивалента,(x = 1 OR x = 2 OR x = 3 ... OR x = 99)
как написал @David Fells.Я проводил несколько тестов, и, как сказал Дэвид Феллс в своем ответе , он довольно хорошо оптимизирован. Для справки я создал таблицу InnoDB с 1 000 000 регистров и делаю выборку с оператором «IN» с 500 000 случайных чисел, это занимает всего 2,5 секунды на моем MAC; выбор только четных регистров занимает 0,5 секунды.
Единственная проблема, которая у меня возникла, это то, что мне пришлось увеличить
max_allowed_packet
параметр изmy.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
источник
%
) с оператором равенства (=
) вместоIN()
.Вы можете создать временную таблицу, в которую вы можете поместить любое количество идентификаторов и выполнить вложенный запрос. Пример:
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 параметрами, а другое с 3500 параметрами, запрашивающими таблицу из 40 миллионов записей.
Мой запрос занял 5 минут по стандарту
WHERE IN
. Вместо этого используя подзапрос для оператора IN (помещая параметры в их собственную индексированную таблицу), я сократил время запроса до ДВУХ секунд.По моему опыту, работал как с MySQL, так и с Oracle.
источник
IN
в порядке и хорошо оптимизирован. Убедитесь, что вы используете его в индексированном поле, и все в порядке.Функционально это эквивалентно:
Что касается движка БД.
источник
IN
использует оптимизацию для повышения производительности.Когда вы предоставляете много значений для
IN
оператора, он сначала должен отсортировать их, чтобы удалить дубликаты. По крайней мере, я так подозреваю. Поэтому было бы нехорошо указывать слишком много значений, так как сортировка занимает N log N времени.Мой опыт показал, что разделение набора значений на более мелкие подмножества и объединение результатов всех запросов в приложении дает лучшую производительность. Я признаю, что я получил опыт работы с другой базой данных (Pervasive), но то же самое может относиться ко всем движкам. Мое количество значений в наборе составляло 500–1000. Более-менее было значительно медленнее.
источник