Формула успеха для медленного SQL

1b01ecbf0e7e71e0b3c99dcba1e78bdc.gif

При подготовке к крупным рекламным акциям одной из самых больших скрытых опасностей является медленный SQL, который наиболее разрушительно влияет на бесперебойную работу сервисов. Это также самая большая скрытая опасность, которая часто вызывает дрожание всего приложения в повседневной работе. Как избежать медленный SQL в повседневной разработке?Мы должны знать, какие идеи следует использовать для решения проблем медленного SQL. В этой статье в основном представлены идеи устранения неполадок и решений для медленного SQL, а также представлен углубленный анализ и обобщение на практических примерах, чтобы быстрее и точнее обнаружить и решить проблему.

1. Этапы решения

Шаг 1. Соблюдайте SQL

По некоторым историческим причинам некоторые SQL-запросы могут быть очень сложными, требующими одновременной ассоциации множества таблиц и использования некоторых сложных функций и подзапросов.Такой SQL не окажет большого влияния на базу данных из-за относительно небольшого объема данных в базе данных. ранние этапы проекта.Давление, но по мере накопления времени и развития бизнеса эти SQL постепенно трансформируются в медленные SQL, что окажет определенное влияние на производительность базы данных.

Для такого SQL рекомендуется сначала понять бизнес-сценарий, разобраться во взаимосвязях, попытаться разобрать SQL на несколько простых небольших SQL и объединить их в памяти.

Шаг 2. Проанализируйте проблему

Наиболее часто используемым инструментом при анализе медленного SQL, безусловно, является оператор объяснения. Ниже приведен результат выполнения оператора объяснения.

8b76cbfe6f708ff038072a35382fec86.png

Как правило, индикаторы, на которые нам следует обратить особое внимание, включают тип, возможные_ключи, ключ, строки и многое другое.

type — тип соединения, который имеет следующие значения.Производительность отсортирована от лучшей к худшей следующим образом:

  • system: Эта таблица имеет только одну строку (эквивалент системной таблицы).Система является особым случаем константного типа.

  • const: эквивалентное сканирование запроса на предмет первичного ключа или уникального индекса, возвращает не более одной строки данных. Константный запрос выполняется очень быстро, поскольку он читается только один раз.

  • eq_ref: этот тип будет использоваться только в том случае, если используются все компоненты индекса, а индекс имеет значение PRIMARY KEY или UNIQUE NOT NULL. Его производительность уступает только system и const.

  • ref: Происходит, когда выполняется правило крайнего левого префикса индекса или индекс не является первичным ключом или индексом уникальности. Если используемый индекс соответствует лишь небольшому количеству строк, производительность хорошая.

СОВЕТЫ

Принцип крайнего левого префикса означает, что индекс соответствует индексу самым левым первым способом. Например, если создается комбинированный индекс (столбец1, столбец2, столбец3), то условие запроса:

  • ГДЕ столбец 1 = 1, ГДЕ столбец 1 = 1 И столбец 2 = 2, ГДЕ столбец 1 = 1 И столбец 2 = 2 И столбец 3 = 3 могут использовать этот индекс;

  • ГДЕ столбец2 = 2, ГДЕ столбец1 = 1 И столбец3 = 3 не может соответствовать индексу.

  • полнотекстовый: полнотекстовый индекс

  • ref_or_null: этот тип похож на ref, но MySQL дополнительно будет искать строки, содержащие NULL. Этот тип часто используется при анализе подзапросов.

  • index_merge: этот тип указывает на использование оптимизации слияния индексов, указывая на то, что в запросе используется несколько индексов.

  • unique_subquery: этот тип похож на eq_ref, но использует запрос IN, а подзапрос является первичным ключом или уникальным индексом. Например:

index_subquery: аналогичен unique_subquery, за исключением того, что подзапрос использует неуникальный индекс.

диапазон: сканирование диапазона, что означает, что были получены строки в указанном диапазоне. В основном используется для ограниченного сканирования индекса. Более распространенное сканирование диапазона осуществляется с помощью предложения BETWEEN или предложения WHERE с такими операторами, как >, >=, <, <=, IS NULL, <=>, BETWEEN, LIKE, IN() и т. д.

  • Индекс: полное сканирование индекса, аналогично ALL, за исключением того, что индекс сканирует все данные индекса. Этот тип можно использовать, когда запрос использует только подмножество столбцов в индексе. Есть два сценария, которые сработают:

  • Дерево индекса сканируется только в том случае, если индекс является покрывающим индексом для запроса и все данные, необходимые в запросе, удовлетворяются индексом. В настоящее время результатом дополнительного столбца объяснения является использование индекса. index обычно быстрее, чем ALL, поскольку размер индекса обычно меньше, чем данные таблицы.

  • Чтобы найти строки данных в индексном порядке, выполняется полное сканирование таблицы. В настоящее время индекс использования не будет отображаться в результатах дополнительного столбца объяснения.

  • ВСЕ: Полное сканирование таблицы, худшая производительность.

возможные_ключи

Показывает, какие индексы могут использоваться текущим запросом. Данные в этом столбце создаются на ранних этапах процесса оптимизации, поэтому некоторые индексы могут оказаться бесполезными для последующих процессов оптимизации.

ключ

Указывает индекс, фактически выбранный MySQL. Важно обратить внимание на использование файловой сортировки и использование временного. Первое означает, что индекс не может использоваться для завершения операции сортировки. Когда данные малы, они сортируются из памяти, в противном случае он сортируется с диска.Последнему MySQL необходимо создать временную таблицу для сохранения результатов.

EXPLAIN можно использовать для первоначального определения того, использует ли SQL индексы, корректны ли используемые индексы, разумна ли сортировка и дифференциация столбцов индекса. Благодаря этому можно в основном обнаружить большинство проблем.

Шаг 3. Укажите план

Если проблему невозможно решить с помощью самого SQL, можно разумно сформулировать план модификации на основе бизнес-сценариев, распределения данных и других факторов.

2. Дисплей корпуса

1. В этом SQL есть две основные проблемы.Одна из них заключается в том, что результат запроса имеет большой объем данных, около 20 000 фрагментов данных.Вторая заключается в том, что он сортируется по неиндексному полю Oil_gun_price, что приводит к сортировке файлов. Есть два варианта модификации: один — преобразовать его в пейджинговый запрос, отсортировать по идентификатору в порядке возрастания и избежать проблемы глубокого разбиения по страницам в соответствии со смещением идентификатора, другой — напрямую получить полный объем данных, которые соответствует условиям без указания метода сортировки, а потом сортировать в памяти. В подобных сценариях старайтесь не использовать базу данных для сортировки. Если вы не можете напрямую использовать индекс для сортировки, попробуйте загрузить все данные в память сразу или в подкачку перед сортировкой.

 
  
SELECT gs.id,
       gs.gas_code,
       gs.tpl_gas_code,
       gs.gas_name,
       gs.province_id,
       gs.province_name,
       gs.city_id,
       gs.city_name,
       gs.county_id,
       gs.county_name,
       gs.town_id,
       gs.town_name,
       gs.detail_address,
       gs.banner_image,
       gs.logo_image,
       gs.longitude,
       gs.latitude,
       gs.oil_gun_serials,
       gs.gas_labels,
       gs.status,
       gs.source,
       gp.oil_number,
       gp.oil_gun_price
FROM fi_club_oil_gas gs
LEFT JOIN fi_club_oil_gas_price gp ON gs.gas_code = gp.gas_code
WHERE oil_number = 95
  AND status = 1
  AND gs.yn = 1
  AND gp.yn=1
ORDER BY gp.oil_gun_price ASC;

2. Основная проблема этого SQL в том, что для сплайсинга в связанном запросе используются подзапросы, в подзапросе мало условий, что эквивалентно сначала полному сканированию таблицы, загрузке результатов первого запроса в память и затем его выполнение.Корреляция, время запроса составляет 2,63 секунды, что является распространенной причиной медленного SQL, и его следует избегать, насколько это возможно.Здесь подзапрос заменяется на корреляционный запрос, а окончательное время выполнения составляет 0,71 секунды.

 
  
SELECT count(0)
FROM trans_scheduler_base tsb
INNER JOIN
  (SELECT scheduler_code,
          vehicle_number,
          vehicle_type_code
   FROM trans_scheduler_calendar
   WHERE yn = 1
   GROUP BY scheduler_code) tsc ON tsb.scheduler_code = tsc.scheduler_code
WHERE tsb.type = 3
  AND tsb.yn = 1;


----------修改后--------------
SELECT count(distinct(tsc.scheduler_code))
FROM trans_scheduler_base tsb
LEFT JOIN trans_scheduler_calendar tsc ON tsb.scheduler_code = tsc.scheduler_code
WHERE tsb.type = 3
  AND tsb.yn = 1
  AND tsc.yn=1

3. Этот SQL относительно типичен. Это медленный SQL, который легко не заметить, но он часто появляется. Оба типа Carrier_code и Trader_code в SQL имеют индексы, но в конце используется индекс update_time. Это связано с результатами оптимизации оптимизатора MYSQL, что может привести к тому, что индекс, используемый при фактическом выполнении, будет отличаться от ожидаемого. Этот тип SQL является общим при использовании общего. Фактически, SQL-запрос не полностью применим во многих случаях, таких как метод сортировки, поле запроса, количество возвращаемых элементов и т. д. Поэтому рекомендуется, чтобы другая бизнес-логика использовала свой собственный, отдельно определенный SQL. Решением может быть использование Force_index для указания индекса или изменение метода сортировки в зависимости от ситуации.

 
  
SELECT id,
       carrier_name,
       carrier_code,
       trader_name,
       trader_code,
       route_type_name,
       begin_province_name,
       begin_city_name,
       begin_county_name,
       end_province_name,
       end_city_name,
       end_county_name
FROM carrier_route_config
WHERE yn = 1
  AND carrier_code ='C211206007386'
  AND trader_code ='010K1769496'
ORDER BY update_time DESC
LIMIT 10;

57bf604fa8a5d69af57d8b74455bd93a.png

Для операторов SQL limit N с группировкой и порядком (поля по порядку и по порядку имеют индексы, которые можно использовать), оптимизатор MySQL попытается использовать упорядоченность существующего индекса, чтобы уменьшить сортировку - кажется, это быть оптимальным решением плана выполнения SQL, но на самом деле эффект может быть совсем другим.Полагаю, каждый сталкивался со многими случаями, когда план выполнения SQL выбирает индекс порядка по id, что приводит к полному сканированию таблицы вместо используя условиеwhere.Поиск по индексу фильтрует данные, что может привести к тому, что запрос будет очень неэффективным (конечно, запрос может быть и очень эффективным, это связано с конкретным распределением данных в таблице)

Предпосылка о том, что оптимизация по пределам может сыграть положительную роль, заключается в том, чтобы, во-первых, предположить, что упорядоченные индексы и неупорядоченные индексы не имеют значения, а во-вторых, предположить, что данные распределены равномерно.

Эти два предположения являются предпосылкой для оценки стоимости доступа через отсортированные индексы (но в реальных производственных средах эти два предположения неверны в большинстве сценариев, поэтому в большинстве сценариев они приводят к неправильному выбору индекса), и вы можете столкнуться с время условной фильтрации индекса составляет десятки миллисекунд, но сканирование сортировки индекса занимает час, что можно считать ошибкой в ​​MySQL.

4. Ограничение в SQL также является одной из причин, которая часто приводит к медленному SQL. Когда предел используется для ограничения SQL, если предел, используемый SQL, превышает общее количество оставшихся записей, и используемые условия индекса не могут быть хорошими. Если характеристики упорядочены, то MYSQL, скорее всего, выполнит полное сканирование таблицы. Например, в следующем SQL-коде SQL использует индекс create_time во время выполнения, но в условии нет параметра create_time. Общее количество результатов SQL равно 6, что меньше предельного результата в 10 на данный момент. Таким образом, MYSQL выполняет полное сканирование таблицы, которое занимает 2,19 секунды, а при изменении лимита на 6 время выполнения SQL составляет 0,01 секунды, поскольку MYSQL возвращает непосредственный возврат при запросе 6 результатов, соответствующих условиям, и полное сканирование таблицы не выполняется. Поэтому, когда данные запроса на подкачку больше не занимают одну страницу, лучше всего установить параметр ограничения вручную.

 
  
SELECT cva.id,
       cva.carrier_vehicle_approval_code,
       dsi.driver_erp,
       d.driver_name,
       cva.vehicle_number,
       cva.vehicle_type,
       cva.vehicle_kind,
       cva.fuel_type,
       cva.audit_user_code,
       dsi.driver_id,
       cva.operate_type,
       dsi.org_code,
       dsi.org_name,
       dsi.prov_code,
       dsi.prov_name,
       dsi.area_code,
       dsi.area_name,
       dsi.node_code,
       dsi.node_name,
       dsi.position_name,
       cva.create_user_code,
       cva.audit_status,
       cva.create_time,
       cva.audit_time,
       cva.audit_reason,
       d.jd_pin,
       d.call_source,
       cv.valid_status
FROM driver_staff_info dsi
INNER JOIN carrier_vehicle_approval cva ON cva.driver_id = dsi.driver_id
INNER JOIN driver d ON dsi.driver_id = d.driver_id
INNER JOIN carrier_vehicle_info cv ON cv.vehicle_number = cva.vehicle_number
WHERE dsi.yn = 1
  AND d.yn = 1
  AND cva.yn = 1
  AND cv.yn = 1
  AND dsi.org_code = '3'
  AND dsi.prov_code = '021S002'
  AND cva.carrier_code = 'C230425013337'
  AND cva.yn = 1
  AND cva.audit_status = 0
  AND d.call_source IN ('kuaidi',
                        'kuaiyun')
ORDER BY cva.create_time DESC
LIMIT 10

5. Следующие таблицы SQL имеют слишком много ассоциаций, что приводит к относительно большому объему данных, загружаемых в базу данных.В зависимости от реальной ситуации вы можете сначала узнать данные одной таблицы в качестве основных данных, а затем заполнить в остальных полях согласно условиям подключения таблицы. Не рекомендуется связывать слишком много таблиц с таблицами с большими объемами данных, их можно заменить соответствующими избыточными полями или обработкой широких таблиц.

 
  
SELECT blsw.bid_line_code,
         blsw.bid_bill_code,
         blsw.bid_line_name,
         blsw.step_code,
         blsw.step_type,
         blsw.step_type_name,
         blsw.step_weight,
         blsw.step_weight_scale,
         blsw.block_price,
         blsw.max_weight_flag,
         blsw.id,
         blsw.need_quote_price,
         bbs.step_item_code,
         bbs.step_item_name,
         bbs.step_seq,
         bl.bid_line_seq
FROM bid_line_step_weight blsw
LEFT JOIN bid_bill_step bbs
    ON blsw.bid_bill_code = bbs.bid_bill_code
        AND blsw.step_code = bbs.step_code
        AND blsw.step_type = bbs.step_type
LEFT JOIN bid_line bl
    ON blsw.bid_line_code = bl.bid_line_code
        AND blsw.bid_bill_code = bl.bid_bill_code
WHERE blsw.yn = 1
        AND bbs.yn = 1
        AND bl.yn=1
        AND blsw.bid_bill_code = 'BL230423051192';

6. Этот SQL использует update_time в качестве индекса временного диапазона.Необходимо обратить внимание на то, существует ли проблема чрезмерной концентрации горячих данных, приводящая к очень большому объему данных запроса и сложным условиям сортировки, которые невозможно решить напрямую через SQL. оптимизация. С одной стороны, в первую очередь необходимо решить проблему чрезмерной концентрации «горячих» данных, а с другой — оптимизировать ее в соответствии с бизнес-сценариями, например добавить некоторые условия по умолчанию для уменьшения объема данных.

 
  
SELECT r.id,
         r.carrier_code,
         r.carrier_name,
         r.personal_name,
         r.status,
         r.register_org_name,
         r.register_org_code,
         r.register_city_name,
         r.verify_status,
         r.cancel_time,
         r.reenter_time,
         r.verify_user_code,
         r.data_source,
         r.sign_contract_flag,
         r.register_time,
         r.update_time,
         r.promotion_erp,
         r.promotion_name,
         r.promotion_pin,
         r.board_time,
         r.sync_basic_status,
         r.personal_verify_result,
        r.cert_verify_result,
        r.qualify_verify_result,
        r.photo_verify_result,
         d.jd_pin,
         d.driver_id,
         v.vehicle_number,
         v.vehicle_type,
         v.vehicle_length,
         r.cancellation_code ,
         r.cancellation_remarks
FROM carrier_resource r
LEFT JOIN carrier_driver d
    ON r.carrier_code = d.carrier_code
LEFT JOIN carrier_vehicle v
    ON r.carrier_code = v.carrier_code
WHERE r.update_time >= '2023-03-26 00:00:00'
        AND r.update_time <= '2023-04-02 00:00:00'
        AND r.yn = 1
        AND v.yn = 1
        AND d.yn = 1
        AND d.status != -1
        AND IFNULL(r.carrier_individual_type,'') != '2'
ORDER BY  (case r.verify_status
    WHEN 30 THEN
    1
    WHEN 20 THEN
    2
    WHEN 25 THEN
    3
    WHEN 35 THEN
    4
    WHEN 1 THEN
    5
    ELSE 6 end), r.update_time desc, if((v.driving_license_time IS null
        AND d.driver_license_time IS null), 0, 1) desc, if(((v.driving_license_time IS NOT null
        AND v.driving_license_time < NOW())
        OR (d.driver_license_time IS NOT null
        AND d.driver_license_time < NOW())), 2, 0) DESC LIMIT 10;

В реальном процессе разработки существует множество сценариев, которые сложно оптимизировать с помощью самого SQL, например, чрезмерная загрузка данных запроса, чрезмерный объем данных таблицы, серьезное искажение данных и т. д. Постарайтесь реализовать некоторые необходимые меры защиты и ограничения, основанные на бизнесе. сценарий, не влияя на него. В бизнес-ситуациях мы можем искать альтернативы, например, использовать ES для запроса, но нам все равно нужно выбирать разные решения на основе фактического сценария.

7. Для некоторых таблиц с большим объемом данных результаты могут быть возвращены быстро при выполнении запросов по страницам, но часто это происходит очень медленно при подсчете страниц для общего количества элементов. запросов., когда MYSQL запрашивает данные, соответствующие количеству элементов, они будут возвращены напрямую. При подсчете он будет запрашивать всю таблицу в соответствии с условиями. Когда объем данных, содержащихся в условиях, слишком велик, он будет ограничить производительность SQL. В этом случае рекомендуется с одной стороны переписать логику подкачки и разделить count и selectList. Можно рассмотреть возможность применения ES в качестве источника данных подсчета, или при определенных условиях, если общее количество элементов уже существует, вы не будете больше подсчета и уменьшите количество подсчетов страниц; С другой стороны, ограничьте глубину подкачки, чтобы избежать глубокой подкачки.

3. Общий принцип оптимизации

  • Создайте соответствующие индексы

  • Уменьшите ненужный доступ к столбцам

  • Использовать индекс покрытия

  • Переписывание операторов

  • Перенос данных

  • Выберите соответствующий столбец для сортировки

  • Правильная избыточность столбцов

  • SQL-разделение

  • Соответствующее применение ES

-конец-

Supongo que te gusta

Origin blog.csdn.net/jdcdev_/article/details/133153928
Recomendado
Clasificación