20.03.2012, 12:34 | #1 |
Участник
|
Оптимизация запросов: CustInvoiceTrans + markupTrans
На одном из клиентов (версия системы 4.0, SQL 2005) стала жутко тормозить форма обработки фактуры по накладной клиента.
Причина - FactureEditLinesEngineCust_RU.dsExecuteMarkup Там в один курсор пытаются вернуть как markupTrans относящиеся к шапке, так и к строкам. Но поскольку обычные подзапросы в аксапте не работают, связка по шапке добавлена внутрь связки по строкам X++: select markupCursor where markupCursor.CustVendPosted_RU && ! markupCursor.ItemPosted_RU exists join tTrans where ( markupCursor.TransTableId == tTrans.TableId && markupCursor.TransRecId == tTrans.RecId && tTrans.SalesId == tJour.SalesId && tTrans.InvoiceId == tJour.InvoiceId && tTrans.InvoiceDate == tJour.InvoiceDate && tTrans.NumberSequenceGroup == tJour.NumberSequenceGroup ) || ( markupCursor.TransTableId == tJour.TableId && markupCursor.TransRecId == tJour.RecId ); SELECT * FROM MARKUPTRANS A WHERE (A.DATAAREAID='exp' AND A.CUSTVENDPOSTED_RU<>0 AND A.ITEMPOSTED_RU=0) AND EXISTS (SELECT 'x' FROM CUSTINVOICETRANS B WHERE (B.DATAAREAID='exp' AND ((A.TRANSTABLEID=64 AND A.TRANSRECID=B.RECID AND B.SALESID='РКЗП-00032212' AND B.INVOICEID='14294-11' AND B.INVOICEDATE='2012-03-13 00:00:00.000' AND B.NUMBERSEQUENCEGROUP='ТД') OR (A.TRANSTABLEID=62 AND A.TRANSRECID=5637435947) ))) Если сделать по нормальному, то все работает быстро. Но как это можно написать в Аксапте одним запросом непонятно. SELECT * FROM MARKUPTRANS A WHERE (A.DATAAREAID='exp' AND A.CUSTVENDPOSTED_RU=0 AND A.ITEMPOSTED_RU=0) AND ((A.TRANSTABLEID=62 AND A.TRANSRECID=5637435947) OR EXISTS (SELECT 'x' FROM CUSTINVOICETRANS B WHERE (B.DATAAREAID='exp' AND ((A.TRANSTABLEID=64 AND A.TRANSRECID=B.RECID AND B.SALESID='РКЗП-00032212' AND B.INVOICEID='14294-11' AND B.INVOICEDATE='2012-03-13 00:00:00.000' AND B.NUMBERSEQUENCEGROUP='ТД') )))) PS: Индексы перестраивались, статистика обновлялась PSS: План запроса во вложении (переименован в txt для загрузки) Последний раз редактировалось malex; 20.03.2012 в 12:39. |
|
20.03.2012, 12:54 | #2 |
Участник
|
может на 2 запроса разбить? 1ый по CustInvoiceTrans 2ой по CustInvoiceJour.
__________________
aLL woRk aNd nO JoY MAKes jAck a dULL Boy |
|
20.03.2012, 12:58 | #3 |
Участник
|
|
|
20.03.2012, 13:07 | #4 |
Участник
|
А если добавить директиву firstonly
X++: select markupCursor where markupCursor.CustVendPosted_RU && ! markupCursor.ItemPosted_RU exists join firstonly tTrans where (...) Интересно. А findRecord умеет работать с запросом который возвращает больше одной строки? Я как-то раньше не задумывался об этом. |
|
20.03.2012, 13:13 | #5 |
Участник
|
Умеет.
Фактически в датасорс тут передается все, что получено запросом, с учетом команды next. Я эту проблему в свое время победил. Топорно немного, но, насколько я знаю, до сих пор работает. Если другого решения не будет, постараюсь сегодня свое описать
__________________
If it ain't broke, take it apart and find out why (с) |
|
|
За это сообщение автора поблагодарили: S.Kuskov (5). |
20.03.2012, 13:13 | #6 |
Moderator
|
Я бы еще попробовал на сервере выключить параллелизм. Там план запроса распараллелен, чего для таких простеньких запросов ни в коем случае нельзя делать. Welcome -- Ax Database Configuration Checklist part 1 Искать по ключевому слову MAXDOP.
IMHO, проблема не в запросе как таковом, а в том что SQL Server кривой план запроса генерит. Я бы начал лечение с MAXDOP==1, а потом попробовал накатить свежие Service Pack и CU на сиквел... |
|
20.03.2012, 16:41 | #7 |
Участник
|
Ну раз все молчат, вверну свой быдлокод.
Собственно в решении ничего неожиданного нет. Заменил использование markupDS.findRecord на банальное накладываение range по заранее собранным RecId (индекс в таблице такой есть, так что это не проблема). А сбор RecId уже спокойно можно разбить на 2 запроса. Ниже уже конкретная реализация, если кому интересно. Метод dsExecuteMarkup по всему семейству сделал типа boolean, чтобы в форме FactureEditLines_RU у датасорса метод executeQuery имел вид X++: public void executeQuery() { // SOI --> /* factureEngine.dsExecuteMarkup(); // super(); // PS 1791 */ if (factureEngine.dsExecuteMarkup()) { super(); } // SOI <-- } X++: mrkDS.clearRanges(); // в потомках мы накладываем range, логично его сначала почистить return false; X++: public boolean dsExecuteMarkup() { MarkupTrans markupCursor; CustInvoiceJour tJour = custInvoiceJourDS.cursor(); CustInvoiceTrans tTrans; // SOI --> QueryBuildDataSource qbds; QueryBuildRange qbr; Set set = new Set(Types::Int64); SetEnumerator sE; counter counter; str range; // SOI <-- ; super(); // SOI --> qbds = markupDs.query().dataSourceTable(tablenum(MarkupTrans)); // собственно собираем все, что связано и со строками и с заголовком while select recid from markupCursor where markupCursor.CustVendPosted_RU && !markupCursor.ItemPosted_RU exists join tTrans where tTrans.SalesId == tJour.SalesId && tTrans.InvoiceId == tJour.InvoiceId && tTrans.InvoiceDate == tJour.InvoiceDate && tTrans.NumberSequenceGroup == tJour.NumberSequenceGroup && tTrans.TableId == markupCursor.TransTableId && tTrans.RecId == markupCursor.TransRecId { set.add(markupCursor.RecId); } while select recid from markupCursor where markupCursor.CustVendPosted_RU && !markupCursor.ItemPosted_RU && markupCursor.TransTableId == tJour.TableId && markupCursor.TransRecId == tJour.RecId { set.add(markupCursor.RecId); } // и лепим это все в range sE = set.getEnumerator(); while (sE.moveNext()) { counter++; if (counter > 15) // не по-научному, знаю, но зато работает :) { qbr = qbds.addRange(fieldnum(MarkupTrans, RecId)); qbr.value(range); range = strmin(); counter = 0; } range = queryRangeConcat(range, sE.current()); } if (!set.elements()) { range = sysQuery::value(0); // ничего не нашли - ничего и не покажем } if (range) { qbr = qbds.addRange(fieldnum(MarkupTrans, RecId)); qbr.value(range); } return true; // SOI <-- } Если у кого есть какие другие варианты или критика/дополнения - буду рад увидеть.
__________________
If it ain't broke, take it apart and find out why (с) |
|
20.03.2012, 21:15 | #8 |
Участник
|
Вы сделали распространенную ошибку, решив что на SQL-сервере выполняется напрямую SQL-запрос. На самом деле это не так. Там создается курсор, в который и "оборачивает" SQL-запрос. А вот у курсора и "чистого" запроса планы исполнения могут отличаться. Причем, как правило, отличия возникают именно при объединении по Exists Join.
Т.е. у Вас два варианта решения проблемы 1. Отказаться от Exists Join в пользу Inner Join. Поскольку у Вас ведущей является таблица строк накладных расходов, то связь получится по типу много-к-одному. Т.е. здесь даже не надо проверять на возможные дубли. Их и так не будет 2. Можно заняться оптимизацией. НО! Оптимизацией не того запроса, что Вы выцепили из профайлера, а обернутого в курсор. Т.е. Вам надо оптимизировать вот это PHP код:
__________________
- Может, я как-то неправильно живу?! - Отчего же? Правильно. Только зря... |
|
|
За это сообщение автора поблагодарили: AlGol (1), Romb (1), malex (1), Alexanderis.ua (1). |
21.03.2012, 01:11 | #9 |
Участник
|
м-да. а ларчик-то просто открывался...
всего-то нужно было мозг включить. из-за режима работы "ааа, мля, давай срочно меняй" привык делать все в лоб. надо что-то решать
__________________
If it ain't broke, take it apart and find out why (с) |
|
|
Похожие темы | ||||
Тема | Ответов | |||
оптимизация запроса статистики по клиенту | 2 | |||
Просмотрщик запросов QueryBrowser DAX 3.0 SP4 | 30 | |||
Оптимизация запросов к БД в коде | 57 | |||
Оптимизация запросов | 6 | |||
Оптимизация запросов | 3 |
|