MySQL SELECT id + SELECT * WHERE id IN (...) кажется быстрее, чем простой SELECT *. Зачем? - PullRequest
0 голосов
/ 08 мая 2020

Я выполняю следующий запрос к представлению с именем shipments_last_twenty_days:

Q1 :

SELECT shipments_last_twenty_days.* FROM shipments_last_twenty_days 
LEFT JOIN customers ON shipments_last_twenty_days.customer_id = customers.id 
WHERE customers.store_id = 147;

Товарищ заметил, что запрос потратит почти все время его обработки, выполняющее «Отправку данных», а также то, что на этот раз сильно колебалось, и он предложил, чтобы я разделил запрос на две части: первая для получения только идентификаторов, а вторая для получения всех полей с использованием идентификаторы (если на первом шаге не возвращаются идентификаторы, что происходит примерно в 80% случаев). Он сказал, что это может работать быстрее , и, основываясь на некоторых сегодняшних тестах, я думаю, что он был прав примерно в два раза. Проблема в том, Я не понимаю, почему . Вот два запроса, на которые я разделил исходный запрос:

Q1a EXPLAIN вывод):

SELECT shipments_last_twenty_days.id FROM shipments_last_twenty_days 
LEFT JOIN customers ON shipments_last_twenty_days.customer_id = customers.id 
WHERE customers.store_id = 147;

+----+-------------+-----------+--------+---------------+---------+---------+-----------------------+-------+-------------+
| id | select_type |   table   |  type  | possible_keys |   key   | key_len |          ref          | rows  |    Extra    |
+----+-------------+-----------+--------+---------------+---------+---------+-----------------------+-------+-------------+
|  1 | SIMPLE      | shipments | range  | date          | date    |       9 |                       | 61758 | Using where |
|  1 | SIMPLE      | customers | eq_ref | PRIMARY       | PRIMARY |       4 | customers.customer_id |     1 | Using where |
+----+-------------+-----------+--------+---------------+---------+---------+-----------------------+-------+-------------+

Q1b ( и EXPLAIN output):

SELECT * FROM shipments_last_twenty_days WHERE id IN (2668955, 2671554);

+----+-------------+-----------+-------+---------------+---------+---------+-----+------+-------------+
| id | select_type |   table   | type  | possible_keys |   key   | key_len | ref | rows |    Extra    |
+----+-------------+-----------+-------+---------------+---------+---------+-----+------+-------------+
|  1 | SIMPLE      | shipments | range | PRIMARY       | PRIMARY |       4 |     |    2 | Using where |
+----+-------------+-----------+-------+---------------+---------+---------+-----+------+-------------+

... (где 2668955 и 2671554, очевидно, являются значениями, возвращаемыми Q1a ).

Кто-нибудь может мне объяснить почему это? Если разница основана на 80% случаев, когда Q1b не выполняется, потому что Q1a не возвращает результатов ... не должно Q1a (SELECT id ) будет столь же дорогостоящим, как Q1 (SELECT *), если нечего возвращать?

PS: Я не добавил информацию об индексе или информацию о таблице, потому что я полагаю, что этот результат должен быть независимо от этого, но при необходимости спрашивайте. Кроме того, версия MySQL - 5.1 (я знаю, что она старая).

EDIT (дополнительная информация):

SHOW CREATE VIEW shipments_last_twenty_days;

CREATE ALGORITHM=UNDEFINED DEFINER=`fakeuser`@`0.0.0.0` 
SQL SECURITY DEFINER VIEW `shipments_last_twenty_days` AS 
select `shipments`.`id` AS `id`,
`shipments`.`prog` AS `prog`,
`shipments`.`customer_id` AS `customer_id`,
`shipments`.`id_corriere` AS `id_corriere`,
`shipments`.`id_destinatario` AS `id_destinatario`,
`shipments`.`id_servizio` AS `id_servizio`,
`shipments`.`id_servizioLDV` AS `id_servizioLDV`,
`shipments`.`id_fattura` AS `id_fattura`,
`shipments`.`id_abbonamento` AS `id_abbonamento`,
`shipments`.`date` AS `date`,
`shipments`.`dataMod` AS `dataMod`,
`shipments`.`dataDel` AS `dataDel`,
`shipments`.`dataCreazioneAWB` AS `dataCreazioneAWB`,
`shipments`.`dataTrackPartenza` AS `dataTrackPartenza`,
`shipments`.`dataTrackConsegna` AS `dataTrackConsegna`,
`shipments`.`hasLDV` AS `hasLDV`,
`shipments`.`nColli` AS `nColli`,
`shipments`.`Contenuto` AS `Contenuto`,
`shipments`.`peso` AS `peso`,
`shipments`.`pesoLDV` AS `pesoLDV`,
`shipments`.`isDocumento` AS `isDocumento`,
`shipments`.`isTrasportoPreziosi` AS `isTrasportoPreziosi`,
`shipments`.`isMerceDaImballare` AS `isMerceDaImballare`,
`shipments`.`isReturn` AS `isReturn`,
`shipments`.`prezzoCalcolato` AS `prezzoCalcolato`,
`shipments`.`prezzoFinale` AS `prezzoFinale`,
`shipments`.`notes` AS `notes`,
`shipments`.`isFatto` AS `isFatto`,
`shipments`.`fatturaImporto` AS `fatturaImporto`,
`shipments`.`fatturaAssicurazione` AS `fatturaAssicurazione`,
`shipments`.`fatturaContrassegno` AS `fatturaContrassegno`,
`shipments`.`isFatturato` AS `isFatturato`,
`shipments`.`isAnnullato` AS `isAnnullato`,
`shipments`.`isModificatoAmministratore` AS `isModificatoAmministratore`,
`shipments`.`valoreContrassegno` AS `valoreContrassegno`,
`shipments`.`valoreAssicurazione` AS `valoreAssicurazione`,
`shipments`.`valoreMerce` AS `valoreMerce`,
`shipments`.`shipmentValueCurrency` AS `shipmentValueCurrency`,
`shipments`.`isConsegnatoTracking` AS `isConsegnatoTracking`,
`shipments`.`failedDelivery` AS `failedDelivery`,
`shipments`.`bookingNumber` AS `bookingNumber`,
`shipments`.`riferimentoMittente` AS `riferimentoMittente`,
`shipments`.`masterTrk` AS `masterTrk`,
`shipments`.`isPagamentoDaziAlMittente` AS `isPagamentoDaziAlMittente`,
`shipments`.`isEsportato` AS `isEsportato`,
`shipments`.`labelMasterExtension` AS `labelMasterExtension`,
`shipments`.`contrassegnoTipo` AS `contrassegnoTipo`,
`shipments`.`isNotificaInternaInviata` AS `isNotificaInternaInviata`,
`shipments`.`trackDataPartenza` AS `trackDataPartenza`,
`shipments`.`trackDataConsegna` AS `trackDataConsegna`,
`shipments`.`trackFirmaConsegna` AS `trackFirmaConsegna`,
`shipments`.`trackDataSalvataggio` AS `trackDataSalvataggio`,
`shipments`.`isCreatoDaWebService` AS `isCreatoDaWebService`,
`shipments`.`shipmentOrigin` AS `shipmentOrigin`,
`shipments`.`boxdropId` AS `boxdropId`,
`shipments`.`isControllataFatturaFornitore` AS `isControllataFatturaFornitore`,
`shipments`.`noteConsegna` AS `noteConsegna`,
`shipments`.`webServiceTipo` AS `webServiceTipo`,
`shipments`.`isTrackDatiConsegnaManuali` AS `isTrackDatiConsegnaManuali`,
`shipments`.`numeroChiusura` AS `numeroChiusura`,
`shipments`.`dataChiusura` AS `dataChiusura`,
`shipments`.`corriere` AS `corriere`,
`shipments`.`isChiusoManifestSDA` AS `isChiusoManifestSDA`,
`shipments`.`costoAcquisto` AS `costoAcquisto`,
`shipments`.`isConsegnaSabato` AS `isConsegnaSabato`,
`shipments`.`isPortoAssegnato` AS `isPortoAssegnato`,
`shipments`.`portoAssegnato_codice` AS `portoAssegnato_codice`,
`shipments`.`portoAssegnato_codice_cap` AS `portoAssegnato_codice_cap`,
`shipments`.`pagamentoDazi_codice` AS `pagamentoDazi_codice`,
`shipments`.`pagamentoDazi_cap` AS `pagamentoDazi_cap`,
`shipments`.`isChiusoBorderoSGT` AS `isChiusoBorderoSGT`,
`shipments`.`isRichiestaImport` AS `isRichiestaImport`,
`shipments`.`idDepartment` AS `idDepartment`,
`shipments`.`use_dpd_predict` AS `use_dpd_predict`,
`shipments`.`dpd_predict_channel` AS `dpd_predict_channel`,
`shipments`.`dpd_predict_value` AS `dpd_predict_value`,
`shipments`.`dpd_predict_language` AS `dpd_predict_language`,
`shipments`.`UPSSignatureRequired` AS `UPSSignatureRequired`,
`shipments`.`returnRecipientOption` AS `returnRecipientOption`,
`shipments`.`returnRecipientDepartmentId` AS `returnRecipientDepartmentId`,
`shipments`.`returnOptionUPS` AS `returnOptionUPS`,
`shipments`.`exportedToManifest` AS `exportedToManifest`,
`shipments`.`shipmentSource` AS `shipmentSource`,
`shipments`.`valueMBESafeValue` AS `valueMBESafeValue`,
`shipments`.`goodsDescriptionMBESafeValue` AS `goodsDescriptionMBESafeValue`,
`shipments`.`NEXShippingCollectionId` AS `NEXShippingCollectionId`,
`shipments`.`NEXShippingId` AS `NEXShippingId`,
`shipments`.`serviceAfterDowngrade` AS `serviceAfterDowngrade`,
`shipments`.`UPSAdultSignatureRequired` AS `UPSAdultSignatureRequired`,
`shipments`.`addressUPSAccessPoint` AS `addressUPSAccessPoint` 
from `shipments` 
where ((`shipments`.`date` >= '2020-04-18 00:00:00') and 
(`shipments`.`date` < '2020-05-09 00:00:00') and 
(`shipments`.`isAnnullato` = 0) and 
(`shipments`.`hasLDV` is true) and 
(`shipments`.`isEsportato` is false))

SHOW INDEX FROM shipments

+-----------+------------+----------------------+--------------+-----------------+-----------+-------------+----------+--------+------+------------+---------+
|   Table   | Non_unique |       Key_name       | Seq_in_index |   Column_name   | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment |
+-----------+------------+----------------------+--------------+-----------------+-----------+-------------+----------+--------+------+------------+---------+
| shipments |          0 | PRIMARY              |            1 | id              | A         |     2636401 |          |        |      | BTREE      |         |
| shipments |          1 | MittentiOrdini       |            1 | id_destinatario | A         |     2636401 |          |        | YES  | BTREE      |         |
| shipments |          1 | masterTrk            |            1 | masterTrk       | A         |     2636401 |          |        | YES  | BTREE      |         |
| shipments |          1 | prog                 |            1 | prog            | A         |      376628 |          |        | YES  | BTREE      |         |
| shipments |          1 | customer_id_and_date |            1 | customer_id     | A         |       32548 |          |        | YES  | BTREE      |         |
| shipments |          1 | customer_id_and_date |            2 | date            | A         |     2636401 |          |        | YES  | BTREE      |         |
| shipments |          1 | date                 |            1 | date            | A         |     2636401 |          |        | YES  | BTREE      |         |
+-----------+------------+----------------------+--------------+-----------------+-----------+-------------+----------+--------+------+------------+---------+

PS: Мне известно, что MySQL, похоже, не использует составной индекс, который я создал для этого запроса. Я попробую запустить его без представления, а затем думаю, что MySQL будет его использовать (представление было улучшением производительности, когда у меня не было этого индекса). В любом случае, для целей этой публикации меня больше всего интересует, почему Q1a + Q1b может быть быстрее, чем Q1 , я хочу понять это (если это не имеет смысла с теоретической точки зрения, скажите, пожалуйста, возможно, мои тесты были неправильными).

1 Ответ

1 голос
/ 09 мая 2020

Добавьте этот индекс в shipments:

INDEX(isAnnullato, hasLDV, isExportato, customer_id, date, id)

Если какой-либо из этих столбцов - TEXT или BLOB, подумайте об их удалении из VIEW.

Разделить запрос?

Почти всегда разделение сложного запроса на два более простых запроса будет медленнее. Подумайте об этом так: есть накладные расходы на каждый запрос - сеть, синтаксический анализ, разработка плана запроса, отправка данных обратно. Для «простого» запроса 90% стоимости запроса составляют эти накладные расходы.

Что включать в INDEX

Это зависит от обстоятельств. Существует множество критериев, поэтому трудно сформулировать какое-либо правило, которое «всегда» работает. Вот некоторые из них, которые «обычно» работают для «составного» (многоколоночного) индекса:

  • Это будет лучше, чем индивидуальный (одностолбцовый) индекс (а).
  • Порядок столбцов в индексе обычно имеет значение.
  • Логические значения подходят для составного индекса.
  • Сначала протестируйте вещи с помощью =. Порядок (в индексе) не имеет значения.
  • Не более одного диапазона в конце.
  • Даже после выполнения вышеуказанных действий рассмотрите возможность создания «покрывающего» индекса, прикрепив к любым другим столбцам упоминается где-либо в запросе.
  • Не более 5 столбцов. (Да, я нарушил это в своем предложении.)
  • Индекс может помогает выбирать / удалять / обновлять.
  • Индекс замедляет вставки - но обычно преимущество к другим запросам намного превышает затраты на вставки.
  • UPDATEing столбец, который находится в INDEX, является несколько дорогостоящим - запись индекса необходимо удалить и вставить новую.

Дополнительные обсуждения: http://mysql.rjweb.org/doc.php/index_cookbook_mysql

Анализ предлагаемого индекса

INDEX(isAnnullato, hasLDV, isExportato,  -- first because of "="; any order
      customer_id,   -- I'm not sure where to put this
      date,          -- range
      id)            -- for "covering"
...