Вы находитесь на странице: 1из 38

«Ну и запросы у вас, сказала база данных и повисла».

Данный документ подготовлен после общения на форуме www.sapforum.biz, так сказать


спасибо камрадам: №1, Darth, Tav_48, Bdmalex и.. если кого забыл, обращайтесь, впишем
так же ваши имена… Отдельное персональное спасибо и пиво, виски, капучино при встрече,
по желанию Dmitriy-ю и Удав-у, внесших много ценных замечаний по документу.

Комментарии и замечания приветствуются… можно на форуме, можно на адрес


uukrul@hotmail.com.

Выбор данных для обработки во внутренние таблицы.................................................................. 2


Использование агрегатных функций................................................................................................ 4
Ограничение выборки в условиях WHERE..................................................................................... 7
Проверка наличия значения в таблице........................................................................................... 11
Буферизация чтения данных............................................................................................................12
Объединение таблиц в запросах......................................................................................................13
COMMIT WORK...............................................................................................................................18
Определение типов внутренних таблиц.........................................................................................25
Работа с внутренними таблицами: LOOP, READ, DELETE и т.д................................................ 31
Как проводить тестирование........................................................................................................... 37

В общем как не крутись, а писать на ABAP приходится, это какие-то


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

Я не претендую на истину, все же некоторые вопросы оптимизации очень специфичны и если


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

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

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

Для понимания описаний, желательны хотя бы минимальные знания языка ABAP, хотя бы в
рамках обзора книги «Разработка приложений SAP R/3" авторы Рюдигер Кречмер и
Вольфганг Вейс.

Страница 1 of 38 по материалам форума www.sapforum.biz (Версия: 1.0)


Выбор данных для обработки во внутренние таблицы.
Время диалоговых процессов в системе, как известно, ограничено. Стандартно диалоговых
процесс в системе может длиться не более 600 секунд и дальше ваша задача будет завершена
принудительно, правда на практике, администраторы системы, это время обычно
увеличивают, но и мы можем тоже, кое-что докрутить в своих запросах, чтобы и они
работали чуть быстрее. И так есть таблица MKPF — заголовки документов материала.
Варианта выбора и обработки значений есть два, первый в цикле оператора SELECT.
ENDSELECT., второй выбор во внутреннюю таблицу и затем обработка данных в цикле
оператора LOOP AT <внутренняя таблица>. ENDLOOP. Так же у этих операторов есть
различные варианты их вызова.

Для тестирования будет использоваться внутренняя таблица.

DATA: lt_mkpf LIKE mkpf OCCURS 1 WITH HEADER LINE.

Варианты выборки данных.

Если структура, куда выбирают данные, совпадает со структурой из выбираемой таблицы, то


можно выбрать данные наиболее быстрым способом

ВремВып: 1.355.738 микросек.


a1) SELECT * INTO TABLE lt_mkpf FROM mkpf.

При реальных запросах, не все поля находящиеся в таблице MKPF, нужны во внутренней
таблице LT_MKPF, тогда такую структуру надо объявить с перечислением только
используемых полей. Если это так сделано, тогда нужно использовать расширение операции
INTO в виде «INTO CORRESPONDING FIELDS OF» т. е., система сама разложит
результат, сделав правильное соответствие выбираемых полей из таблицы, полям в
структуре. При этом если выбираются все поля, то время выполнения различается в районе
0,1%, но при этом всегда время выполнения первого запроса меньше чем второго.

ВремВып: 1.369.663 микросек.


b1) SELECT * INTO CORRESPONDING FIELDS OF TABLE lt_mkpf
FROM mkpf.

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


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

Как уже отмечалось выше, более предпочтительнее, выглядит данный запрос, если нам
нужны не все поля из таблицы, например, хотим выбрать следующие поля: MBLNR —
номер документа, MJAHR — год документа, XBLNR — ссылочный номер, BKTXT —
краткий текст заголовка документа. Тут уже время отличается и существенно.

ВремВып: 360.492 микросек.


c1) SELECT MBLNR MJAHR XBLNR BKTXT
INTO CORRESPONDING FIELDS OF TABLE lt_mkpf
FROM mkpf.

Как видим скорость выборки стала в среднем в 4 раза быстрее. Однако если сделать

Страница 2 of 38 по материалам форума www.sapforum.biz (Версия: 1.0)


следующий вариант вызова, когда внутренняя таблица содержит только необходимый набор
полей, то, как видим, операция «INTO TABLE» будет быстрее, в среднем от 15% до 20%

DATA: BEGIN OF lt_mkpf_short OCCURS 0,


mblnr LIKE mkpf-mblnr,
mjahr LIKE mkpf-mjahr,
xblnr LIKE mkpf-xblnr,
bktxt LIKE mkpf-bktxt,
END OF lt_mkpf_short.

ВремВып: 389.617 микросек.


d1) SELECT mblnr mjahr xblnr bktxt
INTO CORRESPONDING FIELDS OF TABLE lt_mkpf_short
FROM mkpf.

ВремВып: 314.952 микросек.


e1) SELECT mblnr mjahr xblnr bktxt
INTO TABLE lt_mkpf_short
FROM mkpf.

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


Если порядок следования полей внутренней таблицы такой же, как и в таблице базы данных,
то скорость выбора будет быстрее, при этом, если вы выбираете все поля. Например,
требуется выбрать все поля из таблицы MKPF.

DATA: lt_mkpf LIKE mkpf OCCURS 1 WITH HEADER LINE.

ВремВып: 1.323.437 микросек.


f1) SELECT MBLNR MJAHR VGART BLART BLAUM BLDAT BUDAT
CPUDT CPUTM AEDAT USNAM TCODE XBLNR BKTXT
FRATH FRBNR WEVER XABLN AWSYS BLA2D TCODE2
BFWMS EXNUM SPE_BUDAT_UHR SPE_BUDAT_ZONE
LE_VBELN SPE_LOGSYS SPE_MDNUM_EWM GTS_CUSREF_NO
KNUMV
INTO CORRESPONDING FIELDS OF TABLE lt_mkpf
FROM mkpf.

ВремВып: 1.349.799 микросек.


g1) SELECT * INTO TABLE lt_mkpf FROM mkpf.

Как видим перечисление всех полей с оператором «CORRESPONDING FIELDS», работает


чуть быстрее, чем операция «INTO TABLE», разница не существенна, но опять, же если
выжимать максимум, то следует задуматься. Однако если вы вдруг перепутаете порядок
следования полей, то получите вместо ускорения запроса, наоборот его замедление.

ВремВып: 1.348.756 микросек.


h1) SELECT CPUDT CPUTM AEDAT USNAM TCODE XBLNR BKTXT
MBLNR MJAHR VGART BLART BLAUM BLDAT BUDAT
BFWMS EXNUM SPE_BUDAT_UHR SPE_BUDAT_ZONE
FRATH FRBNR WEVER XABLN AWSYS BLA2D TCODE2
KNUMV
LE_VBELN SPE_LOGSYS SPE_MDNUM_EWM GTS_CUSREF_NO
INTO CORRESPONDING FIELDS OF TABLE lt_mkpf
FROM mkpf.

На разных выборках время выполнения запроса H1, фактически вышло на скорость


выполнения запроса G1.

Страница 3 of 38 по материалам форума www.sapforum.biz (Версия: 1.0)


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

ВремВып: 1.590.766 микросек.


I1) SELECT * INTO lt_mkpf FROM mkpf.

ENDSELECT.

ВремВып: 1.611.237 микросек.


k1) SELECT * INTO CORRESPONDING FIELDS OF lt_mkpf FROM mkpf.

ENDSELECT.

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

ВремВып: 561.934 микросек.


j1) SELECT MBLNR
MJAHR
VGART
BLART
INTO CORRESPONDING FIELDS OF lt_mkpf
FROM mkpf.

ENDSELECT.

Как видим скорость выполнения запроса J1 в среднем в три раза выше, чем запросов I1. По
поводу перечисления полей и порядка их следования, правила такие же, как и для запросов
F1 и H1, т. е. порядок следования полей важен.

Выводы (скорость): Наиболее быстрым, будет вариант выбора данных, во внутреннюю


таблицу используя запрос вида E1, при этом внутренняя таблица должна содержать
только те поля, которые нам необходимы для работы. Порядок следования полей,
желательно должен совпадать с порядком следования полей из выбираемой таблицы.

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

Использование агрегатных функций.


Агрегатные функции это использование таких конструкций как SUM, COUNT, MAX и т. д. в
запросах. Однозначно сказать, что будет быстрее, т. е. выбрать данные во внутреннюю
таблицу и посчитать все самому или же попросить, чтобы за нас все посчитала база данных
Страница 4 of 38 по материалам форума www.sapforum.biz (Версия: 1.0)
сложно. Так же, наблюдается зависимость работы данных функции от типа базы данных, на
которой работает система. Данные тесты проводились для базы данных DB6, рисунок 1:
Database.png. Большая вероятность, что для других баз данных результаты будут отличными,
от полученных в данном описании.

Рисунок 1: Database.png

В общем виде ситуация следующая, есть запрос вида

SELECT SUM( menge ) FROM mkpf WHERE xauto = 'X' AND meins = 'ST'.

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

DATA: l_menge LIKE mseg-menge.


DATA: BEGIN OF lt_mseg OCCURS 0,
menge LIKE mseg-menge,
END OF lt_mseg.

ВремВып: 1.764.975 микросек.


a2) SELECT SUM( menge ) INTO l_menge FROM mseg
WHERE xauto = 'X' AND meins = 'ST'.

ВремВып: 1.524.474 микросек.


b2) SELECT menge INTO TABLE lt_mseg
FROM mseg WHERE xauto = 'X' AND meins = 'ST'.

CLEAR: l_menge.
LOOP AT lt_mseg.
l_menge = l_menge + lt_mseg-menge.
ENDLOOP.

В среднем время выполнения второго запроса B2, оказывается быстрее, чем использование
агрегатной функции, хотя конечно написать кода приходится больше и выглядит он не так
красиво как в запросе A2. В среднем после более чем десяти прогонов, время выполнения
запроса B2 было быстрее от 7% до 15%. В среднем было около 10% выигрыш в скорости
работы.

Примечание: Об оптимизации циклов по внутренним таблицам, будет рассказано ниже.


Использование такой оптимизации, даст еще прирост производительности для запроса B2.

С функцией COUNT результат еще интереснее

DATA: l_menge LIKE mseg-menge,


l_count LIKE sy-tabix.
DATA: BEGIN OF lt_mseg OCCURS 0,
menge LIKE mseg-menge,
END OF lt_mseg.

ВремВып: 2.880.489 микросек.


c2) SELECT COUNT( * ) INTO l_count FROM mseg
WHERE xauto = 'X' AND meins = 'ST'.

Страница 5 of 38 по материалам форума www.sapforum.biz (Версия: 1.0)


ВремВып: 2.152.414 микросек.
d2) SELECT menge INTO TABLE lt_mseg
FROM mseg WHERE xauto = 'X' AND meins = 'ST'.

CLEAR: l_count.
LOOP AT lt_mseg.
l_count = l_count + 1.
ENDLOOP.

По факту, скорость выполнения запроса D2 меньше, чем выполнения C2. При этом после 50
прогонов в паре случаев было, что D2 почти был равен по скорости запросу С2. Среднее
значение процента, по сумме прогонов получилось в районе от 10% до 15% в скорости
выполнения.

Функции MIN и MAX показывают обратный результат, т. е. запрос к базе отрабатывает


быстрее, чем локальная обработка.

DATA: l_menge LIKE mseg-menge,


DATA: BEGIN OF lt_mseg OCCURS 0,
menge LIKE mseg-menge,
END OF lt_mseg.

ВремВып: 2.394.439 микросек.


e2) SELECT menge INTO TABLE lt_mseg
FROM mseg WHERE xauto = 'X' AND meins = 'ST'.
SORT lt_mseg BY menge DESCENDING.
READ TABLE lt_mseg INDEX 1.

ВремВып: 2.332.711 микросек.


f2) SELECT MAX( menge ) INTO l_menge FROM mseg
WHERE xauto = 'X' AND meins = 'ST'.

ВремВып: 2.335.711 микросек.


G2) SELECT menge INTO TABLE lt_mseg
FROM mseg WHERE xauto = 'X' AND meins = 'ST'
ORDER BY menge DESCENDING.
READ TABLE lt_mseg INDEX 1.

При этом если в запрос E2 добавить сортировку, используя конструкцию ORDER BY, т. е.
исключаем локальную сортировку после запроса, пример запроса G2, то скорость
практически была такой же, как и запроса F2. Индексов на поле, по которому выполнялась
сортировка, не было. В целом скорость выполнения между запросами E2 и F2, колебалась в
районе от 3% до 5%.

Примечание: При проверке работы агрегатных функций для базы данных Oracle 9, по
таблицам COSS/COCP/COEP, функция суммирования работала быстрее, чем локальная
выгрузка во внутренние таблицы с последующей обработкой в цикле по внутренней
таблице. При проверке суммирования, GROUP BY был по индексу.

Выводы (скорость): Так как работа агрегированных функций очень специфична в


зависимости от используемой базы данных, то сделайте тестирование работы этих
функций на своей системе, чтобы определить наиболее быстрый вариант. Для текущей
используемой системы рекомендации оказались следующими, для функций COUNT и
SUM по возможности нужно было использовать локальную обработку, с чтением
данных во внутренние таблицы и последующим расчетом, после запросов, а для функций
MIN и MAX, наоборот, лучше оказалось выполнять обработку, используя их.
Страница 6 of 38 по материалам форума www.sapforum.biz (Версия: 1.0)
Выводы (память): С точки зрения использования памяти, всегда наиболее
оптимальным будет вариант использования агрегированных функций, так как в этом
случае все расчеты идут на уровне запроса к базе данных.

Ограничение выборки в условиях WHERE.


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

ВремВып: 1.835.003 микросек.


a3) SELECT * FROM SBOOK INTO SBOOK_WA.
CHECK: SBOOK_WA-CARRID = 'LH' AND
SBOOK_WA-CONNID = '0400'.
ENDSELECT.

ВремВып: 72.706 микросек.


b3) SELECT * FROM SBOOK INTO SBOOK_WA
WHERE CARRID = 'LH' AND
CONNID = '0400'.
ENDSELECT.

Как видим, запросы с ограничением условия в WHERE, работают быстрее, в разы. Кстати,
известный факт при использовании довеска «FOR ALL ENTRIES IN», не забываем делать
проверку на наличие данных в таблице, используемой в условии. Потому что, если данных
там нет, то будет выполнен полный проход по таблице выборки, а это плачевно отражается на
скорости выбора данных, даже если вам нужно получить все записи.

DATA: BEGIN OF lt_mseg OCCURS 0,


menge LIKE mseg-menge,
END OF lt_mseg.
DATA: BEGIN OF lt_meins OCCURS 0,
meins LIKE mseg-meins,
END OF lt_meins.

ВремВып: 2.190.261 микросек.


с3) CLEAR: lt_meins[].
SELECT menge INTO TABLE lt_mseg
FROM mseg
FOR ALL ENTRIES IN lt_meins
WHERE meins = lt_meins-meins.

ВремВып: 1.748.061 микросек.


d3) SELECT menge INTO TABLE lt_mseg
FROM mseg.

Страница 7 of 38 по материалам форума www.sapforum.biz (Версия: 1.0)


Как видим, простой проход по таблице выполняется быстрее, чем тот же самый проход, но с
пустой таблицей в условии FOR ALL ENTRIES. Поэтому отслеживайте, чтобы таблица в
таком условии не была пустой. Если в ходе работы программы у вас может возникать такая
ситуация, то возможно более правильным будет сделать два запроса в программе, в
зависимости от заполнения таблицы lt_meins.

При задании ограничения по полям очень важно, чтобы в системе были подходящие
индексы, иначе при запросе, система будет выполнять полный проход по таблице. Наличие
индексов к таблицам можно посмотреть в транзакции SE11. В режиме просмотра данных
таблицы, на панели инструментов есть кнопка «Индексы», рисунок 2: SE11-4.png.

Рисунок 2: SE11-4.png
Кроме показанных в списке индексов, где первая колонка это имя индекса, всегда существует
еще так называемый индекс первичного ключа с именем «0», в этот индекс входят все поля,
которые отмечены в таблице как ключевые. В данном случае, для таблицы MKPF, существует
только один дополнительный индекс, построенный по дате проводки документов материала.
Для просмотра данных индекса сделайте двойной клик в строке.

Во время построения запроса, оптимизатор базы данных, анализирует поля в условии


WHERE и старается найти подходящий индекс, если такой не найден, то будет выполнен
полный перебор записей таблицы, что конечно же катастрофически скажется на скорости
выбора данных. Для большинства баз данных, порядок перечисления полей в условии
WHERE не важен, т.е. если есть индекс по полям 1 и 2, а в ограничении порядок полей стоит
2 и 1, то оптимизатор теоретически найдет правильный индекс и будет его применять. Для
базы DB2 это правило работает. Так же оптимизатор старается подобрать индекс, если есть
не полная совместимость по используемым полям, т.е. например индекс содержит только
некоторые из полей WHERE или же, наоборот, в индексе содержится больше полей, чем в
запросе. При наличии нескольких подходящих индексов, система будет использовать тот
индекс, у которого лучшая статистика. Статистику индекса умеют собирать системные
администраторы базы данных. В общем виде статистика, в терминах системы «Estimated

Страница 8 of 38 по материалам форума www.sapforum.biz (Версия: 1.0)


Costs» это число, значение которого рассчитывается в зависимости от величины различных
(неодинаковых) записей в таблице, чем оно меньше, тем более лучшим считается индекс.

По возможности следует избегать оператора NOT в условиях, так как при использовании
такой конструкции поля определенные через NOT не обрабатываются как пригодные для
индекса. Поэтому нужно стараться избегать операции отрицания. Так же это относится и к
операциям не равно – «<>», такие поля так же пропускаются индексом. Для примера
использовалась таблица MSEG с анализом использования индексов в транзакции ST05. В
системе создан завод U100 и для этого завода созданы два склада с кодами 0001 и 0002.
Простым запросом выбираем документы, проведенные по одному или другому складу.
Например, выберем документы, проведенные по складу 0001.

Данные для работы:


data: lt_mseg like mseg occurs 1 with header line.

ВремВып: 1.660 микросек.


e3) SELECT * INTO TABLE lt_mseg
FROM mseg WHERE werks = '1000' AND lgort = '0001'.

ВремВып: 16.215 микросек.


f3) SELECT * INTO TABLE lt_mseg
FROM mseg WHERE werks = '1000' AND lgort <> '0002'.

Как видим простое указание, выбирать документы, где завод не равен 0002, т.е. только
документы с заводом 0001, приводит к значительному увеличению времени работы запроса.
Поверьте что завода там действительно только два. Причина кроется в выборе индексов для
запросов E3 и F3. Для запроса E3 система использовала созданный индекс по заводу и
складу с именем MSEG~TST, рисунок 3: ST05-TST-0.png.

Рисунок 3: ST05-TST-0.png

Для запроса F3, этот индекс уже не использовался, как видим, был взят индекс MSEG~M,
причем в этом индексе, использовалось, только одно поле с кодом завода, данные кода склада
<> 0002, были вообще пропущены, рисунок 4: ST05-TST-2.png, т.е. по факту оптимизатор
выбирал индекс исходя их поля «Завод», и совсем не интересовался полем «Склад».

Страница 9 of 38 по материалам форума www.sapforum.biz (Версия: 1.0)


Рисунок 4: ST05-TST-2.png

Исходя из построения плана запроса, можно так же сделать следующий вывод, если для
завода будет создано, например, много складов, например завод 2200 в IDES содержит более
30 складов и стоит задача выбрать все документы за исключением склада 0001, то более
правильно, будет перечислить в условии все склады в используя команду IN, за исключением
склада 0001, чем указать в условии значение «не равно складу 0001».

ВремВып: 274 микросек.


g3) select * into table lt_mseg
from mseg where werks = '2200' and
lgort IN ('0002', '0003', '0004', '0088', '0131', <Все
склады>, 'WL41', 'WL42').

ВремВып: 9.981 микросек.


h3) select * into table lt_mseg
from mseg where werks = '2200' and lgort <> '0001'.

Как видим скорость в выполнении запросов существенно различается, а все потому, что для
запроса H3, использовался не тот индекс, по аналогии с рисунком 4: ST05-TST-2.png. Хотя
конечно писать запрос G3 дольше, но как пример можно использовать переменную типа
RANGES выбрать в нее нужные все склады завода 2200 и удалить лишние, например 0001.

Выводы (скорость): Если возможно, то при любых запросах к базе данных, стараемся
ограничивать объем выбираемых данных, используя конструкцию WHERE, при этом
особое внимание обращаем на наличие индексов по полям, участвующим в ограничениях
выбора, так как это значительно ускоряет обработку данных. Не используйте в
ограничениях WHERE операции отрицания NOT или не равно («<>»), раскладывайте
эти конструкции на более понятные для оптимизатора варианты равно или операцию
IN. Иначе, при выборе, система будет выполнять полный проход по таблице данных или
будет использовать не корректные для данной ситуации индексы.

Выводы (память): При выборе данных во внутренние таблицы, ограничение WHERE,


всегда позволяет получить из базы меньший объем данных и соответственно получить

Страница 10 of 38 по материалам форума www.sapforum.biz (Версия: 1.0)


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

Проверка наличия значения в таблице.


Фактически это данные справки системы SAP, но кто же читает инструкцию на холодильник,
до начала его использования. И так задача проверить наличие записи по определенным
ключам в таблице. Варианты из классики системы.

ВремВып: 764 микросек.


a4) SELECT * FROM SBOOK INTO SBOOK_WA
WHERE CARRID = 'LH'.
EXIT.
ENDSELECT.

ВремВып: 103 микросек.


b4) SELECT * FROM SBOOK INTO SBOOK_WA
UP TO 1 ROWS
WHERE CARRID = 'LH'.
ENDSELECT.

По факту, в среднем, скорость работы отличается от 6 до 8 раз, так что не забываем такой
оператор как UP TO <x> ROWS и уже ни в коем случае не используем для проверки
наличия агрегатную функцию COUNT( * ), результат будет совсем грустным.

ВремВып: 1.424 микросек.


c4) DATA l_count like sy-tabix.
SELECT COUNT( * ) INTO l_count
FROM SBOOK UP TO 1 ROWS
WHERE CARRID = 'LH'.

ВремВып: 103 микросек.


d4) SELECT SINGLE * FROM sbook INTO sbook_wa
WHERE carrid = 'LH' .

Еще один способ проверить наличие значения в таблице это использовать конструкция
SELECT SINGLE. Скорость выполнения запроса D4, будет аналогичная запросу B4. С точки
зрения интерфейса к базе данных, оба запроса B4 и D4, выдадут одинаковую инструкцию на
уровень базы данных. Однако, если использовать расширенную проверку программы,
транзакция SLIN, то будет сгенерировано следующее предупреждение:

In "SELECT SINGLE ...", the WHERE condition for the key field "BOOKID" does not
test for equality. Therefore, the single record in question may not be unique.

Суть предупреждения, сводится к тому, что для запросов вида SELECT SINGLE, система
предполагает всегда задание уникального ключа в условии отбора WHERE, в данном же
случае, ограничения условия WHERE в запросе, не обеспечивают такой уникальности, то
система сообщает, что может существовать более одной записи для запроса. Так как это
предупреждение, выдается только при расширенной проверке программы, то это уже дело
разработчика, обращать на него внимание или нет.

Страница 11 of 38 по материалам форума www.sapforum.biz (Версия: 1.0)


Выводы (скорость): Для проверки наличия значений в таблице, используем конструкцию
с UP TO <X> ROWS. Кстати, документация по разработке SAP, так же рекомендует
данную инструкцию.

Выводы (память): Для оптимизации памяти, если мы проверяем только наличии


записи в базе данных, вместо выбора всех полей, лучше указать одно поле, на пример
CARRID. Так же, такая конструкция, влияет и на скорость обработки, при этом
замечена следующая закономерность, время запроса B4 уменьшается на 6-7
микросекунд, а запроса D4 на 10-11 микросекунд, т.е. время запроса D4 поле 50 тестовых
прогонов было всегда меньше, хотя 4-5 микросекунд, величина практически не
ощутимая.

Буферизация чтения данных


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

ВремВып: 83 микросек.
а5) SELECT SINGLE * FROM T100 INTO T100_WA
BYPASSING BUFFER
WHERE SPRSL = 'D' AND
ARBGB = '00' AND
MSGNR = '999'.

ВремВып: 5 микросек.
b5) SELECT SINGLE * FROM T100 INTO T100_WA
WHERE SPRSL = 'D' AND
ARBGB = '00' AND
MSGNR = '999'.

Выводы (скорость): Если не знаем зачем, то никогда не отключаем буферизацию


чтения данных. Для некоторых таблиц базы, вы можете обратить внимание, что
операции чтения одинаковы, не зависимо от наличия директивы BYPASSING BUFFER,
это связано с техническими параметрами настройки таблиц участвующих в запросе,
для просмотра технических параметров таблиц, можно воспользоваться транзакцией
SE13, рисунок 3: SE13.png. Как видим для таблицы MSEG буферизация запрещена, а для
таблицы T100, буферизация разрешена, поэтому для таблицы MSEG директива
BYPASSING BUFFER не актуальна.

Выводы (память): С точки зрения работы с системой вы можете влиять на


буферизацию таблиц. SAP допускает такую модификацию технических параметров
настройки, без запроса ключа на модификацию объекта. В общем итоге таблицы с
включенной буферизацией используют специальную область памяти «table buffer»,
которая имеет свой ограниченный размер. При чтении данных, для таблиц с
включенной буферизацией, в общем виде система, проверят наличие записи в буфере, и
только в случае ее отсутствия, производит чтение с диска. Если данные в таблице из
категории, «часто изменяемые», то при включенной буферизации запросы на чтение
могут выбирать уже не актуальные данные. Для своих Z-таблиц использовать
буферизацию или нет решать вам, базируясь на логике использования таблиц в
разрабатываемых приложениях, чаще всего в приложении буферизируются таблицы
справочников. Для таблиц SAP, изменять тип буферизации крайне не желательно.
Последствия могут быть катастрофическими.

Страница 12 of 38 по материалам форума www.sapforum.biz (Версия: 1.0)


Рисунок 5: SE13.png

Объединение таблиц в запросах.


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

ВремВып: 6.292 микросек.


a6) SELECT * FROM SPFLI INTO SPFLI_WA.
SELECT * FROM SFLIGHT INTO SFLIGHT_WA
WHERE CARRID = SPFLI_WA-CARRID AND
CONNID = SPFLI_WA-CONNID.
ENDSELECT.
ENDSELECT.

ВремВып: 7.673 микросек.


b6) SELECT * INTO WA
FROM SPFLI AS P
INNER JOIN SFLIGHT AS F ON
P~CARRID = F~CARRID AND
P~CONNID = F~CONNID.
ENDSELECT.

Как видим, вложенные запросы работают быстрее, чем выборка с использованием


объединения, хотя если немного переделать запрос, то результат будет уже другим.

ВремВып: 28.083.839 микросек.


c6) TABLES: mkpf, mseg.

SELECT * FROM mkpf.


SELECT * FROM mseg
WHERE mblnr = mkpf-mblnr AND
mjahr = mkpf-mjahr.
ENDSELECT.
ENDSELECT.

ВремВып: 16.145.212 микросек.


d6) DATA: lt_mseg LIKE mseg.

SELECT * INTO CORRESPONDING FIELDS OF lt_mseg


FROM mkpf AS m

Страница 13 of 38 по материалам форума www.sapforum.biz (Версия: 1.0)


INNER JOIN mseg AS s ON
m~mblnr = s~mblnr AND
m~mjahr = s~mjahr.
ENDSELECT.

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

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

При выполнении соединения, важным условием является наличие индексов по полям


соединения таблиц. В большинстве случае, при наличии нужных индексов, оптимизатор базы
данных правильно выбирает необходимые индексы и делает соединение. Однако, в
некоторых случаях может быть ситуация когда нужно явно указать использование индекса,
для этого в запросах есть специальная команда %_HINTS. В случае базы данных Oracle при
выборе из таблицы MARA, например, нужно принудительно использовать индекс с именем
«Т», тогда запрос будет иметь следующий вид:

e6) SELECT * UP TO 10 ROWS FROM mara


WHERE MTART = 'HAWA' AND
MATKL = '100'
%_HINTS ORACLE 'index(mara"T")'.

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

Выводы (скорость): Если вы не уверены, в том, что делаете, лучше расширение типа
%_HINTS, в запросах не использовать, но если решили применить такую конструкцию,
то, как минимум, сообщите об этом администратору базы данных.

Из опыта соединения таблиц и подзапросов, как известно, при больших объемах данных,
вложенные запросы существенно замедляют быстродействие основного. Однако была задача,
выбрать документы материала с исключением из выборки сторнированных документов.
Исходя из структуры данных и того как система хранит признак сторно, это довольно не
простая задача, при большом объеме документов, более > 1.000.000 позиций, так как в
позиции прямого документа в таблице MSEG нет ссылки на сторнирующий. Поэтому
построенный обычный запрос по загрузке данных во внутреннюю таблицу, с последующим
отсечением сторнированных документов, в цикле по полученным записям используя
единичную выборку из MSEG (SELECT SINGLE ...) и при положительном ее результате
(sy-subrc = 0) – удаление позиции из внутренней таблицы, оказался неприемлемым. Система
Страница 14 of 38 по материалам форума www.sapforum.biz (Версия: 1.0)
падала в дамп по таймауту. Поэтому, в этом случае, подзапросы оказались более
оптимальными, чем локальная обработка внутренних таблиц. Конечный запрос имел такой
вид.

f6) SELECT mseg~mblnr mseg~mjahr mseg~zeile mseg~bwart mseg~xauto


mseg~matnr mseg~werks mseg~lgort mseg~insmk mseg~shkzg
mseg~dmbtr mseg~bwtar mseg~menge mseg~bustm mkpf~budat
INTO TABLE lt_mseg
FROM mseg
JOIN mkpf ON mkpf~mblnr = mseg~mblnr AND
mkpf~mjahr = mseg~mjahr
WHERE mseg~matnr IN s_matnr
AND mseg~werks IN s_werks
AND mseg~lgort IN s_lgort
AND mseg~sobkz = ' '
AND mseg~smbln = ' '
AND mkpf~budat IN r_date
AND NOT EXISTS ( SELECT * FROM *mseg WHERE smbln = mseg~mblnr
AND sjahr = mseg~mjahr
AND smblp = mseg~zeile ).

Вот такой вот частный случай оптимизации с использованием подзапросов. Вполне


возможно, что на вашей системе этот запрос может быть не оптимальным.

Выводы (скорость): Всегда нужно моделировать несколько вариантов выполнения


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

Выводы (память): С точки зрения памяти, использование подзапросов, более


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

Соединение большой таблицы с относительно маленькой. Такие задачи обычно возникают


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

* Общие данные для обработки


DATA: BEGIN OF ls_t006a,
msehi LIKE t006a-msehi,
mseht LIKE t006a-mseht,
msehl LIKE t006a-msehl,
END OF ls_t006a.

DATA: BEGIN OF lt_t006a OCCURS 0,


msehi LIKE t006a-msehi,
mseht LIKE t006a-mseht,
msehl LIKE t006a-msehl,
END OF lt_t006a.

DATA: BEGIN OF lt_mseg_short OCCURS 0,


mblnr LIKE mseg-mblnr,

Страница 15 of 38 по материалам форума www.sapforum.biz (Версия: 1.0)


mjahr LIKE mseg-mjahr,
zeile LIKE mseg-zeile,
bwart LIKE mseg-bwart,
matnr LIKE mseg-matnr,
menge LIKE mseg-menge,
meins LIKE mseg-meins,
mseht LIKE t006a-mseht,
msehl LIKE t006a-msehl,
END OF lt_mseg_short.

Выполним выборку основных данных из таблицы MSEG – позиции документа материала, а


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

ВремВып: 10.677.944 микросек.


g6) CLEAR: lt_mseg_short[], lt_t006a[].
SELECT mblnr
mjahr
zeile
bwart
matnr
menge
meins
INTO TABLE lt_mseg_short
FROM mseg.

LOOP AT lt_mseg_short.
SELECT SINGLE mseht msehl INTO CORRESPONDING FIELDS OF ls_t006a
FROM t006a WHERE spras = sy-langu AND
msehi = lt_mseg_short-meins.

lt_mseg_short-mseht = ls_t006a-mseht.
lt_mseg_short-msehl = ls_t006a-msehl.
MODIFY lt_mseg_short.
ENDLOOP.

Сделаем то же самое, но теперь используем соединение таблиц MSEG и T006A, код будет
значительно короче и выполняться будет быстрее

ВремВып: 6.795.740 микросек.


h6) CLEAR: lt_mseg_short[], lt_t006a[].
SELECT m~mblnr
m~mjahr
m~zeile
m~bwart
m~matnr
m~menge
m~meins
t~mseht
t~msehl
INTO TABLE lt_mseg_short
FROM mseg AS m
JOIN t006a AS t ON t~spras = sy-langu AND
t~msehi = m~meins.

Значительно лучше, но данные результаты можно еще улучшить, для этого модифицируем
запрос G6. Путем вынесения чтения списка единиц измерения в отдельный запрос и затем в
цикле будем читать данные из внутренней таблицы.

ВремВып: 5.563.088 микросек.

Страница 16 of 38 по материалам форума www.sapforum.biz (Версия: 1.0)


i6) CLEAR: lt_mseg_short[], lt_t006a[].
SELECT mblnr
mjahr
zeile
bwart
matnr
menge
meins
INTO TABLE lt_mseg_short
FROM mseg.

SELECT msehi
mseht
msehl
INTO TABLE lt_t006a
FROM t006a
WHERE spras = sy-langu.

LOOP AT lt_mseg_short.
READ TABLE lt_t006a WITH KEY msehi = lt_mseg_short-meins.
lt_mseg_short-mseht = ls_t006a-mseht.
lt_mseg_short-msehl = ls_t006a-msehl.
MODIFY lt_mseg_short.
ENDLOOP.

Как видим скорость выполнения этого запроса самая минимальная. Однако есть еще один
вариант решения данной задачи, состоящий в том, чтобы объединить варианты G6 и I6.

ВремВып: 5.889.013 микросек.


j6) CLEAR: lt_mseg_short[], lt_t006a[].
SELECT mblnr
mjahr
zeile
bwart
matnr
menge
meins
INTO TABLE lt_mseg_short
FROM mseg.

LOOP AT lt_mseg_short.
READ TABLE lt_t006a WITH KEY msehi = lt_mseg_short-meins.
IF sy-subrc <> 0.
CLEAR: ls_t006a.
SELECT SINGLE msehi mseht msehl INTO ls_t006a
FROM t006a WHERE spras = sy-langu AND
msehi = lt_mseg_short-meins.
APPEND ls_t006a TO lt_t006a.
lt_mseg_short-mseht = ls_t006a-mseht.
lt_mseg_short-msehl = ls_t006a-msehl.
ELSE.
lt_mseg_short-mseht = lt_t006a-mseht.
lt_mseg_short-msehl = lt_t006a-msehl.
ENDIF.
MODIFY lt_mseg_short.
ENDLOOP.

Т.е. суть выборки заключается в том, чтобы выполнять выбор единиц измерения из базы
данных, только в том случае, если во внутренней таблице измерения нет нужной нам
величины. В данном случае этот вариант оказался немного медленнее, чем вариант I6, но это
связано с тем, что тесты делаются на системе IDES и в проводках документов материала из
возможных 260 единиц измерения используются 76. В большинстве же реальных систем,
Страница 17 of 38 по материалам форума www.sapforum.biz (Версия: 1.0)
наверное, количество вариантов использования единиц измерения будет меньше,
соответственно количество запросов SELECT SINGLE xxxx FROM T006A будет тоже
меньше и вполне возможно, что запрос J6 будет самым быстрым. Это как раз тот самый
случай, когда нужно моделировать запросы на клиентских данных. Так же способ обработки
I6 будет более быстрым, если справочными таблицами, будут такие таблицы как основные
записи материала или дебиторов/кредиторов, тогда выигрыш будет очень значительным, так
как обычно, в периоде работы редко используются все существующие в системе коды
материалов или дебиторов/кредиторов.

Примечание: В данном случае, при чтении данных в операторе READ TABLE не


использовалось расширение BINARY SEARCH, так как таблица lt_t006a не была объявлена
как сортированная, а для не сортированных данных, использовать такой поиск нельзя. Более
подробно о том, как идет выбор данных, из внутренних таблиц, рассмотрим в части, где
будет описаны способы объявления таких таблиц.

Выводы (скорость): При соединении большой таблицы со справочниками таблицами,


содержащими относительно основной таблицы минимальный набор значений, более
оптимальным будет использование отдельного чтения значений справочников во
внутренние таблицы с последующей ручной обработкой соединения в цикле LOOP AT
<xxxx>. ENDLOOP. Если же использовать объединение основное таблицы, со
справочными, используя оператор JOIN в одном запросе выбора данных, то это может
немного замедлить выбор данных, хотя и не на много, так как обычно справочные
таблицы буферизированы. Проверить буферизацию таблиц можно используя
транзакцию SE13.

Выводы (память): С точки зрения оптимизации памяти, более выгодным будет


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

COMMIT WORK.
COMMIT WORK — В общем виде, производит фиксирование измененных или добавленных
данных в базу данных. В системе, есть два варианта использования COMMIT, собственно
сам COMMIT WORK и есть еще специальный функциональный модуль «DB_COMMIT».
Функциональный модуль вызывает операцию фиксирования изменений на уровне базы
данных, собственно говоря, там используется внутренний запрос к базе данных, используя
блок:
EXEC SQL.
COMMIT WORK
ENDEXEC.

При этом в случае ошибки применения изменений, вы не можете получить никакие


сообщения, т.е. этот ФМ отработает, но вот узнать, как произошла операция и в случае
ошибки выполнить ROLLBACK уже будет невозможно. Поэтому в системе введена
специальная команда COMMIT WORK, которая на самом деле выполняет не только
фиксация измененных данных, но и выполняет еще кучу полезных действий:
• Выполняет запуск всех подпрограмм, которые вызываются с использованием

Страница 18 of 38 по материалам форума www.sapforum.biz (Версия: 1.0)


инструкций вида PERFORM <имя подпрограммы> ON COMMIT и всех
функциональных модулей объявленных как CALL FUNCTION <имя модуля> IN
UPDATE TASK.
• Вызывает инициирование событий для службы Object Services. Поэтому если у вас
есть фоновые процессы, запускающиеся по событию, то именно эта операция
генерирует все заявленные в блоке обработки события.
• Обрабатываются все SAP блокировки, установленные в текущей программе по
значению формального параметра _SCOPE для соответствующих функций
блокирования, происходит снятие таких блокировок.
• Закрывается текущий LUW блок и все курсоры на уровне базы данных.
• Вызывается событие TRANSACTION_FINISHED, что устанавливает для системного
класса CL_SYSTEM_TRANSACTION_STATE значения COMMIT_WORK.

При массовых вставках или изменениях данных, не следует использовать COMMIT WORK,
после каждой вставки или изменения данных. Так же не следует заливать все данные и
делать один общий COMMIT на все вставленные/измененные записи. Оптимальным,
обычно является фиксация обновления/вставки через 200-500-1000 обработанных строк, по
факту от 1% до 3% от объема всех данных. К сожалению, это правило часто не работает с
функциями BAPI, которые не являются повторно входимыми, т. е. нельзя вызвать, например
проводку документа ММ, не сделав фиксацию или отмену (ROLLBACK) предыдущего
вызова этой же функции проводки документа. Поэтому, так как в таком случае ничего не
поделаешь, то после каждого вызова будем делать COMMIT. Пример такой функции это
проводка документа материала: BAPI_GOODSMVT_CREATE. Хотя если функция
допускает такую возможность, то ее нужно использовать, например функция
BAPI_FIXEDASSET_CHANGE - Изменение карточек ОС, позволяет делать COMMIT на
группу вызовов этой функции.

Из практики нужно было изменить порядка ~20 000 карточек, в программке случайно сбился
счетчик, в итоге COMMIT вызвался только после обновления всех карточек ОС. К чему это
привело, во-первых, каждая следующая запись карточки ОС, обновлялась все медленнее и
медленнее, так как рос лог транзакции, во-вторых, когда лог вырос до 400 Мб наступил
COMMIT, и вот конца уже этого процесса никто не дождался, так как 3 часа выполнения
COMMIT-а и было принято решение, удалить процесс. Далее подправили программку,
чтобы COMMIT был через 200 записей и в итоге ~20 000 карточек обновилось всего минут
за 40.

Выводы (скорость): При массовых обновлениях записей стараемся фиксировать


изменения не через каждую запись и не для всего блока изменений одним вызовом
COMMIT. Находим оптимальное значение, например для 20 000 записей, оптимальным
было делать фиксация через 200 вставленных/измененных записей, т.е. из этого следует
правило, обрабатываем от 1% до 3% записей, после чего выполняем COMMIT WORK.

Другая ситуация с COMMIT возникает когда выполняем создание сложных объектов


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

Страница 19 of 38 по материалам форума www.sapforum.biz (Версия: 1.0)


сразу же после COMMIT-а проведенный документ закончится ошибкой чтения, документа с
номером ХХХХХХХ не найден.

Какой выход из этой ситуации, ну скажем там часто имеющий место быть вариант, установка
задержки в секунду или полсекунды после каждого COMMIT, мне кажется, является очень
грубой ошибкой, особенно при массовой обработке данных. Например, мы обрабатываем 500
000 записей. Если после каждой будет установлена задержка в 1 секунду, то это значит, что
время работы программы будет растянуто на 5 с лишним суток, хотя при нормально проверке
используя функции блокировки скорость обновления данных 15-20 записей в секунду.
Поэтому общая обработка вместо 5 суток занимает всего 7 с половиной часов, по факту было
около 6. Как видим оптимизация быстродействия на лицо. Как это выполнить, в системе?
Практически для всех объектов, есть понятие записи блокирования объекта. Запись
блокирования это фактически специальный признак устанавливаемый системой, и
сообщающий о том, что запись кем-то изменяется (техническую реализацию рассматривать
не будем). Блокирование делается для того, чтобы исключить конфликт одновременного
изменения объекта. Для работы с записями блокирования есть специальные функции,
поэтому чтобы не лезть в таблицу блокировок запросами, можно воспользоваться уже
готовыми реализациями. Общий алгоритмы работы будет следующий, после каждого
обновления объекта, пытаемся его заблокировать. Если блокировка прошла успешно значит,
объект уже полностью прошел изменения, блокировку можно снять и далее запросы к
таблицам, будут возвращать, что записи есть в базе данных.

Для использования объектов блокирования, сначала нужно посмотреть какой объект


используется. Например, возьмем карточки ОС. Самое просто это зайти в изменения любой
карточки ОС, транзакция AS02, а затем в другом режиме запустить транзакцию SM12. Если у
вас больше ничего в других окнах не открыто на изменение, тогда там будет одна запись
блокирования, для карточки ОС. Делаете двойной клик на этой записи и получаете
детальную информацию по объекту блокирования, рисунок 4: SM12V.png.

Страница 20 of 38 по материалам форума www.sapforum.biz (Версия: 1.0)


Рисунок 6: SM12V.png

Нас интересует код объекта, на рисунке он выделен рамкой, это и есть объект блокировки.
Теперь нужно пойти в транзакцию SE11, чтобы узнать функциональные модули управления
блокировкой. Вводим полученный объект блокирования, как на рисунке 5: SE11-1.png, далее
жмем просмотр объекта.

Страница 21 of 38 по материалам форума www.sapforum.biz (Версия: 1.0)


Рисунок 7: SE11-1.png

Мы попадаем в экран просмотра, рисунок 6: SE11-2.png и там по меню: "Перейти к" –


"Модули блокировки", получаем две функции, одна для блокирования объекта основное
средство, а другая для деблокирования, рисунок 7: SE11-3.png.

Страница 22 of 38 по материалам форума www.sapforum.biz (Версия: 1.0)


Рисунок 8: SE11-2.png

Страница 23 of 38 по материалам форума www.sapforum.biz (Версия: 1.0)


Рисунок 9: SE11-3.png

Ну а дальше дело техники. После вызова функции создания/обновления карточки ОС, если
параметр BAPIRET2 не содержит ошибок, т.е. например создание карточки прошло
успешно, то далее вставляем код попытки блокирования на карточку ОС:

CALL FUNCTION 'ENQUEUE_EANLA'


EXPORTING
bukrs = l_bukrs
anln1 = l_anln1
anln2 = l_anln2
_wait = 'X'
EXCEPTIONS
foreign_lock = 1
system_failure = 2
OTHERS = 3.
IF sy-subrc = 0.
* Запись находится в БД, блокировку можно снять и бежать дальше.
CALL FUNCTION 'DEQUEUE_EANLA'
EXPORTING
bukrs = l_bukrs
anln1 = l_anln2
anln2 = l_anln3.
ELSE.
* Системная ошибка, пишем лог-обработки для дальнейшего анализа.
ENDIF.

Т.е. система будет пытаться поставить блокировку на созданный/измененный объект, если


блокировка установилась, то мы ее тут же снимаем и идем обрабатывать следующие данные,
так как запись точно уже существует в базе данных, если же получили ошибку, то такой

Страница 24 of 38 по материалам форума www.sapforum.biz (Версия: 1.0)


объект не обрабатываем, скорее всего, какие-то проблемы на уровне базиса. Как работает
флаг «_wait = 'X'»? В системе на уровне параметра enque/delay_max, задается количество
попыток установки блокировки. По умолчанию это значение установлено = 0005, код
который выполняется примерно такой:

if waitflag is initial.
times = 1.
else.
delonrej = 1.
call 'C_ENQ_WILDCARD' id 'DELAY_MAX' field times.
endif.

И далее:

DO time TIMES.
call 'C_ENQUEUE'… <далее куча параметров>
ENDDO.

Т.е. будет попытка вызвать системную функцию блокировки 5 раз, если блокировка была
отклонена, система делает следующую попытку установить блокировку, при этом время
задержки перед повторным вызовом будет примерно равно 1 секунда, как говорит нам
документация SAP, после 5 попыток будет выдана ошибка установки блокировки объекта.
Посмотреть, как оно именно работает можно в модуле LSENAF01, где-то со строки 230, для
версии системы 6.0

Выводы (скорость): Используйте быструю проверку на существование объектов базы


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

Определение типов внутренних таблиц.


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

Для начала проверим влияние чтения данных в различные типы внутренних таблиц.
Например, нужно сделать выборку позиций заказов на закупку, таблица EKPO. Варианты
объявления данных следующие:
DATA: lt_ekpo_sort LIKE SORTED TABLE OF ekpo WITH UNIQUE KEY ebeln ebelp,
lt_ekpo_hash LIKE HASHED TABLE OF ekpo WITH UNIQUE KEY ebeln ebelp,
lt_ekpo_standart LIKE ekpo OCCURS 0,
lt_ekpo_header LIKE ekpo OCCURS 0 WITH HEADER LINE.

Выборка данных делалась следующим образом.

ВремВып: 4.089.295 микросек.


A8) SELECT * INTO TABLE lt_ekpo_sort

Страница 25 of 38 по материалам форума www.sapforum.biz (Версия: 1.0)


FROM ekpo.

ВремВып: 4.147.068 микросек.


B8) SELECT * INTO TABLE lt_ekpo_header
FROM ekpo.

ВремВып: 4.022.872 микросек.


C8) SELECT * INTO TABLE lt_ekpo_standart
FROM ekpo.

ВремВып: 4.203.968 микросек.


D8) SELECT * INTO TABLE lt_ekpo_hash
FROM ekpo.

Каждый запрос выполнялся 100 циклов, при этом после 10 и 50 циклов результаты были
разными для разных типов таблиц. Однако, при увеличении числа прогонов общее время
выполнения, судя по всему, будет практически одинаковым, т.е. операции чтения из базы
данных, всегда дольше, по времени, чем вставка прочитанных данных во внутреннюю
таблицу. Проверка была выполнена на таблице EKPO, общий объем считанных данных в
районе 30 000 записей. Так же, тест прогонялся на таблице MSEG, в которой находилось
около 125 000 записей, результат на большом числе итераций 50 и 100, так же особо не
зависел от типа внутренней таблицы, в которую производилась выборка.

Выводы (скорость): Скорость заполнения данными практически не зависит от типа


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

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

Стандартные таблицы – внутренняя таблица, представляющая собой список строк


данных, размещенных в памяти, при этом, каждая следующая строка при выборе, будет
добавлена в конец уже существующих данных, по аналогии с добавлением записей
оператором APPEND, по факту поступления или же оператором INSERT в требуемую
позицию внутри внутренней таблицы. Для доступа к записям/строкам такой таблицы,
используют прямой доступ по номеру записи или перебор в цикле. Исходя из документации
доступ по номеру записи наиболее быстрый, но если номер записи не известен, то, будет
использоваться, прямой перебор пока нужная запись будет не найдена, поиск, используя
конструкцию BINARY SEARCH, не работает, так как в общем виде данные таблицы не
сортированные. Соответственно, время поиска пропорционально количеству записей в такой
таблице, так как в самом худшем случае приходится пройтись по всей таблице. Как вариант
ускорения доступа к данным такой таблицы, можно вызвать операцию сортировки данных с
последующим использованием, бинарного поиска (BINARY SEARCH), записей для уже
отсортированной таблицы. Правда при этом не следует забывать, что сортировка больших
внутренних таблиц данных сама по себе требует иногда значительного времени, хотя если мы
заполняем внутреннюю таблицу сами, то можно поддерживать упорядоченность записей в
ручном режиме. Для примера можно рассмотреть пример кода J6, где мы заполняем таблицу
справочник кодов единиц измерения. Суть метода базируется на том, что при использовании
бинарного поиска, если запись не найдена во внутренней таблице, то системная переменная
SY-TABIX, содержит номер позиции, в которой должна была бы находиться такая запись.

Страница 26 of 38 по материалам форума www.sapforum.biz (Версия: 1.0)


Поэтому код будет выглядеть следующим образом:

* Общие данные для обработки


TYPES: BEGIN OF tp_t006a,
msehi LIKE t006a-msehi,
mseht LIKE t006a-mseht,
msehl LIKE t006a-msehl,
END OF tp_t006a.

DATA: ls_t006a TYPE tp_t006a,


lt_t006a TYPE TABLE OF tp_t006a,
lt_t006a_st TYPE SORTED TABLE OF tp_t006a WITH UNIQUE KEY msehi.

DATA: BEGIN OF lt_mseg_short OCCURS 0,


mblnr LIKE mseg-mblnr,
mjahr LIKE mseg-mjahr,
zeile LIKE mseg-zeile,
bwart LIKE mseg-bwart,
matnr LIKE mseg-matnr,
menge LIKE mseg-menge,
meins LIKE mseg-meins,
mseht LIKE t006a-mseht,
msehl LIKE t006a-msehl,
END OF lt_mseg_short.

ВремВып 157.575 микросек.


e8) LOOP AT lt_mseg_short.
READ TABLE lt_t006a INTO ls_t006a
WITH KEY msehi = lt_mseg_short-meins
BINARY SEARCH.
IF sy-subrc <> 0.
CLEAR: ls_t006a.
SELECT SINGLE msehi mseht msehl INTO ls_t006a
FROM t006a WHERE spras = sy-langu AND
msehi = lt_mseg_short-meins.
INSERT ls_t006a INTO lt_t006a INDEX sy-tabix.
ENDIF.
lt_mseg_short-mseht = ls_t006a-mseht.
lt_mseg_short-msehl = ls_t006a-msehl.
MODIFY lt_mseg_short.
ENDLOOP.

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

ВремВып: 164.022 микросек.


f8) LOOP AT lt_mseg_short.
READ TABLE lt_t006a INTO ls_t006a
WITH KEY msehi = lt_mseg_short-meins.
IF sy-subrc <> 0.
CLEAR: ls_t006a.
SELECT SINGLE msehi mseht msehl INTO ls_t006a
FROM t006a WHERE spras = sy-langu AND
msehi = lt_mseg_short-meins.
APPEND ls_t006a TO lt_t006a.
ENDIF.
lt_mseg_short-mseht = ls_t006a-mseht.

Страница 27 of 38 по материалам форума www.sapforum.biz (Версия: 1.0)


lt_mseg_short-msehl = ls_t006a-msehl.
MODIFY lt_mseg_short.
ENDLOOP.

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

ВремВып: 164.925 микросек.


g8) LOOP AT lt_mseg_short.
READ TABLE lt_t006a_st INTO ls_t006a
WITH KEY msehi = lt_mseg_short-meins.
IF sy-subrc <> 0.
CLEAR: ls_t006a.
SELECT SINGLE msehi mseht msehl INTO ls_t006a
FROM t006a WHERE spras = sy-langu AND
msehi = lt_mseg_short-meins.
INSERT ls_t006a INTO lt_t006a_st INDEX sy-tabix.
ENDIF.
lt_mseg_short-mseht = ls_t006a-mseht.
lt_mseg_short-msehl = ls_t006a-msehl.
MODIFY lt_mseg_short.
ENDLOOP.

Эффект такой, по скорости наблюдается проигрыш даже прямому перебору, возможно это
связано с тем, что внутренняя таблица содержит относительно мало данных и накладные
расходы на обработку сортированной таблицы на таком объеме выше, чем для не
сортированной. После прогона 100 циклов, можно сказать, что для небольшого объема
данных использовать сортированную таблицу оказалось затратной операцией. В основном
время выбора из сортированной таблицы равно скорости выбора из не сортированной, при
этом скорость обработки для стандартной таблицы, отсортированной в ручном режиме,
всегда была выше.

Сортированная таблица – таблица, которая содержит изначально упорядоченные записи,


при чтении значений из таблицы базы данных, записи вставляются во внутреннюю таблицу
согласно заданному ключу. При объявлении таблицы обязательно требуется задать поля, по
которым будет выполняться сортировка и так же определить уникальный или не уникальный
будет ключ для таблицы. Согласно документации SAP, для таких таблиц, всегда используется
бинарный поиск – «…since the system always uses a binary search.». Скорость доступа к
записям сортированной таблицы логарифмически пропорциональна количеству записей в
таблице.

Примечание: Бинарный поиск, более известен как метод половинного деления, т.е. при
наличии сортированных данных, на пример у нас есть таблица с одним ключевым полем, для
простоты это поле типа целое число, пусть внутренняя таблица содержит следующие
записи, таблица 1. Всего записей 14, нам нужно найти запись с ключем 95. Система
выполняет деление количества записей в таблице пополам, т.е. ( 14 / 2 ) + 1 = 8 и читает
запись следующую по индексу, т.е. в данном случае это запись с индексом 8. Значение ключа
этой записи 67, так как это меньше чем искомое значение, то система будет
обрабатывать вторую половину списка, т.е. оставшийся диапазон с 9 по 14, который
тоже делится пополам, после чего выходим на запись с номером 12. Сверяем значение ключа
89, оно меньше, чем требуемое 95, поэтому снова делим оставшийся диапазон из двух

Страница 28 of 38 по материалам форума www.sapforum.biz (Версия: 1.0)


записей пополам. Выходим на запись номер 14, значение ключа 100, больше чем 95, значит
надо взять предыдущий интервал и делить уже пополам его. Однако, в этом интервале
всего одна запись и ее ключ 95, значит индекс искомой записи 13. Если, искомый ключ, был,
например 90, то мы получили бы ошибку, что запись с требуемы ключом не найдена.
Потому что, оставшийся интервал состоит из одной записи и делить больше нечего. Таким
образом, для поиска значения ключа потребовалось сделать 4 чтения, вместо 13 при поиске
записи по последовательному перебору.

Таблица 1
№ Ключ Поле 1 1 2 3 Номер чтения Результат
записи
1 1 Aaaaaaaaaa
2 2 Ccccccccccc
3 7 Zzzzzzzzzzzzz
4 15 Yyyyyyyyyyy
5 23 Еееееееееее
6 45 Wwwwwww
7 50 Yyyyyyyyyy
8 67 Qqqqqqqqq 1 ↓
9 68 Rrrrrrrrrrrr
10 77 Bbbbbbbbb
11 80 Uuuuuuuuu
12 89 Ooooooooo 2 ↓
13 95 Vvvvvvvvvvv 4 (нужная нам
запись)
14 100 Kkkkkkkkkk 3 ↑

Хэш-таблицы – таблица, содержащие упорядоченные записи, которые сортируются


автоматически при чтении значений из таблицы базы данных. Для хэш-таблиц всегда должен
существовать уникальный ключ, т.е. в отличие от сортированных таблиц, не уникальные
значения ключей не могут присутствовать во внутренней таблице. Скорость доступа к
записям такой таблицы не зависит от количества записей в таблице, так как индекс записи
фактически вычисляется по ключу, через хэш-функцию. Какой алгоритм расчета индекса по
ключу используется системой, не известно. Так же, из за отсутствия исходных текстов, не
известен алгоритм разрешения коллизий для хэш-функции (в общем виде, чем больше
коллизий при расчете индекса выдает функция, тем медленнее идет чтение данных по ключу
). Поэтому, остается только верить, что действительно, скорость доступа к записям хэш-
таблицы по ключу, не зависит от количества записей в этой таблице – «The response time for
key access remains constant». Обратной стороной «медали» будет размер, который занимает
такая таблица в памяти.

Примечание: Как работает хэш-функция и что это вообще такое. В самом простом случае,
считаем, что имеем черный ящик, на вход которого подается уникальный ключ записи, на
выходе получаем индекс, строки в памяти, куда нужно вставить или откуда нужно
прочитать запись. Для еще большего упрощения возьмем пример значений записей из
таблицы 1 выше. Так как ключ у нас уникальное число, то пусть наша хэш-функция
возвращает индекс записи = значение ключа. Это значит, что записи будут размещены во
внутренней таблице следующим образом:

Таблица 2
№ Ключ Поле 1
Страница 29 of 38 по материалам форума www.sapforum.biz (Версия: 1.0)
1 1 Aaaaaaaaaa
2 2 Ccccccccccc
3
4
5
6
7 7 Zzzzzzzzzzzzz
8
9
10
11
12
13
14
15 15 Yyyyyyyyyyy
Пустые записи с 16 по 22
23 23 Еееееееееее
Пустые записи с 24 по 44
45 45 Wwwwwww
Пустые записи с 46 по 49
50 50 Yyyyyyyyyy
Пустые записи с 51 по 66
67 67 Qqqqqqqqq
68 68 Rrrrrrrrrrrr
Пустые записи с 69 по 76
77 77 Bbbbbbbbb
Пустые записи с 78 по 79
80 80 Uuuuuuuuu
Пустые записи с 81 по 88
89 89 Ooooooooo
Пустые записи с 90 по 94
95 95 Vvvvvvvvvvv
Пустые записи с 96 по 99
100 100 Kkkkkkkkkk

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

Выводы (скорость и память): Как видим, есть три вида внутренних таблиц. У каждой
есть свои плюсы и минусы, как обычно на весах скорость доступа против размера,
занимаемого внутренней таблицей в памяти:
• Стандартные таблицы можно использовать как начальные накопительные
буферы данных, т.е. если в любом случае вы читаете данные из базы данных, а
затем выполняете полный проход по такой таблице по всем прочитанным
записям, то стандартная внутренняя таблица даст вам минимальное

Страница 30 of 38 по материалам форума www.sapforum.biz (Версия: 1.0)


использование памяти. Однако, поиск значений по ключу, для таких таблиц,
будет самым медленным и в конечном итоге приводит по факту к полному
перебору значений таблицы с начала до конца, пока необходимая запись не будет
найдена. При этом если количество запросов к несуществующим записям
большое, то это вызывает постоянные полные проходы, при каждом запросе не
существующего значения ключа, по всей внутренней таблице, что вызывает
катастрофическое падении производительности. Если вы заполняете данные
стандартной таблицы сами, то для использования конструкции BINARY
SEARCH старайтесь поддерживать упорядоченность вставляемых записей,
используя особенности инициализации системной переменной SY-TABIX, из
примера E8.
• Сортированные таблицы это компромисс между скоростью доступа и объемом
памяти. Такие таблицы удобнее использовать, если у вас определены ключи
сортировки, и вы должны получать быстрый доступ к любым записям
внутренней таблицы, по заданному ключу в случайном порядке. Однако, при
быстром увеличении количества записей в сортированной таблице, скорость
доступа по ключам будет падать, так как количество чтений записей, при
поиске будет расти. Так же для сортированных таблиц следует избегать
определения не уникальных ключей, на пример не имеет смысла делать
сортированную таблицу по полю, для которого существует только два значения
«space» и «X», после чего считывать в такую таблицу, например 10 000 записей.
• Хэш-таблицы, будут занимать много памяти, но при этом обеспечивать
равномерный и максимально быстрый доступ к записям по ключу, не зависимо от
количества записей в такой таблице, так как скорость расчета индекса
нахождения записи по ключу, будет величиной постоянной. При этом время
проверки отсутствия записи по ключу, будет такое же, как и проверка наличия
записи. Поэтому, если у вас большое количество проверок на существование
записей во внутренней таблице, при большом объеме обрабатываемых данных, то
хэш-таблица очень неплохой вариант.

Работа с внутренними таблицами: LOOP, READ, DELETE и


т.д.
LOOP AT <xxx>. ENDLOOP. Циклы по внутренним таблицам, казалось бы, что тут
оптимизировать, есть таблица, есть ее построчная обработка, однако как оказалось и тут есть
подходы к совершенству. Кстати, если использовать данную оптимизацию для запросов G6,
J6 и I6, то скорость обработки еще возрастет по сравнению с простым соединением из
примера H6. И так суть оптимизации заключается в использовании FIELD-SYMBOLS.

* Данные для работы


DATA: lt_mseg_loop LIKE mseg OCCURS 1 WITH HEADER LINE,
FIELD-SYMBOLS: <fs> LIKE LINE OF lt_mseg_loop.

ВремВып: 71.894 микросек.


a8) SELECT * INTO TABLE lt_mseg_loop
FROM mseg.

LOOP AT lt_mseg_loop.

ENDLOOP.

Страница 31 of 38 по материалам форума www.sapforum.biz (Версия: 1.0)


ВремВып: 8.001 микросек.
b8) SELECT * INTO TABLE lt_mseg_loop
FROM mseg.

LOOP AT lt_mseg_loop ASSIGNING <fs>.

ENDLOOP.

Как видим, простой проход по циклу, используя конструкцию с FIELD-SYMBOLS, дает


разницу в скорости выполнения почти в 9 раз. Еще более существенные цифры получаются,
если требуется выполнить какие-то обновления во внутренней таблице, используя
конструкцию MODIFY для первого примера A8.

ВремВып: 193.230 микросек


c8) SELECT * INTO TABLE lt_mseg_loop
FROM mseg.

LOOP AT lt_mseg_loop.
lt_mseg_loop-bwart = '000'.
MODIFY lt_mseg_loop.
ENDLOOP.

ВремВып: 11.722 микросек.


d8) SELECT * INTO TABLE lt_mseg_loop
FROM mseg.

LOOP AT lt_mseg_loop ASSIGNING <fs>.


<fs>-bwart = '000'.
ENDLOOP.

Выигрыш в выполнении составляет уже 16 раз. В принципе суть прироста


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

Выводы (скорость): По возможности используйте при работе с внутренними


таблицами, чтение данных через переменные, объявленные в FIELD-SYMBOLS, это
существенно ускоряет работу циклов по внутренним таблицам. Негативом, в данном
случае, может быть только то, что при модификации значений внутренней таблицы,
нет явной команды типа MODIFY и как следствие нужно быть аккуратными, чтобы
случайно не затереть данные таблицы, и потом долго искать, где же это произошло.
При этом выигрыш будет тем больше, чем больше полей находится во внутренней
таблице, т.е. если длина записи большая, то выигрыш будет очень существенным.

LOOP AT <xxx> WHERE <val>. ENDLOOP. Так же рассмотрим варианты, когда нужно
пройти не по всем записям внутренней таблицы, а только по подходящим под условие
WHERE. Тут тоже возможна оптимизация обработки в зависимости от типа объявленной
таблицы. Выборку делаем в сортированную таблицу, так как такой вариант более быстрый с
точки зрения загрузки данных.

Страница 32 of 38 по материалам форума www.sapforum.biz (Версия: 1.0)


Данные для обработки:
DATA: itab TYPE SORTED TABLE OF bkpf
WITH UNIQUE KEY bukrs belnr gjahr.

FIELD-SYMBOLS: <itab> LIKE LINE OF itab.

ВремВып: 33.764 микросек.


e8) LOOP AT itab ASSIGNING <itab>
WHERE bukrs EQ '1000' AND belnr CP '55*'.
ENDLOOP.

ВремВып: 24 микросек.
f8) READ TABLE itab TRANSPORTING NO FIELDS
WITH KEY bukrs = '1000'
belnr = '5500000000'
BINARY SEARCH.
LOOP AT itab ASSIGNING <itab> FROM sy-tabix.
IF <itab>-belnr+0(2) <> '55'.
EXIT.
ENDIF.
ENDLOOP.

ВремВып: 45 микросек.
g8) READ TABLE itab TRANSPORTING NO FIELDS
WITH KEY bukrs = '1000'
belnr = '5500000000'
BINARY SEARCH.
LOOP AT itab ASSIGNING <itab> FROM sy-tabix
WHERE bukrs EQ '1000' AND
belnr CP '55*'.
ENDLOOP.

ВремВып: 11 микросек.
h8) READ TABLE itab TRANSPORTING NO FIELDS
WITH KEY bukrs = '1000'
belnr = '5500000000'
BINARY SEARCH.
LOOP AT itab ASSIGNING <itab> FROM sy-tabix
WHERE bukrs EQ '1000' AND
Belnr(2) EQ '55'.
ENDLOOP.

Выводы (скорость): Думаю, цифры говорят сами за себя. Хотя обратите внимание на
запросы G8 и H8, т.е. простая замена в условии «belnr CP '55*'» на «Belnr(2) EQ '55'»,
тут же вывело запрос H8 по скорости на первое место, причем сначала он проигрывал в
два раза запросу F8, а поле такой замены стал выигрывать в два раза.

DELETE – удаление записей из внутренней таблицы. Вариантов удаления есть несколько и


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

Есть внутренняя таблица, в которой находится 115.528 записей. В ней отмаркированы 22.106
записей для удаления, маркировка сделана принудительно путем проставления цифры «1» в
поле IS_NULL некоторых записей, точнее каждой пятой.

Страница 33 of 38 по материалам форума www.sapforum.biz (Версия: 1.0)


* Данные для работы
DATA: BEGIN OF gs_alvtab,
belnr TYPE bkpf-belnr,
is_null(1) TYPE c,
END OF gs_alvtab,
gt_alvtab LIKE STANDARD TABLE OF gs_alvtab,
tabix TYPE sy-tabix,
idx1 LIKE sy-tabix,
idx2 LIKE sy-tabix.

FIELD-SYMBOLS: <fs_alvtab> LIKE gs_alvtab.

ВремВып: 30.944 микросек.


e8) LOOP AT gt_alvtab ASSIGNING <fs_alvtab> WHERE is_null = 1.
DELETE gt_alvtab INDEX sy-tabix.
ENDLOOP.

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


меня в данном случае отказ от использования конструкции LOOP AT gt_alvtab ASSIGNING
<fs_alvtab>, и ее замена на LOOP AT gt_alvtab INTO gs_alvtab, дал разницу в скорости
всего в 1000 микросекунд. Причина только в том, что таблица содержит два поля с длинной
записи 11 байт.

ВремВып: 40.718 микросек. – Сортировка


ВремВып: 32.706 микросек. – Удаление
f8) SORT gt_alvtab BY is_null.
LOOP AT gt_alvtab ASSIGNING <fs_alvtab> WHERE is_null = 1.
DELETE gt_alvtab INDEX sy-tabix.
ENDLOOP.

Аналогичное удаление, но уже по сортированной внутренней таблице. Тут у нас два


временных интервала, первый это время на сортировку таблицы и второй, собственно само
удаление. Как видим прироста скорости никакого нет, наблюдается даже замедление работы.,
при этом если сортировку сделать «SORT gt_alvtab2 BY is_null ASCENDING,» то время
удаления станет 31.628 микросекунд, т.е. будет небольшое улучшение в скорости, но все
равно вариант E8 быстрее, скорость сортировки при этом не изменяется.

ВремВып: 40.718 микросек. – Сортировка


ВремВып: 38.970 микросек – Удаление
g8) READ TABLE gt_alvtab WITH KEY is_null = 1
BINARY SEARCH TRANSPORTING NO FIELDS.
tabix = sy-tabix.

WHILE sy-subrc = 0.
sy-subrc = 0.
READ TABLE gt_alvtab ASSIGNING <fs_alvtab> INDEX tabix.
IF sy-subrc <> 0. EXIT. ENDIF.
IF <fs_alvtab>-is_null <> 1.
sy-subrc = 4.
ELSE.
DELETE gt_alvtab INDEX tabix.
ENDIF.
ENDWHILE.

Тоже в некотором роде вариант удаления данных. Первыми строками проверяем, есть ли
записи для удаления и дальше в цикле пока такие записи есть, производим удаление. Если
таблица не сортированная, тогда мы не можем использовать вариант поиска с ключом
BINARY SEARCH, поэтому код будет выглядеть, немного по-другому.
Страница 34 of 38 по материалам форума www.sapforum.biz (Версия: 1.0)
ВремВып: 151.734 микросек – Удаление
h8) READ TABLE gt_alvtab WITH KEY is_null = 1
TRANSPORTING NO FIELDS.
tabix = sy-tabix.

WHILE sy-subrc = 0.
sy-subrc = 0.
READ TABLE gt_alvtab ASSIGNING <fs_alvtab> INDEX tabix.
IF sy-subrc <> 0.
EXIT.
ENDIF.
IF <fs_alvtab>-is_null <> 1.
ADD 1 TO tabix.
ELSE.
DELETE gt_alvtab INDEX tabix.
ENDIF.
ENDWHILE.

Как видим «Сортировка+Удаление», будет почти в два раза быстрее, чем просто «Удаление»
из не отсортированной таблицы. Кстати, если вы хотите, только проверить существование
записи с заданными ключами, то всегда используйте ключ TRANSPORTING NO FIELDS.

ВремВып: 37.390 микросек


i8) tabix = LINES( gt_alvtab ).
DO tabix TIMES.
READ TABLE gt_alvtab INDEX sy-index
TRANSPORTING NO FIELDS.
IF sy-subrc <> 0. EXIT. ENDIF.
ENDDO.

ВремВып: 44.628 микросек


j8) tabix = LINES( gt_alvtab ).
DO tabix TIMES.
READ TABLE gt_alvtab INTO gs_alvtab INDEX sy-index.
IF sy-subrc <> 0. EXIT. ENDIF.
ENDDO.

На ~115 000 записей как видим расхождение в скорости обработки уже существенное, при
этом gt_alvtab, содержит только 5 полей, для записей с большим количеством полей и
соответственно большей длинной записи, цифры будут еще больше.

Следующий вариант использование оператора DELETE <внутренняя таблица> WHERE


<условие отбора>. При этом тест делался как для уже отсортированной таблицы, так и для
не сортированной.

ВремВып: 30.401 микросек – Удаление не сортированные данные.


ВремВып: 40.718 микросек. – Сортировка
ВремВып: 19.893 микросек – Удаление сортированные данные.
k8) DELETE gt_alvtab WHERE is_null = 1.

Так как в данном случае на 115.528 записей количество вариантов значения поля is_null не
очень большое, всего пять вариантов, а мы удаляем 20% записей, то для сортированных
таблиц можно воспользоваться вариантом удаления диапазона записей.

ВремВып: 40.718 микросек. – Сортировка


ВремВып: 5.350 микросек. – Удаление

Страница 35 of 38 по материалам форума www.sapforum.biz (Версия: 1.0)


l8) SORT gt_alvtab BY is_null.
READ TABLE gt_alvtab WITH KEY is_null = 1
BINARY SEARCH TRANSPORTING NO FIELDS.
idx1 = sy-tabix.

READ TABLE gt_alvtab WITH KEY is_null = 2


BINARY SEARCH TRANSPORTING NO FIELDS.

idx2 = sy-tabix - 1.
CHECK idx2 >= idx1.
DELETE gt_alvtab FROM idx1 TO idx2.

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

ВремВып: 40.718 микросек. – Сортировка


ВремВып: 5.768 микросек. – Удаление
m8) READ TABLE gt_alvtab WITH KEY is_null = 1
BINARY SEARCH TRANSPORTING NO FIELDS.
idx1 = sy-tabix.
CHECK idx1 <= LINES( gt_alvtab ).
DELETE gt_alvtab FROM idx1.

Примечание: В примере M8 удалилось 80% записей, при этом по скорость это оказалось
фактически равным времени выполнения запроса L8, в котором удалялось 20% но из средины
таблицы, поэтому время выполнения удаления в этом примере, не нужно сравнивать с
предыдущими примерами, все таки условия удаления для этого примере несколько другие.

Еще один метод удаления, конструкция DELETE TABLE, но результаты по сравнению с


остальными в данном случае очень плохие. Удалялись те же самые 20% записей, время
просто катастрофическое.

ВремВып: 80.131.915 микросек. – Удаление


n8) LOOP AT gt_alvtab ASSIGNING <fs_alvtab> WHERE is_null = 1.
DELETE TABLE gt_alvtab FROM <fs_alvtab>.
ENDLOOP.

Кстати если сделать цикл без условия с удалением всех записей, то скорость удаления
значительно быстрее, но смысл, удалять таким образом все записи? Более быстрым будет
просто команда очистки таблицы CLEAR gt_alvtab.

По возможности следует освобождать память, используемую внутренними таблицами.


Операция DELETE, как показала практика, такого не выполняет, поэтому требуется
использовать последовательные команды REFRESH и FREE. Только после этого память
становится освобожденной. Особенно следует обратить внимание на внутренние таблицы,
которые работают внутри функциональных модулей, и при этом таблицы объявлены в
заголовке функциональной группы. Поэтому при использовании ФМ перед выходом следует
делать REFRESH и FREE для всех таких глобальных таблиц, за исключением буферов для
справочников.

При копировании данных из одной таблицы в другую для различных последующих


обработок следует избегать конструкций вида IT_DATA2[] = IT_DATA1[], вместо этого
лучше скопировать только действительно нужные записи в таблицу IT_DATA2. По скорости
копирования вариант IT_DATA2[] = IT_DATA1[], конечно же быстрый, но при этом затраты
на память удваиваются, если таблицы большие, то вполне память может и закончиться.

Страница 36 of 38 по материалам форума www.sapforum.biz (Версия: 1.0)


ВремВып: 4 микросек.
o8) gt_alvtab = gt_alvtab2.

ВремВып: 32.226 микросек.


p) LOOP AT gt_alvtab2 ASSIGNING <fs_alvtab>.
APPEND <fs_alvtab> TO gt_alvtab.
ENDLOOP.

Выводы (скорость): В случае не сортированных данных, используйте конструкцию


DELETE <int_tab> WHERE <key> = <val>, кстати, пример E8 по времени как видим не
сильно отстал от примера K8, для не сортированных данных. В общем виде пример K8,
это наиболее быстрый способ удаления данных из внутренней не сортированной
таблицы. Если же таблица изначально уже содержит отсортированные значения, то
наиболее быстрыми будут способы удаления L8 и M8. Делать отдельную сортировку, а
затем использовать способы L8 или M8 не имеет смысла, так как общее время удаления
с учетом сортировки получается большим, чем для примера K8.

Выводы (память): Всегда очищайте память, от уже не нужных для работы


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

Как проводить тестирование.


Для проверки своих запросов, особенно если есть различные варианты можно
воспользоваться в транзакции SE38 на первом экране нужно по меню перейти: Среда –
Примеры – Примеры производительности, рисунок 10: SE38-0.png.

Рисунок 10: SE38-0.png

Слева дерево примеров производительности, а справа два окна, куда можно вводить текст
своей программы, при этом можно использовать объявления переменных и все конструкции
языка ABAP. После заполнения обоих вариантов, запускаем отчеты по кнопке «Измерить
Страница 37 of 38 по материалам форума www.sapforum.biz (Версия: 1.0)
время выполнения» и система покажет время выполнения обоих запросов. Если текст
программы довольно большой со ссылками на различные внутренние переменные, тогда
можно сделать замеры самому, используя оператор: GET RUN TIME FIELD time<n>,
перед началом интересующего нас блока и в конце. Разница между значениями time1 и time2
и будет временем выполнения блока команд в микросекундах. Пример кода:

DATA: time2 TYPE i,


time1 TYPE i,
time_diff TYPE i.

GET RUN TIME FIELD time1.


SELECT * FROM sbook INTO sbook_wa UP TO 1 ROWS
WHERE carrid = 'LH'.

ENDSELECT.
GET RUN TIME FIELD time2.

time_diff = time2 - time1.


WRITE: / 'SELECT TIME:', time_diff.

Страница 38 of 38 по материалам форума www.sapforum.biz (Версия: 1.0)