Почему запросы на основе реляционных множеств лучше, чем курсоры? - PullRequest
32 голосов
/ 23 августа 2008

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

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

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

Ответы [ 11 ]

18 голосов
/ 23 августа 2008

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

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

16 голосов
/ 23 августа 2008

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

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

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

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

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

Простой курсор с процедурной логикой и примером на основе множеств (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
8 голосов
/ 01 декабря 2008

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

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

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

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

3 голосов
/ 23 августа 2008

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

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

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

1 голос
/ 01 декабря 2008

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

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

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

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

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

1 голос
/ 01 декабря 2008

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

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

0 голосов
/ 13 июля 2009

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

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

0 голосов
/ 14 мая 2009

НАСТОЯЩИЙ ответ - иди и получи один из E.F. Книги Кодда и освоение реляционной алгебры . Тогда получите хорошую книгу по Big O нотации . После почти двух десятилетий в ИТ это, ИМХО, одна из главных трагедий современной степени MIS или CS: очень немногие действительно изучают вычисления. Вы знаете ... "вычислить" часть "компьютера"? Язык структурированных запросов (и все его надмножества) - это просто практическое применение реляционной алгебры. Да, СУБД оптимизировали управление памятью и чтение / запись, но то же самое можно сказать и о процедурных языках. Насколько я понимаю, первоначальный вопрос касается не IDE, программного обеспечения, а скорее эффективности одного метода вычислений по сравнению с другим.

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

0 голосов
/ 23 августа 2008

набор основан на одной операции Курсор столько же операций, сколько набор строк курсора

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...