2008-08-23 6 views
29

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

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

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

ответ

15

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

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

0

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

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

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

0

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

12

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

  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 
+0

'UPDATE Клиент SET AreaCode = AreaCode_Exchange.AreaCode FROM Customer РЕГИСТРИРУЙТЕСЬ AreaCode_Exchange НА ЛЕВЫЙ (Customer.PhoneNumber, 3) = AreaCode_Exchange.Exchange WHERE Customer.AreaCode IS NULL», Вы можете объяснить этот 'ЛЕВЫЙ (Customer.PhoneNumber, 3) 'и его функциональность – Smart003

2

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

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

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

15

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

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

Я согласен с Мэттом. Чтение некоторых книг Джо Селко также помогает при принятии некоторых из этих решений. –

+2

Вы забыли упомянуть оптимизацию запросов и декларативный характер SQL; курсоры и другие подходы на основе строк определяют точно, как извлекать/обрабатывать данные, где SQL-запросы определяют только, что делать - RDBMS затем может придумать лучший план на основе статистики (например, в зависимости от индекса статистики поиск может быть хуже или лучше подходит, тогда сканирование индекса, RDBMS может сделать различие, подходы на основе строк не могут ...) – Unreason

6

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

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

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

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

+0

Хотелось бы добавить эту ссылку: http://wiki.lessthandot.com/index.php/Cursors_and_How_to_Avoid_Them – HLGEM

1

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

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

1

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

Например, просто посмотрите, как соединяется база данных. Изучая планы объяснения, вы можете увидеть несколько способов объединения. Скорее всего, с помощью курсора вы начинаете строку за строкой в ​​одной таблице, а затем выбираете значения, которые вам нужны из другой таблицы. В принципе, это как вложенный цикл только без герметичности цикла (который, скорее всего, скомпилирован в машинный язык и супер оптимизирован). 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 :)

0

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

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

0

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

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