| | | | |
Несколько приемов работы с базами данных | Полный текст материала
Другие публикации автора: Сергей Дуплик
Цитата или краткий комментарий: «... Данная статья предназначена в основном для тех, кто начинает работать с базами данных. Здесь собраны приемы, направленные на оптимизацию и ускорение работы с базами данных. ...» |
Важно:- Страница предназначена для обсуждения материала, его содержания, полезности, соответствия действительности и так далее. Смысл не в разборке, а в приближении к истине :о) и пользе для всех.
- Любые другие сообщения или вопросы, а так же личные эмоции в адрес авторов и полемика, не относящаяся к теме обсуждаемого материала, будут удаляться без предупреждения авторов, дабы не мешать жителям нормально общаться.
- При голосовании учитывайте уровень, на который расчитан материал. "Интересность и полезность" имеет смысл оценивать относительно того, кому именно предназначался материал.
- Размер одного сообщений не должен превышать 5К. Если Вам нужно сказать больше, сделайте это за два раза. Или, что в данной ситуации правильнее, напишите свою статью.
Всегда легче осудить сделанное, нежели сделать самому. Поэтому, пожалуйста, соблюдайте правила Королевства и уважайте друг друга.
Добавить свое мнение.
| | Содержит полезные и(или) интересные сведения | [1] | 3 | 25% | | | | Ничего особенно нового и интересного | [2] | 6 | 50% | | | | Написано неверно (обязательно укажите почему) | [3] | 3 | 25% | | Всего проголосовали: 12 | | | Все понятно, материал читается легко | [1] | 8 | 80% | | | | Есть неясности в изложении | [2] | 2 | 20% | | | | Непонятно написано, трудно читается | [3] | 0 | 0% | | Всего проголосовали: 10 |
[Вопросы скорости обработки]
Отслеживать это обсуждение
Всего сообщений: 2617-01-2009 06:38
Да, если выполняются запросы на добавление и модификацию записей. Потому есть мнение, что удаление индекса до массового добавления записей и обратное построение их после операции. Мною лично не проверялось, если кто имеет информацию - поделитесь.
Писал как-то импорт с массовыми вставками записей. Было много индексов по строковым полям (Код, Код поставщика, Код производителя, Код таможенный, Код нормированный, Наименование, ....). Импрорт выполнялся сутки. После того, как сделал предварительное удаление индексов (осталась парочка) с восстановлением после импорта, сам импорт сократился до 1.5 часов. Сами индексы строятся считанные секунды/минуты. |
|
14-01-2009 17:02Во-1, это еще один запрос к БД.
Спорить не буду: если есть проблема и ее решение имеет практическую ценность что стоит публикации, то решение верно.
Во-2, где гарантия, что за время работы триггера никто ничего не вставит? ...
Что что-то не вставят -- никакой, а что ничего при этом не изменится -- "уровень изоляции".
:param in(fieid,0)
Oracle такую конструкцию точно не поддерживает |
|
13-11-2008 02:01сообщение от автора материала зачем восстанавливать идентификатор: если этот идентификатор используется как генератор суррогатного ключа, то значение этого ключа (и идентификатора) можно взять из только-что вставленной записи
Во-1, это еще один запрос к БД.
Во-2, где гарантия, что за время работы триггера никто ничего не вставит? Да, операция вставки и все, что есть в триггере, выполняется в одной транзакции. Но все же...
:param in(fieid,0)
Не могу сказать, поддерживает ли такую конструкцию Парадокс. SQL Server и Access поддерживают. Oracle - с ходу не помню. |
|
10-11-2008 11:07Спасибо за ответ. Использую DBE и Paradox 7.0. |
|
10-11-2008 09:49Лучше тогда
where (:ParamObsh = PObsh) Or (:ParamObsh = 0)
Возможно не каждый СУБД поддерживает такой синтаксис :ParamObsh in (PObsh,0) |
|
10-11-2008 08:45У меня не работает структура вида:
:param in(fieid,0)
Формирую запрос:
select PObsh,POtdelenie
from KPodrazd
where :ParamObsh in (PObsh,0)
Выдает ошибку в 3-й строке: PObsh,0)
Запрос переделал иначе, но идея та же:
select PObsh,POtdelenie
from KPodrazd
where (:ParamObsh=PObsh) Or (:ParamObsh in (0))
Работает.
Почему не работает первый вариант? |
|
03-10-2008 03:15>>>А вообще статья правильная ... Предлагаю считать все
>>>замечания к статье ... ломанем пальцев и дутьем
>>>пузырей "оч.умелых специалистов"
+1 |
|
19-09-2008 07:36Задумка правильная, но реализация требует изменений:
5. Устраняем повторяющиеся запросы
В этом разделе надо было указать, что использование конструкции select count(*) as cnt ... не просто увеличивает время Операции, а в принципе вредно, т.к. является источником труднонаходимых ошибок. Если Операция -- это insert в таблицу из которой делается выборка select count(*), то рано или поздно получим ошибку вставки (ведь БД многопользовательская и пока вторая транзакция собирается делать вставку на основе cnt = 0, первая транзакция уже сделала вставку и изменила cnt). Дальше описано все правильно.
9. Курсоры - это долго
Заголовок имеет отношение к Курсорам, а тело раздела имеет отношение к "Индивидуальной обработке" и "Массовой обработке".
Конструкции типа insert-select, а также delete(update)-select относятся к Массовой (bulk) обработке. Конечно, они работают в разы быстрее.
Надо было назвать раздел типа "Массовая обработка", а то и правда подумают, что Курсоры - это долго, а значит, плохо.
10. Восстанавливаем идентификатор в триггере
Спорный раздел. Непонятно, зачем восстанавливать идентификатор: если этот идентификатор используется как генератор суррогатного ключа, то значение этого ключа (и идентификатора) можно взять из только-что вставленной записи.
Мое мнение, этот раздел специфический для MS SQL Server. В других СУБД таких проблем нет, т.к. имеются стандартные механизмы возврата суррогатного ключа, например, массивы SQLCA (SQL Communication Area) или генераторы, значение из которых можно взять заранее и оно останется неизменным.
Или речь идет о чем-то еще. |
|
17-09-2008 23:49 А вообще статья правильная, есть конечно немного неточностей и не мешало бы уточнять к каким БД предлагаемое решение или более всего сооотвесвует или просто не применимо (как в случае с FB ":param in(fieid,0)"). Для новичков самое то. Предлагаю считать все замечания к статье (в том числе и мои) ломанем пальцев и дутьем пузырей "оч.умелых специалистов". :-) |
|
17-09-2008 23:42> :param in(fieid,0) Нетривиально.
Мало того IB и FB пошлют вас unknown и будут правы. |
|
17-09-2008 23:32> наиболее эффективным способом повышения быстродействия часто является простой SQL оператор
drop database
:-) |
|
16-09-2008 03:02:param in(fieid,0) Нетривиально. |
|
15-09-2008 06:23C апострофами малось запутался... Так примерно должно быть:
Query.SQL.Text := 'SELECT * FROM TABLE WHERE FIELD = ''' + Edit1.Text + '''';
Query.SQL.Text := 'SELECT * FROM TABLE WHERE FIELD = :Parametr';
Query.ParamByName('Parametr').asString := Edit1.Text; |
|
15-09-2008 04:51Хорошая статья. Я бы еще добавил пару слов о большом вреде НЕиспользования bind переменных в запросах.
Т.е. когда пишут
SELECT * FROM TABLE WHERE FIELD = ''' + Edit1.Text + '''';
вместо
SELECT * FROM TABLE WHERE FIELD = :Parametr'; |
|
15-09-2008 04:49SELECT * FROM TABLE WHERE FIELD = :Parametr';
:Parametr' это что?
меня просто комментирование кавычек напрягает, в первом примере, а второй не понял :( |
|
11-09-2008 01:56сообщение от автора материала Сергей Хачатуров
Придирайтесь. Буду исправлять, если соглашусь. :) |
|
10-09-2008 02:38На обобщенную информацию не тянет, очень многое справедливо только для определенных БД и компонентов доступа к данным.
Соответственно, придраться можно почти к каждому пункту. Иногда по-крупному. |
|
08-09-2008 04:23...
4. восстановили значение генератора:
генератор = n + 1
5. сработал триггер
выполнился insert:
генератор = n + 2
(то есть - insert, конечно, не выполнился,
по причине условия уникальности, наложенного
на первичный ключ)
триггер завершился с ошибкой
И?..
Триггеры вызываются в контексте транзакций, но вот работа с генераторами - _внетранзакционна_. Если так уж надо восстанавливать значение генератора - то его надо каким-либо образом синхронизировать, как в любом другом многопоточном приложении. |
|
08-09-2008 04:22Пункт 10.
Не знаю, конечно, насчет MS SQL'а, но в Interbase/Firebird такое действие считается совершенным моветоном, и вот почему (в IB/FB генераторов можно насоздавать сколько угодно - но представим, что работа идет с одним и тем же генератором):
наш триггер другой триггер (для другой таблицы)
1. сохранили значение генератора в n:
n = генератор
2. выполнили insert:
генератор = n + 1
(как я понимаю - в примере имеется в виду,
что выполнен некий вспомогательный insert,
в некую вспомогательную таблицу, для которой
первичный ключ получается не из генератора,
либо же просто не важен)
3. сработал триггер
выполнился insert:
генератор = n + 2
(в данном случае - выполнен _нужный_ insert,
для которого важно значение генератора,
из которого получился первичный ключ)
триггер завершился |
|
08-09-2008 04:19Пункт 4.
Есть такая замечательная возможность - определение собственных функция и процедур называется. И намного понятней для построения строки фильтра было бы написать простенькую процедуру вроде такой:
procedure AddConditionToFilter(
var Filter: String;
const FieldName, Condition, Value: String;
NeedToAdd: Boolean = True);
begin
if NeedToAdd then begin
if Filter <> '' then
Filter := Filter + ' and ';
Filter := Filter + FieldName + ' ' + Condition + ' ' + Value;
end;
end;
и вынести ее в отдельный функциональный модуль. И тогда добавление условий к фильтру можно писать просто и понятно:
sFilter := '';
AddConditionToFilter(sFilter, 'NumericField', '=', IntToStr(SpinEdit.Value), (SpinEdit.Value > 0));
AddConditionToFilter(sFilter, 'StringField', '=', Edit.Text, (Edit.Text <> ''));
AddConditionToFilter(sFilter, 'ClassField', '=', IntToStr(DBLookupComboBox.KeyValue),
not VarIsNull(DBLookupComboBox.KeyValue) and (DBLookupComboBox.KeyValue <> 0));
|
|
06-09-2008 00:46Поскольку объем текста превышает 5к, пишу продолжение. Кстати, ограничение странное :-)
>>> 11. Передаем соединение с базой данных в библиотеку
И опять нигде не сказана одна важная вещь, которую Delphi пишет в каждом проекте DLL. Только ее никто не читает. Ни те, кто знают (потому что знают), ни те, кто не знает (потому что не понимают, что это за фигня написана). Потому еще раз - требуется использование ShareMem при такой передаче. Хотя кто мешает передать только ConnectionString - непонятно. Да, создадутся разные подключения ну и что?
>>> Не нравится, как выполняется запрос
Хитро! А если человек получил запрос на форуме, он ему не нравится, но он даже не понимает, что он делает, только видит, что он выполняет то, что надо. А потом на другом форуме он получает другой совет, переделывает запрос по другому и в результате получается... ерунда. Так что еще один совет - не уверен - не обгоняй... тьфу, не знаешь, чего делает запрос - сперва разберись. Это сэкономит много времени потом, когда придется его модифицировать.
Разрешите пользователю вводить уточняющие запросы. Например, пусть пользователь ищет в базе абонентов Петрова из Москвы. База возвращает ему тысячу записей. Дайте возможность пользователю уточнить запрос без необходимости вновь вводить все данные - про Петрова и про Москву. Это как в Гугле - "Искать в найденном". Это очень удобно. Позвольте удалять уточняющие фильтры хотя бы как из стека. Поверьте, это ускоряет работу. Вопрос интерфейсной реализации этой "фичи" стоит рассматривать отдельно. Я сделал в виде меню: "Уточнить запрос", "Отменить уточнение", которая выключается, если уточняющих запросов нет, но это не совсем удобно. Уточняющие запросы удобно вводить уже в поле Filter. |
|
06-09-2008 00:45Ну, есть все-равно ряд замечаний.
>>> Исключением может быть ситуация, когда нужно выбрать из таблицы почти все поля
Вот я создал базу данных, где есть поля "Фамилия", "Имя", "Отчество", "Дата рождения", "Телефон". Мне нужно найти телефон по имени и фамилии сотрудника. Дата рождения мне не нужна. Следуя Вашей логике, я могу выбрать все поля (*), так как поле "Дата рождения" очень мало по сравнению с остальными запрашиваемыми полями. Шло время и я добавлял новые поля. Появились поля "Фотография", "Характеристика", еще какие-то большие поля. Но звездочка в запросе осталась, ибо кому знать, что при добавлении нового поля в базу потребуется переработать запрос поиска телефона сотрудника (!)
Вывод: ВСЕГДА используйте список полей, даже если у Вас есть подозрения, что база расти не будет. На самом деле, если Вы пишете проект не для своего эксклюзивного пользования, то база расти будет. А * - это временная мера, а нет ничего более постоянного, чем временное.
>>> 3. Строим фильтр
Попробуйте запустить Ваш код и убедитесь, что в конце оператора осталось лишнее and. Я предлагаю простое решение: в конце фильтра добавить условие (1=1). Оно всегда истинно, поддерживается всеми базами данных и мало. Далее, по этой же статье. Везде Вы используете параметры - и это, безусловно, правильно. В этом же разделе Вы параметры не используете. Это может быть опасно, если Вы передаете на сервер строковые данные, подготовленные пользователем. Умный пользователь может подготовить специальный SQL запрос в поле ввода, который скомпрометирует Ваши данные, или повредит их. Не допускайте этого даже в теории - используйте параметры. Вы знаете, что изменение строки SQL.Text приводит к перепостроению списка параметров. Потому запрос нужно строить в два прохода: сперва Вы строите строку фильтра, потом устанавливаете SQL.Text, потом (во второй проход!) выставляете параметры. Данный подход опасен - при изменении данных в полях ввода Вы рискуете создать неправильный запрос и получить неверный фильтр. Убедитесь в отсутствии несинхронизируемых обращений к полям ввода, а лучше - до составления запроса создайте локальные копии всех введеных на форме данных. Потом это может избавить Вас от головной боли.
Кстати, главу 8 (Исключаем условие из запроса) я бы поместил поблизости. Например, 4 номером. Потому что такое использование параметров в принципе оправдано при построении фильтра. Только не знаю, как это скажется на производительности - я формирую фильтр динамически, попробую делать и так.
Кстати, в ту же копилку: изменение SQL.Text приводит к парсингу запроса компонентами доступа к базе данных. Это недолго, но при наличии большого количества обновлений может несколько снизить производительность программы. Используйте временную переменную для создания запроса и потом однократного присвоения его SQL.Text. Помните: в большинстве случаев скорость и требуемая память - взаимоисключающие понятия. Размеры оперативной памяти растут быстрее, чем мощности процессоров, потому - ориентируйтесь на производительность, если нет явного требования обратного.
>>> 4. Накладываем фильтр
По этой главе могу заметить только одно - используйте SQL. Дело в том, что если у Вас данных очень много, а требуемых - очень мало, то фильтр выкачает все данные, а SQL.Text - только нужно. Пусть у Вас есть таблица со всеми абонентами Росии (краденая Центртелекомовская к примеру). Вы хотите найти телефон какого-то Петрова из Москвы. SQL.Text возвратит несколько сот записей, они быстро загрузятся, дальше поиск можно вести в нем. Но если Вы рассчитываете, что пользователь будет много и часто использовать поиск (фильтрацию) в относительно небольшом наборе данных - используйте Filter. Кстати, компоненты доступа к базам данных в большинстве своем разумны и не выкачивают ВСЕ данные с сервера сразу, а подкачивают их по мере необходимости.
>>> 6. Объединяем похожие запросы
Можно использоать динамическое создание и открытие запросов. Это тоже неплохо, если обращения в разным наборам данных независимы. Возможно, это вызовет снижение производительности - нужно смотреть.
>>> Построение новых индексов может положительно сказаться на одних запросах
Да, если выполняются запросы на добавление и модификацию записей. Потому есть мнение, что удаление индекса до массового добавления записей и обратное построение их после операции. Мною лично не проверялось, если кто имеет информацию - поделитесь. |
|
05-09-2008 02:17Неплохая статья по основам SQLя да ещё и с некими намёками на оптимизацию БД.
Только всё же в статье под названием "Несколько приемов работы с базами данных" ожидались приемы работы с БД, а не приемы построения SQL-запросов. Может быть, имеет смысл подумать над изменением названия, чтобы оно более соответствовало изложенному в статье материалу? |
|
04-09-2008 14:07Статья нормальная. Но, считаю, следовало бы отметить, что процентов 80 «тормозов» БД связаны в первую очередь с плохо сконструированной структурой базы. Когда структура создается для решения сиеминутной задачи, без минимальных попыток разобраться в предметной области и хотя бы как-то спрогнозировать возможное развитие проекта. По мере развития проекта такая БД начинает обрастать массой непонятных таблиц, полей, триггеров и т.д., назначение которых не помнят даже старожилы... Поэтому наиболее эффективным способом повышения быстродействия часто является простой SQL оператор Drop Table... :) |
|
02-09-2008 12:34так лучше не делать:
inttostr(DBLookupComboBox.KeyValue);
KeyValue - типа Vaiant и может принимать значение null, что может привести к ошибке преобразовании типов.
Следует использовать IntToStrDef(DBLookupComboBox.KeyValue,0) или проверять с помощью VarIsNull. |
|
02-09-2008 09:32Хорошая статья. Я бы еще добавил пару слов о большом вреде НЕиспользования bind переменных в запросах.
Т.е. когда пишут SELECT * FROM TABLE WHERE FIELD = ''' + Edit1.Text + ''''; вместо
SELECT * FROM TABLE WHERE FIELD = :Parametr'; |
|
|
|