28.09.2007, 08:43 | #21 |
Участник
|
По поводу блокировок пока ничего не могу сказать, сейчас как раз разбираюсь, как это работает (на практике так сказать)...
Да и по поводу многих пунктов из списка golyshev'а не все понятно. Но вот по поводу SETCURRENTKEY я не совсем согласен. Чтобы наиболее эффективно использовать SETFILTER и SETRANGE насколько я знаю есть такая рекомендация: надо сделать активным ключ из полей, по которым накладывается фильтрация и накладывать фильтры на поля желательно в порядке их расположения в этом ключе. Разве не так? |
|
28.09.2007, 12:46 | #22 |
Участник
|
Это верно только для нативной базы.
Для SQL это не верно. MS SQL Server самостоятельно выбирает ключи поиска и план выполнения запроса. Независимо от переданного ему ключа через функцию SETCURRENTKEY пример: Код: recCustLedgerEntry Record Cust. Ledger Entry ________________________________________ recCustLedgerEntry.RESET; recCustLedgerEntry.SETCURRENTKEY("Customer No.","Posting Date","Currency Code"); recCustLedgerEntry.SETRANGE("Customer No.",'C3438'); recCustLedgerEntry.SETRANGE("Posting Date",231106D); recCustLedgerEntry.SETRANGE("External Document No.",'03'); recCustLedgerEntry.FIND('-'); Код: SELECT * FROM "dbo"."КРОК$Cust__Ledger_Entry" WHERE (("External_Document_No_"='03')) AND (("Customer_No_"='C3438')) AND (("Posting_Date"='2006-11-23')) ORDER BY "Customer_No_","Posting_Date","Currency_Code","Entry_No_" Параметры, переданные через SETCURRENTKEY, трактуется исключительно как порядок сортировки полученного результата (поле ORDER BY) и никак не влияют на выбор ключей, используемых при поиске. Изменим вышеприведенный код, убрав из него вызов SETCURRENTKEY: Код: recCustLedgerEntry Record Cust. Ledger Entry ________________________________________ recCustLedgerEntry.RESET; recCustLedgerEntry.SETRANGE("External Document No.",'SCI_604983/SZ622586'); recCustLedgerEntry.SETRANGE("Posting Date",200906D); recCustLedgerEntry.SETRANGE("Customer No.",'C3137'); recCustLedgerEntry.FIND('-'); Код: SELECT * FROM "dbo"."КРОК$Cust__Ledger_Entry" WHERE (("External_Document_No_"='03')) AND (("Customer_No_"='C3438')) AND (("Posting_Date"='2006-11-23')) ORDER BY "Entry_No_" План выполнения запроса выглядит следующим образом: Данный план выполнения лучше предыдущего, поскольку в нем отсутствует трудоемкая задача – сортировка конечного результата (сортировка входит в этап join’а таблиц). Последний запрос в среднем в 5-10 раз быстрее первого (зависит от размеров таблицы) |
|
01.10.2007, 12:40 | #23 |
Участник
|
1) golyshev, а вы с какой версией навика работаете? Роботают ли ваши рекомендации для версии 3.60 (3.70А) ?
2) судя по Код: recCustLedgerEntry.RESET; recCustLedgerEntry.SETRANGE("External Document No.",'SCI_604983/SZ622586'); recCustLedgerEntry.SETRANGE("Posting Date",200906D); recCustLedgerEntry.SETRANGE("Customer No.",'C3137'); recCustLedgerEntry.FIND('-'); Код: SELECT * FROM "dbo"."КРОК$Cust__Ledger_Entry" WHERE (("External_Document_No_"='03')) AND (("Customer_No_"='C3438')) AND (("Posting_Date"='2006-11-23')) ORDER BY "Entry_No_"
__________________
Ведрусса. Я не волшебник, а только учусь |
|
01.10.2007, 13:58 | #24 |
Участник
|
Честно говоря, наблюдал массу вещей под сиквелем, кот. слабо укладываются в понимание. Например, что списочная форма, отображающая отфильтрованную большую таблицу с установленным ключом, отличным от первичного невероятно тупит при навигации. Стоит для уже отвильтрованного рекордсета выставить первичный ключ - работа формы становится много быстрее. Насчет выставления ключа тоже не все столь однозначно: есть пример отчетов, которые работает по разному в зависимости от выставленного ключа, причем с ключом они работают значительно быстрее, чем без него. Это немного неукладывается со сказанным в посте выше. Прихожу к выводу, что по наву под сиквелем все очень не однозначно, и на постоянно возникающие вопросы никакие SQL troubleshooting referencы не дают ответов....
|
|
01.10.2007, 16:14 | #25 |
Участник
|
Цитата:
Сообщение от Critic
1) golyshev, а вы с какой версией навика работаете? Роботают ли ваши рекомендации для версии 3.60 (3.70А) ?
2) судя по Код: recCustLedgerEntry.RESET; recCustLedgerEntry.SETRANGE("External Document No.",'SCI_604983/SZ622586'); recCustLedgerEntry.SETRANGE("Posting Date",200906D); recCustLedgerEntry.SETRANGE("Customer No.",'C3137'); recCustLedgerEntry.FIND('-'); Код: SELECT * FROM "dbo"."КРОК$Cust__Ledger_Entry" WHERE (("External_Document_No_"='03')) AND (("Customer_No_"='C3438')) AND (("Posting_Date"='2006-11-23')) ORDER BY "Entry_No_" 2) не понял суть вопроса. SETRANGE - это поле WHERE в SQL запросе, SETCURRENTKEY - это поле ORDER by в SQL запросе. Цитата:
Сообщение от MSI
Честно говоря, наблюдал массу вещей под сиквелем, кот. слабо укладываются в понимание. Например, что списочная форма, отображающая отфильтрованную большую таблицу с установленным ключом, отличным от первичного невероятно тупит при навигации. Стоит для уже отвильтрованного рекордсета выставить первичный ключ - работа формы становится много быстрее. Насчет выставления ключа тоже не все столь однозначно: есть пример отчетов, которые работает по разному в зависимости от выставленного ключа, причем с ключом они работают значительно быстрее, чем без него. Это немного неукладывается со сказанным в посте выше. Прихожу к выводу, что по наву под сиквелем все очень не однозначно, и на постоянно возникающие вопросы никакие SQL troubleshooting referencы не дают ответов....
(например кодеюнит коррекции себестоимости. Чтобы его ускорить и отказаться от SETCURRENTKEY мы его сильно переписали) Конечно, есть ситуации, когда указание ключа ускоряет запрос. Каждый случай надо рассматривать индивидуально. Но если понять почему происходит ускорение, то зачастую становится ясно, что быстрее всё равно будет с сортировкой по первичному ключу. Надо лишь подточить запрос. Чаще всего это касается списочных форм на большой таблице. Вот пример: Форма на 17 таблице (Фин Журнал) с наложенными фильтрами по Posting Date, G/L Account No.,Debit Amount Сортировка по первичному ключу - Entry No. Предположим, Вы первый раз открыли форму, navision при этом открывает курсор, следующим запросом: Код: SELECT * FROM "dbo"."КРОК$G_L_Entry" WITH (READUNCOMMITTED) WHERE (("Posting_Date"=@P1)) AND (("Debit_Amount"<>@P2)) AND (("G_L_Account_No_"=@P3)) AND "Entry_No_">=@P4 ORDER BY "Entry_No_" OPTION (FAST 5) Предположим, что @P4 большое число - т.е. форма позиционируется на последних записях. При этом план запроса будет наверняка Clustered index Seek - т.е. поиск по первичному ключу. План запроса при этом кешируется, и будет использован для всех запросов, независимо от переданных параметров. Таким образом, в следующий раз, когда мы откроем форму, и спозиционируемся на первых записях, согласно уже скомпилированному плану, выборка будет идти по кластерному индексу. А так как @P4 у нас маленькое число, то SQL Server просканирует практически всю таблицу. Если же на форме установить сортировку по Posting Date, то запрос будет Код: SELECT * FROM "croc_na"."dbo"."КРОК$G_L_Entry" WITH (READUNCOMMITTED) WHERE (("Posting_Date"=@P1)) AND (("Debit_Amount"<>@P2)) AND (("G_L_Account_No_"=@P3)) AND "Posting_Date"=@P4 AND "Entry_No_">=@P5 ORDER BY "Posting_Date","Entry_No_" OPTION (FAST 5) И запрос будет выполняться одинакого быстро как для больших @P5 так и для маленьких. То есть указание порядка сортировки, косвенно влияет на план запроса. Но скорость отображения формы можно ускорить, если подсказать SQL Server'у что мы от него хотим. Если создать plan guid для первого запроса, запретив SQL Server'у делать Clutered Index Seek, план построится таким образом, что скорость запроса будет относительно независима от параметров и быстрее второго случая (т.к. в нем будет отсутсвовать сортировка). Скорее всего это будет выборка по ключу G_L_Account_No_,Posting_Date с джоином выборки по кластерному индексу и без сортировки. Код: sp_create_plan_guide @name = N'PlanGuid1', @stmt = N'SELECT * FROM "dbo"."КРОК$G_L_Entry" WITH (READUNCOMMITTED) WHERE (("Posting_Date"=@P1)) AND (("Debit_Amount"<>@P2)) AND (("G_L_Account_No_"=@P3)) AND "Entry_No_">=@P4 ORDER BY "Entry_No_" OPTION (FAST 5)', @type = N'SQL', @module_or_batch = NULL, @params = N'@P1 datetime,@P2 decimal(38,20),@P3 varchar(20),@P4 int', @hints = N'OPTION (OPTIMIZE FOR (@P4 = 0))' |
|
01.10.2007, 19:51 | #26 |
Участник
|
Получается, что стандартный функционал без переписывания не имеет шансов работать с большими базами и относительно большим количеством пользователей (~100)?
|
|
01.10.2007, 21:47 | #27 |
Участник
|
Зависит от используемых модулей и серверного оборудования.
Моё имхо - простая дистрибуция скрипя потянет при несколько-процессорном сервере с ~4gb озу и нормальным RAID'ом На форуме писалось о технических ограничениях. вот на вскидку: http://forum.mazzy.ru/index.php?showtopic=...l=пользователей Оборудование для Navision Что касается нас - самое масштабное внедрение: 250-300 конкурентных пользователей Сильно переделанный функционал (как на стороне NAV так и на стороне SQL) Репликации, всяческие интеграции и прочее. 16-ти процессорный сервер IBM с 16GB ОЗУ и RAID'ом от EMC:CLARiiON Часть пользователей висит на ферме Citrix серверов. |
|
02.10.2007, 07:58 | #28 |
Участник
|
Цитата:
2) не понял суть вопроса. SETRANGE - это поле WHERE в SQL запросе, SETCURRENTKEY - это поле ORDER by в SQL запросе.
а по теме Код: recCustLedgerEntry.RESET; recCustLedgerEntry.SETRANGE("External Document No.",'SCI_604983/SZ622586'); recCustLedgerEntry.SETRANGE("Posting Date",200906D); recCustLedgerEntry.SETRANGE("Customer No.",'C3137'); recCustLedgerEntry.FIND('-'); Код: recCustLedgerEntry.RESET; recCustLedgerEntry.SETRANGE("Customer No.",'C3137'); recCustLedgerEntry.SETRANGE("External Document No.",'SCI_604983/SZ622586'); recCustLedgerEntry.SETRANGE("Posting Date",200906D); recCustLedgerEntry.FIND('-'); Что из параметров WHERE Код: (("External_Document_No_"=''SCI_604983/SZ622586'')) (("Customer_No_"='C3137')) (("Posting_Date"='200906D'))
__________________
Ведрусса. Я не волшебник, а только учусь |
|
02.10.2007, 11:30 | #29 |
Участник
|
A+B=B+A
|
|
03.10.2007, 18:23 | #30 |
Участник
|
Находясь в состоянии борьбы за производительность нава под сиквелем у меня (уверен что ни только у меня ) возник ряд вопросов, в поиске ответов на которые мне любезно помог господин Golyshev. Даные вопросы могут оказаться интересными и для других участников форума. Господину Golyshev - огромное спасибо.
Цитата:
Цитата:
Сталкиваюсь со следующей проблемой: списочные формы больших таблиц иногда просто подвисают намертво при открытии (если стоит сортировка не по первичному ключу). Т.е. форма открывается, белеет, и нав пишет что не отвечает. И не выходит из этого состояния. (!!!) Особенно этот эффект неприятен при DrillDown во FlowFields, где выбором ключа какими-то средствами разработки Нава сложно управлять. Кстати, до конца не уверен, но есть ощущение, что ситуации не возникает, если для ключа отключена опция MaintainSQLIndex, т.е. формы никудышно работают именно с SQL-ными ключами.
На всех больших таблицах (17,32, 5802 и т.п.) нужно очень тщательно подобрать ключи, для которых необходим MaintainSQLIndex и правильно установить для них SQLIndex Проблема со списочными формами - это как раз проблема, описанная мной в примере (про 17 таблицу) Для этих форм везде надо установить ключ по умоланию в первичный (св-во SourceTableView) Цитата:
И еще один вопрос, который может показаться странным, но ситуация имеет место быть: иногда при прекращении работы учетных кодъюнитов по ERROR сиквель "забывает" решить, что транзакция завершена и снять блокировки с таблиц кот. были заблокированны до момента как (!) пользователь не нажмет ок на ERROR. В то время как все мануалы четко утверждают о том, что, дескать, все сообщения выводятся после завршения транзакции.
Если есть опыт борьбы с такой ситуацией очень бы хотелось получить какие-то рекомендации. Заранее благодарю. Мы боролись с этим так: скрипт, запускаемый каждые 15 секунд, проверяет - если какой-то пользователь является причиной блокировки и не посылает команды на сервер в течении 15 секунд, мы его отключаем. P.S. Думаю наш диалог будет полезен и другим участникам форума. Если Вам не принципиально, я бы рекомендовал публичное обсуждение |
|
03.10.2007, 19:16 | #31 |
Участник
|
Вот рекомендации, разработанные мной для наших разработчиков и подходящие для OLTP баз.
По умолчанию рекомендуется устанавливать поле MaintainSQLIndex в значение No, изменяя это значение только после того, когда станет ясно, что необходим дополнительный индекс в СУБД для таблицы и какие именно поля необходимо в него включить. (Анализируется Profiler'ом) Большинство таблиц (а соответственно и индексов) создавались на ранних версиях Navision Attain, и были рассчитаны на работу с собственной СУБД. Скорее всего, именно этот факт является причиной того, что в существующей версии Navision (3.60 – 4.00 SQL Option) большое количество индексов построено по принципам, не учитывающим специфику MS SQL.
ключ «Item No.», «Variant Code», «Drop Shipment», «Location Code», «Posting Date» Этот ключ слишком длинный, для того чтобы делать из него индекс в MS SQL (правило 1). Определим, из каких полей должен состоять индекс MS SQL – для этого посмотрим, каким образом разбивается таблица по приведенным полям. Выполнение следующего запроса выдаст разбиение по «Item No.»: Код: select count(*),"Item_No_" from "dbo"."КРОК$item_Ledger_Entry" group by "Item_No_" Код: select count(*),"Item_No_","Variant_Code" from "dbo"."КРОК$item_Ledger_Entry" group by "Item_No_","Variant_Code" Код: select count(*),"Item_No_","Drop_Shipment" from "dbo"."КРОК$item_Ledger_Entry" group by "Item_No_","Drop_Shipment" Код: select count(*),"Item_No_","Location_Code" from "dbo"."КРОК$item_Ledger_Entry" group by "Item_No_","Location_Code" Код: select count(*),"Item_No_","Posting_Date" from "dbo"."КРОК$item_Ledger_Entry" group by "Item_No_","Posting_Date" Последние два варианта – разбиение по «Item No.»+«Location Code» и «Item No.»+«Posting Date» - удовлетворяют правилу 2. и могут быть использованы в качестве индекса. Аналогично оптимизируются и остальные индексы таблицы «Item Ledger Entry». После подобных манипуляций, из 30 длинных ключей Navision обслуживаются лишь 10 коротких. Это существенно увеличивает скорость записи и модификации таблицы, что в свою очередь увеличивает скорость учетных операций. Кроме того, при выборках в коде по этой таблице, не будет неоднозначностей по поводу того, какой именно индекс будет использовать MSSQL (главное не забывать НЕ указывать SETCURRENTKEY). |
|
09.10.2007, 11:05 | #32 |
Участник
|
Цитата:
[codebox] TT := TIME; GLEntry.RESET; //GLEntry.SETCURRENTKEY("G/L Account No."); GLEntry.SETRANGE("G/L Account No.",'20.70'); IF GLEntry.FIND('-') THEN; MESSAGE('ok %1',TIME - TT);[/codebox] Время выполнения запроса - 4407ms. В Мониторе вижу, что SQL Plan = Clustered Index Scan(Test$G_L Entry$0)[3,1], SQL Index = "Операция Но.". Далее делаю так: [codebox] TT := TIME; GLEntry.RESET; GLEntry.SETCURRENTKEY("G/L Account No."); GLEntry.SETRANGE("G/L Account No.",'20.70'); IF GLEntry.FIND('-') THEN; MESSAGE('ok %1',TIME - TT);[/codebox] Время выполнения - 47ms. SQL Plan = Bookmark Lookup[3,1];Index Seek($1)[4,3], SQL Index = "Фин. Счет Но., Дата Учета, Операция Но.". Т.е., SETCURRENTKEY все-таки влияет на выбор ключа при выполнении запроса??? Или где я ошибся? PS: 3.70B, SQL2000. |
|
09.10.2007, 15:03 | #33 |
Участник
|
очень интересно!
Выложите, пожалуйста, SQL Statement для обоих случаев |
|
09.10.2007, 15:54 | #34 |
Участник
|
1) SELECT * FROM "Test$G_L Entry" WITH (READUNCOMMITTED) WHERE (("G_L Account No_"=?)) ORDER BY "Entry No_" OPTION (FAST 5)
2) SELECT * FROM "Test$G_L Entry" WITH (READUNCOMMITTED) WHERE (("G_L Account No_"=?)) ORDER BY "G_L Account No_","Posting Date","Entry No_" OPTION (FAST 5) |
|
09.10.2007, 16:21 | #35 |
Участник
|
Это изза идиотских хинтов FAST в конце запроса (интересна логика разработчиков микрософт, которые их решили использовать).
Попробуйте перестроить индексы и обновить статисику вашей таблицы: Код: DBCC DBREINDEX ('[Test$G_L Entry]') Код: UPDATE STATISTICS [Test$G_L Entry] |
|
09.10.2007, 16:32 | #36 |
Участник
|
Сделал. Ничего не изменилось.
|
|
09.10.2007, 16:40 | #37 |
Участник
|
Хотя нет. Сейчас получилось:
SELECT TOP 1 NULL FROM "Test$G_L Entry" WITH (READUNCOMMITTED) WHERE (("G_L Account No_"=?)) SQL Plan = Top[2,1];Index Seek($1)[4,2] |
|
09.10.2007, 16:58 | #38 |
Участник
|
SELECT TOP 1 NULL - это ISEMPTY а не FIND('-')
Вы что-то путаете, по-моему. |
|
09.10.2007, 17:25 | #39 |
Участник
|
Нет, точно FIND('-')
Сейчас проверил, получилось: Source Text = IF GLEntry.FIND('-') THEN; SQL Statement = SELECT * FROM "Test$G_L Entry" WITH (READUNCOMMITTED) WHERE (("G_L Account No_"=?)) ORDER BY "Entry No_" OPTION (FAST 5) SQL Plan = Sort[3,1];Bookmark Lookup[4,3];Index Seek($1)[5,4] Ничего не понимаю, через 5 мин. запустил: Source Text = IF GLEntry.FIND('-') THEN; SQL Statement = SELECT * FROM "Test$G_L Entry" WITH (READUNCOMMITTED) WHERE (("G_L Account No_"=?)) ORDER BY "Entry No_" OPTION (FAST 5) SQL Plan = Clustered Index Scan(Test$G_L Entry$0)[3,1] |
|
09.10.2007, 17:32 | #40 |
Участник
|
Изза хинтов (FAST 5) SQL тупит.
Вы перестроили индексы? Обновили статистику? |
|