효과적인 코딩기법
1 SELECT 구문 최적화
1.1 SAP R/3 구조 이해하기
- R/3 구조를 이해하기 위해 아래와 같이 VBAK 테이블에서 데이터를 1000건 SELECT 하는 프로그램을 작성해 보자.
- 읽어올 정보는 다음과 같다.
- VBAK-VBELN: 오더 번호
- VBAK-AUART: 오더 종류
- VBAK-BNAME: 오더 이름
- VBAK-KUNNR: Sold to party
- 일반적으로 아래와 같은 방식으로 SELECT 문장을 작성할 것이다.
REPORT ZSAMPLE1. TABLES : vbak. SELECT * FROM vbak. IF sy-dbcnt > 1000. EXIT. ENDIF. WRITE: / vbak-vbeln, … ENDSELECT. |
- 위 구문은 VBAK 테이블의 모든 칼럼을 한 건씩 읽어 가면서, WRITE 하는 구조로 되어 있으며, SY-DBCNT를 확인하여 1000건이 넘으면 EXIT하도록 되어 있다.
- 그럼 다음 단계에서 왜 위 문장이 비효율적인지 알아보자.
1.2 SAP R/3 구조와 실행시간
- R/3 는 아래 그림과 같은 구조로 이루어져 있다.
- R/3 는 위의 그림과 같이 3 계층 구조로 되어 있다.
즉, 사용자가 시스템에 특정 데이터에 대한 요청을 하면, Application Server가 중간에서 사용자의 요청을 가공한 후, DB Server에 데이터에 대한 요청을 보내고, DB Server로부터 데이터를 받아, 다시 가공하여 사용자에게 보내는 방식으로 처리한다.
- 따라서, 앞과 같이 SELECT * 하게 되면, 문제에서 제시한 네 개 필드 외에 해당 레코드의 모든 필드를 가져오게 된다. 즉, VBAK 테이블의 한 레코드 크기가 대략 575바이트인데, 1000건을 가져와야 하므로 총 575,000 바이트를 DB Server에서 Application Server로 전송해야 하며, 한번 전송할 때의 패킷 크기가 32,000 바이트인 경우(시스템에 따라 다를 수 있음), 총 20여 회의 네트워크 트래픽이 발생하게 된다. (575,000 / 32,000)
- 다음 단계에서 앞 문제의 해답과 이유를 알아보자.
1.3 SELECT * 개선
- SELECT * 형태의 문장을 COLUMN Selection 이용하여 아래와 같이 개선할 수 있다.
SELECT vbeln auart bname kunnr INTO (vbak-vbeln, vbak-auart, vbak-bname, vbak-kunnr) FROM vbak WRITE : / vbak-vbeln, vbak-auart, vbak-bname, vbak-kunnr. ENDSELECT. |
- 위와 같이 사용하려는 네 개의 Column만을 가져오도록 할 경우 네트워크 트래픽은 다음과 같다.
- 즉, 네 개 Column 크기의 합이 59 바이트이므로 1000건을 읽는다 해도 59,000 바이트의 데이터만 DB Server에서 Application Server로 전송되면 된다. 패킷의 크기가 역시 32,000 이라면, 이 경우에는 두 번의 트래픽만 필요하게 된다.
- BSEG, VBFA 같은 SAP Cluster Table에 대해서는 Column Selection 을 할 수 없다(Column Selection을 하도록 작성해도 내부적으로는 전체 Column을 읽는다.). 이러한 테이블을 Column Selection으로 읽고 싶은 경우에는, 시스템을 내리고, 테이블 형태를 Cluster Table에서 Transparent Table로 바꾼다.
1.4 데이터 건수 확인 요령
- 아래 방법은 SY-DBCNT를 이용한 데이터 건수 확인 문장을 UP TO n ROWS 구문을 이용하여 개선하는 예를 보여준다.
*-- 개 선 전 SELECT vbeln auart bname kunnr INTO (vbak-vbeln, vbak-auart, vbak-bname, vbak-kunnr) FROM vbak. IF SY-DBCNT > 1000. EXIT. ENDIF. WRITE : / vbak-vbeln, vbak-auart, vbak-bname, vbak-kunnr. ENDSELECT. *----------------------------------------------------------------------* *-- 개 선 후 SELECT vbeln auart bname kunnr INTO (vbak-vbeln, vbak-auart, vbak-bname, vbak-kunnr) FROM vbak UP TO 1000 ROWS. WRITE : / vbak-vbeln, vbak-auart, vbak-bname, vbak-kunnr. ENDSELECT. |
- 위의 예에서 첫 번째 문장은 데이터 1000건을 건건이 DB Server에서 Application Server로 전송하는 반면, 두 번째 문장은 DB Server에서 1000건을 읽은 후 일괄적으로 Application Server로 전송하는 방식으로 동작한다. 따라서 두번째 문장이 훨씬 좋은 성능을 나타낸다.
- WHERE 조건 없이 데이터 한 건만을 가져오는 경우라면, 차이가 더욱 두드러진다. 한 건을 가져올 경우 전자의 경우 150,000ms, 후자의 경우 1,500ms 시간이 걸린다.
1.5 CHECK 구문 개선
- 아래 예는 CHECK 문장에 들어가는 조건을 SELECT 문장에 포함하여 개선하는 방법이다.
PARAMETERS : param1. DATA : BEGIN OF search_string, FIRST(9) VALUE ‘_________’, PARAM, END OF search_string. *-- 개 선 전 SELECT vbeln auart bname kunnr INTO (vbak-vbeln, vbak-auart, vbak-bname, vbak-kunnr) FROM vbak. CHECK vbak-vbeln+9(1) = param1. WRITE : / vbak-vbeln, vbak-auart, vbak-bname, vbak-kunnr. ENDSELECT. *----------------------------------------------------------------------* *-- 개 선 후 SELECT vbeln auart bname kunnr INTO (vbak-vbeln, vbak-auart, vbak-bname, vbak-kunnr) FROM vbak WHERE vbeln LIKE search_string. WRITE : / vbak-vbeln, vbak-auart, vbak-bname, vbak-kunnr. ENDSELECT. |
- Vbak 테이블에서 57,000건을 가져오는 것을 비교한 경우
SELECT & CHECK à 27,958,000ms
SELECT WITH WHERE condition à 3,065,000ms
- 가능하다면 어떠한 경우라도, CHECK 조건은 WHERE 절 안에 들어와야 하며 이것은 SELECT SINGLE에도 적용된다.
SELECT SINGLE vbeln auart … INTO (vbak-vbeln, …) FROM vbak
WHERE vbeln = ‘0090000090’ AND bname = ‘smith’.
- 위 구문에서 vbak 테이블의 키 필드는 vbeln이지만, bname을 CHECK로 빼지 않고, WHERE 절에 포함시킴으로써, DB Server와 Application Server 간에 쓸데없는 네트워크 트래픽을 줄일 수 있다.
- 그러나, CHECK 절이 매우 까다롭고 복잡한 경우에는 오히려 DB Server에서 일단 데이터를 받아오는 것이 빠를 수도 있다. 이것은 SQL Trace를 이용하여 실제로 어떤 것이 빠른지 조사해보고 결정하여야 한다.
1.6 ORDER BY 구문 개선
- 아래 예는 SELECT 구문에서 사용되는 ORDER BY를 Internal Sort를 이용하여 개선하는 방법을 보여준다.
*-- 개 선 전 SELECT vbeln auart bname kunnr INTO (vbak-vbeln, vbak-auart, vbak-bname, vbak-kunnr) FROM vbak WHERE bname IN so_bname ORDER BY bname DESCENDING. ENDSELECT. *----------------------------------------------------------------------* *-- 개 선 후 WHERE bname IN so_bname. SORT it_vbak BY bname DESCENDING. |
- 많은 양의 DB를 DB Server에서 정렬하는 것은 그 시스템을 사용중인 모든 사용자에게 영향을 미친다. 하지만 ABAP/4 프로그램 내에서 처리하게 되면 해당 Application Server에만 영향을 미친다.
- 하지만 SELECT 구문 내에서 ORDER BY를 이용하여 정렬하고자 하는 필드에 INDEX가 적절하게 구성되어 있다면, DB Server에 해롭지 않다.
1.7 APPEND 구문 개선
- 아래 예는 APPEND 구문을 이용하여 내부 테이블에 건건이 데이터를 추가하는 것을 INTO TABLE을 사용하여 개선하는 방법을 보여준다.
*-- 개 선 전 SELECT vbeln auart bname kunnr INTO (it_vbak-vbeln, it_vbak-auart, it_vbak-bname, it_vbak-kunnr) FROM vbak WHERE bname IN so_bname. APPEND it_vbak. “ 추가 작업. ENDSELECT. *----------------------------------------------------------------------* *-- 개 선 후 WHERE bname IN so_bname. LOOP AT it_vbak. “ 추가 작업. ENDLOOP. |
- 내부 테이블의 내용을 한 번에 전송하는 것이 더 효과적이다.
- 데이터를 내부 테이블로 한 번에 읽어 들이는 방법의 또 다른 효과는 처리시간이 매우 짧다는 것이다.
- SELECT .. ENDSELECT. 내에서 많은 처리를 할수록 ORAO155 Snapshot too old(rollback segment too small) 오류를 만날 확률이 높다.
- 수행시간이 긴 SELECT 문장은 동일한 작업 대상 테이블에 대한 INSERT/UPDATE 문장과 I/O Contention을 일으킬 확률이 높으며, 이럴 경우, DB Server가 반드시 read consistency를 보장한다고 단언하기 어렵다.
1.8 VIEW를 이용한 Nested SELECT 개선
- 아래 예는 여러 개의 테이블에서 데이터를 가져올 때, view를 이용하여 NESTED SELECT를 개선하는 방법을 보여준다.
*-- 개 선 전 SELECT vbeln auart bname kunnr INTO (vbak-vbeln, vbak-auart, vbak-bname, vbak-kunnr) FROM vbak WHERE vbeln IN so_vbeln. SELECT posnr matnr kwmeng meins INTO … FROM vbap WHERE vbeln = vbak-vbeln. WRITE : / vbak-vbeln, vbak-auart, vbak-bname, vbak-kunnr. ENDSELECT. ENDSELECT. *----------------------------------------------------------------------* *-- 개 선 후 SELECT * FROM zv_vbak “ VIEW WHERE vbeln IN so_vbeln. WRITE : / … ENDSELECT. |
- VBAK : 716건, VBAP 2779건의 데이터를 대상으로 테스트 한 결과는 아래와 같다.
n 개선 전: 3,892,184 microseconds(약 4초)
n 개선 후: 981,385 microseconds(약 1초)
- Nested SELECT 대신에 VIEW를 이용하여 두 개의 테이블을 JOIN한 다음, VIEW에서 데이터를 읽어오는 것이 훨씬 수행 속도가 빠르다.
- 그러나, DB VIEW는 버퍼를 경유하지 않고 바로 DB에서 데이터를 읽어 오기 때문에 두 개의 테이블 중 하나가 버퍼링이 되고 있는 경우, 버퍼링의 장점을 살리지 못하여, 늦어지는 경우가 아주 가끔 발생할 수도 있다. 이런 경우는 SQL Trace를 이용하여 실제로 수행시간을 측정하고 결과가 좋은 것을 사용하도록 한다.
- DB Dictionary 의 DB VIEW는 항상 INNER JOIN이다.
- VIEW생성시 MANDT열을 가져오도록 정의한 테이블을 가장 먼저 읽는다.
1.9 SELECT FOR ALL ENTRIES를 이용한 OUTER JOIN
- 아래는 SELECT FOR ALL ENTRIES를 이용하여 ABAP에서 OUTER JOIN을 구현한 예이다.
*-- 개선 전. SELECT vbeln auart … INTO … FROM vbak WHERE …. SELECT posnr matnr … INTO … FROM vbap WHERE vbeln = vbak-vbeln. … ENDSELECT. ENDSELECT. *----------------------------------------------------------------------* *-- 개선 후. SELECT vbeln auart… INTO TABLE it_vbak FROM vbak WHERE vbeln IN so_vbeln. SELECT vbeln posnr matnr … INTO TABLE it_vbap FROM vbap FOR ALL ENTRIES IN it_vbak WHERE vbeln = it_vbak-vbeln. “ vbeln과 it_vbak-vbeln은 같은 형과 길이여야 한다. LOOP AT it_vbak. READ TABEL it_vbap WITH KEY vbeln = it_vbak-vbeln BINARY SEARCH TRANSPORTING NO FIELDS. LOOP AT it_vbap FROM sy-tabix. … ENDLOOP. ENDLOOP. |
- OUTER JOIN을 할 경우 위처럼 FOR ALL ENTRIES IN 구문을 이용하여 SELECT한 후, 내부 테이블에서 처리하도록 한다.
1.10 SQL 함수 사용하기
- SELECT구문에서 LOOP를 돌면서 처리하는 형태의 합, 평균을 구하는 방법을 SQL 함수를 이용하여 개선하는 예이다.
DATA : BEGIN OF vbqty OCCURS 0. matnr LIKE vbap-matnr, kwmeng LIKE vbap-kwment, meins LIKE vbap-meins. *-- 개선 전. SELECT matnr kwmeng meins INTO vbqty FROM vbap WHERE … . COLLECT vbqty. ENDSELECT. *----------------------------------------------------------------------* *-- 개선 후. SELECT matnr SUM( kwmeng ) meins INTO TABLE vbqty FROM vbap WHERE … GROUP BY matnr meins. |
- 10,000건의 데이터를 처리하였을 경우, 아래와 같은 성능 차이가 나왔다.
n 개선 전: 2,370,000 microseconds
n 개선 후: 1,574,000 microseconds
- GROUP BY 절은 Pool Table이나 Cluster Table에는 사용될 수 없다(BSEG, VBFA).
댓글
댓글 쓰기