Подтвердить что ты не робот

Почему реляционные запросы на основе набора лучше, чем курсоры?

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

Кроме того, у нас есть выбор просто потянуть большой набор данных обратно в наше приложение, а затем обрабатывать его по строкам, с помощью С# или Java или PHP или что-то еще.

Почему лучше использовать запросы на основе набора? Какова теория этого выбора? Что является хорошим примером решения на основе курсора и его реляционного эквивалента?

4b9b3361

Ответ 1

Основная причина, о которой я знаю, - это то, что операции на основе набора могут быть оптимизированы движком, запуская их через несколько потоков. Например, подумайте о быстрой сортировке - вы можете разделить список, который вы сортируете на несколько "кусков", и сортировать каждый отдельно в своем потоке. Механизмы SQL могут делать подобные вещи с огромным количеством данных в одном запросе на основе набора.

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

Ответ 2

В дополнение к вышеизложенному "пусть СУБД выполняет работу" (что является отличным решением), есть еще несколько веских причин оставить запрос в СУБД:

  • Это (субъективно) легче читать.. Когда вы посмотрите на код позже, скорее попробуйте и проанализируйте сложную хранимую процедуру (или код на стороне клиента) с помощью циклов и т.д., или вы бы скорее посмотрите на краткую инструкцию SQL?
  • Это позволяет избежать круговых поездок по сети.. Зачем перебрасывать все эти данные клиенту, а затем перетаскивать обратно? Зачем нужна сеть, если вам не нужно?
  • Это расточительно. Вашим серверам СУБД и приложений потребуется будет загрузить некоторые/все эти данные для работы над ним. Если у вас нет бесконечной памяти, вы, вероятно, опубликуете другие данные; зачем выбрасывать, возможно, важные вещи из памяти, чтобы буферировать набор результатов, который в основном бесполезен?
  • Почему бы вам не быть? Вы купили (или иным образом используете) высоконадежную, очень быструю СУБД. Почему бы вам не использовать его?

Ответ 3

Запросы на основе набора (обычно) быстрее, потому что:

  • У них есть дополнительная информация для оптимизатора запросов для оптимизации
  • Они могут выполнять пакетное чтение с диска
  • При откате, журналах транзакций и т.д. меньше регистрации.
  • Меньше блокировок снимается, что уменьшает накладные расходы
  • Настроенная логика ориентирована на РСУБД, поэтому они были сильно оптимизированы для нее (часто, за счет процедурной производительности).

Вытягивание данных на средний уровень для его обработки может быть полезным, хотя, поскольку оно удаляет издержки обработки с сервера БД (что является самым сложным инструментом для масштабирования и обычно делает другие вещи). Кроме того, вы обычно не имеете накладных расходов (или льгот) в среднем уровне. Такие вещи, как транзакционное протоколирование, встроенная блокировка и блокировка и т.д. - иногда это необходимо и полезно, в других случаях это просто трата ресурсов.

Простой курсор с процедурной логикой и примером на основе набора (T-SQL), который назначит код области на основе телефонной станции:

--Cursor
DECLARE @phoneNumber char(7)
DECLARE c CURSOR LOCAL FAST_FORWARD FOR
   SELECT PhoneNumber FROM Customer WHERE AreaCode IS NULL
OPEN c
FETCH NEXT FROM c INTO @phoneNumber
WHILE @@FETCH_STATUS = 0 BEGIN
   DECLARE @exchange char(3), @areaCode char(3)
   SELECT @exchange = LEFT(@phoneNumber, 3)

   SELECT @areaCode = AreaCode 
   FROM AreaCode_Exchange 
   WHERE Exchange = @exchange

   IF @areaCode IS NOT NULL BEGIN
       UPDATE Customer SET AreaCode = @areaCode
       WHERE CURRENT OF c
   END
   FETCH NEXT FROM c INTO @phoneNumber
END
CLOSE c
DEALLOCATE c
END

--Set
UPDATE Customer SET
    AreaCode = AreaCode_Exchange.AreaCode
FROM Customer
JOIN AreaCode_Exchange ON
    LEFT(Customer.PhoneNumber, 3) = AreaCode_Exchange.Exchange
WHERE
    Customer.AreaCode IS NULL

Ответ 4

Вам нужны были примеры из реальной жизни. У моей компании был указатель, который занял более 40 минут, чтобы обработать 30 000 записей (и были случаи, когда мне нужно было обновить более 200 000 записей). Для выполнения одной задачи без курсора потребовалось 45 секунд. В другом случае я удалил курсор и отправил время обработки от 24 часов до минуты. Одна из них была вставкой, в которой использовалось предложение values ​​вместо select, а другое - обновление, в котором вместо переменных были использованы переменные. Хорошее эмпирическое правило состоит в том, что если это вставка, обновление или удаление, вы должны искать способ, основанный на наборах для выполнения задачи.

Курсоры имеют свои возможности (или код не будет их в первую очередь), но они должны быть крайне редки при обращении к реляционной базе данных (кроме Oracle, которая оптимизирована для их использования). Одно место, где они могут быть быстрее, - это делать вычисления на основе значения предыдущей записи (итоговые итоги). BUt даже те, которые должны быть протестированы.

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

Третье использование курсора - запуск системных хранимых процедур через группу входных значений. SInce это ограничивается обычно небольшим набором, и никто не должен возиться с системными процессами, это приемлемо для администратора. Я не рекомендую делать то же самое с созданным пользователем хранимым proc для обработки большой партии и повторного использования кода. Лучше написать версию на основе набора, которая будет лучше, так как производительность в большинстве случаев должна переименовывать код кода.

Ответ 5

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

  • Вы обновляете большой набор данных в базе данных, где блокировка строк неприемлема (возможно, в часы производства). Обновление на основе набора имеет возможность блокировки таблицы в течение нескольких секунд (или минут), где курсор (если он правильно написан) не работает. Курсор может перебирать строки, обновляющие по одному, и вам не нужно беспокоиться о том, чтобы воздействовать на что-либо еще.

  • Преимущество использования SQL заключается в том, что основная часть работы по оптимизации обрабатывается механизмом базы данных в большинстве случаев. С двигателями db корпоративного класса разработчики перешли на кропотливую длину, чтобы убедиться, что система эффективна при обработке данных. Недостатком является то, что SQL - это язык, основанный на наборе. Вы должны иметь возможность определить набор данных для его использования. Хотя это звучит просто, в некоторых случаях это не так. Запрос может быть настолько сложным, что внутренние оптимизаторы в двигателе не могут эффективно создать путь выполнения и догадываться, что происходит... ваш супер мощный блок с 32 процессорами использует один поток для выполнения запроса, потому что он не знает как сделать что-либо еще, так что вы тратите время процессора на сервер базы данных, который обычно существует только в одном, а не в нескольких серверах приложений (так что, вернувшись к разуму 1, вы сталкиваетесь с ресурсами с другими вещами, которые нужно запускать на сервере базы данных). С языком на основе строк (С#, PHP, JAVA и т.д.) У вас больше контроля над тем, что происходит. Вы можете получить набор данных и заставить его выполнять так, как вы хотите. (Разделите данные, установленные для нескольких потоков и т.д.). Большую часть времени он все еще не будет эффективным, как запуск его в базе данных, потому что ему все равно придется обращаться к движку, чтобы обновить строку, но когда вам нужно сделать 1000+ вычислений, чтобы обновить строку ( и позволяет сказать, что у вас миллион строк), сервер базы данных может начать иметь проблемы.

Ответ 6

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

Функционально штраф за курсоры будет сильно отличаться от продукта к продукту. Некоторые (большинство?) Rdbmss построены, по крайней мере, частично поверх процессоров isam. Если вопрос уместен, и шпон достаточно тонкий, на самом деле может быть так же эффективно использовать курсор. Но это одна из вещей, с которыми вам следует познакомиться, с точки зрения вашего бренда dbms, перед тем, как попробовать.

Ответ 7

Как уже было сказано, база данных оптимизирована для операций с множеством. Буквально инженеры сели и отлаживали/настраивали эту базу данных в течение длительных периодов времени. Шансы вы оптимизировать их довольно тонкие. Есть всевозможные забавные трюки, с которыми вы можете играть, если у вас есть набор данных для работы с такими дисками, как чтение/запись на диске, кеширование, многопоточность. Кроме того, некоторые операции имеют высокие накладные расходы, но если вы делаете это с совокупностью данных сразу, стоимость одной части данных низкая. Если вы работаете только по одной строке за раз, многие из этих методов и операций просто не могут произойти.

Например, просто посмотрите, как соединяется база данных. Изучая планы объяснения, вы можете увидеть несколько способов объединения. Скорее всего, с помощью курсора вы начинаете строку за строкой в ​​одной таблице, а затем выбираете значения, которые вам нужны из другой таблицы. В основном это как вложенный цикл только без герметичности цикла (который, скорее всего, скомпилирован в машинный язык и супер оптимизирован). SQL Server сам по себе имеет целую кучу способов объединения. Если строки отсортированы, он будет использовать некоторый тип алгоритма слияния, если одна таблица мала, она может превратить одну таблицу в таблицу поиска хэша и выполнить соединение, выполнив поиск O (1) из одной таблицы в таблицу поиска. Существует несколько стратегий объединения, которые многие СУБД будут бить, когда вы просматриваете значения из одной таблицы в курсоре.

Посмотрите на пример создания таблицы поиска хэша. Чтобы построить таблицу, возможно, m операций, если вы соединяете две таблицы одной длиной n и длиной m, где m - меньшая таблица. Каждый поиск должен быть постоянным, так что это n операций. поэтому в основном эффективность хэш-соединения составляет около m (setup) + n (поиск). Если вы делаете это самостоятельно и не предполагаете никаких поисков/индексов, то для каждой из n строк вам нужно будет искать m записей (в среднем это соответствует m/2 поискам). Таким образом, в основном уровень операций идет от m + n (соединяется с группой записей одновременно) до m * n/2 (выполняется поиск через курсор). Также операции упрощения. В зависимости от типа курсора выборка каждой строки курсора может быть такой же, как и для другого выбора из первой таблицы.

Замки также убивают вас. Если у вас есть курсоры на столе, вы блокируете строки (на SQL-сервере это менее строго для статических и forward_only курсоров... но большинство кода курсора, который я вижу, просто открывает курсор, не указав ни одну из этих опций). Если вы выполняете операцию в наборе, строки все равно будут заблокированы, но в течение меньшего времени. Также оптимизатор может видеть, что вы делаете, и он может решить, что более эффективно блокировать всю таблицу вместо нескольких строк или страниц. Но если вы идете по строкам, оптимизатор понятия не имеет.

Другое дело, что я слышал, что в случае с Oracle супер оптимизирован для работы с курсором, поэтому он не имеет нигде рядом с тем же штрафом для операций на основе набора по сравнению с курсорами в Oracle, как и в SQL Server. Я не эксперт Oracle, поэтому не могу сказать точно. Но более одного человека Oracle сказал мне, что курсоры более эффективны в Oracle. Поэтому, если вы пожертвовали своим первенцем для Oracle, вам, возможно, не придется беспокоиться о курсорах, обратитесь к местному высокооплачиваемому Oracle DBA:)

Ответ 8

Идея, предпочитающая выполнять работу в запросах, заключается в том, что механизм базы данных может оптимизировать, переформулировав ее. Вот почему вы хотите запустить EXPLAIN в своем запросе, чтобы узнать, что делает db на самом деле. (например, использование индексов, размеров таблиц, а иногда и знаний о распределении значений в столбцах.)

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

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

Ответ 9

набор основан на одной операции курсор как много операций, как набор строк курсора

Ответ 10

РЕАЛЬНЫЙ ответ: получите один из E.F. Codd и обновить Big O notation. Спустя почти два десятилетия в ИТ это, IMHO, одна из больших трагедий современной MIS или CS степени: очень немногие фактически изучают вычисления. Вы знаете... "вычислить "часть" компьютера"? Язык структурированных запросов (и все его надмножества) - это просто практическое применение реляционной алгебры. Да, RDBMS оптимизировали управление памятью и чтение/запись, но то же самое можно сказать и о процедурных языках. Когда я прочитал это, исходный вопрос касается не IDE, а программного обеспечения, а скорее эффективности одного метода вычисления по сравнению с другим.

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

Ответ 11

Проще говоря, в большинстве случаев быстрее или проще позволить базе данных сделать это за вас.

Цель базы данных в жизни - хранить/извлекать/манипулировать данными в установленных форматах и ​​быть очень быстрыми. Ваш код VB.NET/ASP.NET, скорее всего, не так быстр, как выделенный движок базы данных. Использование этого является разумным использованием ресурсов.