카테고리 보관물: DEV

MSSQL에서 Oracle Linked Server 설정

1. 오라클 ODAC 설치
https://www.oracle.com/technetwork/topics/dotnet/index-090165.html


– 64 비트 Oracle Data Access Components (ODAC) 다운로드 (ODAC122010_x64.zip)

2. tnsnames.ora 파일복사

설치한 경로의 sample폴더에 있는 tnsnames.ora 파일을 복사하여 Admin 폴더 아래에 붙혀넣는다.
(예 c:\oracleexe/app/oracle/product/11.2.0/server/network/admin/sample/tnsnames.ora)
3. 연결정보 입력
– tnsnames.ora 파일에 연결정보를 입력 한다.

 

 *TNS명 =
(DESCRIPTION =
(ADDRESS = (PROTOCOL = TCP)(HOST = *IP주소)(PORT = *포트번호))
(CONNECT_DATA =
(SERVER = DEDICATED)
(SERVICE_NAME = *DB명)
)
)

 

 

예) TNS명 : ISM / DBName : MMC / IP:222.222.222.222 / 포트번호: 1521

ISM =
(DESCRIPTION =
(ADDRESS = (PROTOCOL = TCP)(HOST = 222.222.222.222)(PORT = 1521))
(CONNECT_DATA =
(SERVER = DEDICATED)
(SERVICE_NAME = MMC)
)
)
4. Inprocess허용

링크드 서버를 등록할때 다음과 같은 에러 메시지가 나올것이다.

연결된 서버 “ISM시스템’에 대한 OLE DB공급자 “OraOLEDB.Oracle”의 인스턴스를 만들 수 없습니다.
(Microsoft SQL Server, 오류:7302)

해결방법 : Inprocess 허용을 해줘야 한다.

 

연결된 서버 > OraOLEDB.Oracle 의 속성창을 연다.

 

 

 

 

5. MSSQL에 링크드서버 등록

 

 

예) 위 예제 내용을 등록

 

**. Oracle Database User / Password 설정

[새 연결된 서버 팝업창] ->[패이지선택 영역] -> [보안] -> [다음 보안 컨텍스트를 사용하여 연결]

> 원격로그인 : ID

> 암호 : PW

 

6. 5번까지 했는데 링크드서버가 등록되지 않는다면 재부팅을 해야한다.

오라클 드라이버가 재부팅을 해야 적용되는 경우도 있다. (방화벽도 체크 해보시길)

MySQL Workbench의 VISUAL EXPLAIN으로 인덱스 동작 확인하기

인덱스의 기초

인덱스의 구조

인덱스는 MySQL 5.6Reference ManualThe Physical Structure of an InnoDB Index에 나와 있듯이, B-tree 구조로 되어 있습니다. 이번 포스팅에서는 B-tree 구조에 대해서 자세히 다루지 않고, 아주 간략한 이미지만 아래에 첨부했습니다.

리프(leaf) 노드는 인덱스로 지정한 컬럼의 오름차순으로 정렬되어 있으며, 실제 행이 어디에 저장되어 있는지에 대한 데이터를 가지고 있습니다. WHERE col1 = 3과 같은 등호 연산이나 WHERE col1 < 2와 같은 범위 검색을 빠른 속도로 처리할 수 있습니다.

복합 인덱스는 아래 그림처럼 지정한 컬럼 순(여기에서는 col1 col2 순)으로 정렬됩니다. 이렇게 만든 복합인덱스는 WHERE col1 <= 2 조건에는 사용할 수 있지만, WHERE col2 > 1 조건에는 사용할 수 없습니다. col2는 col1 내에서는 정렬되어 있지만, 전체적으로 보았을 때는 정렬되지 않은 상태(그림에서 col2는 왼쪽부터 1 2 3 1 2 1)이기 때문입니다. 따라서, 이 인덱스로는 col2에서 1보다 큰 값이 어디에 있는지 알 수가 없습니다. 마찬가지로, WHERE col1 <= 2 AND col2 >= 2의 경우에도 col1 조건은 트리를 순회하면 탐색할 수 있지만, col2는 트리를 사용해서 찾을 수 없습니다. col1이 정해지면 col2는 정렬된 상태이니, WHERE col1 = 1 AND col2 >= 2에는 사용할 수 있습니다. 이 부분에 대해서는 MySQL with InnoDB 인덱스의 기초 지식과 자주 하는 실수(일본어)도 같이 읽어 보시기 바랍니다.

인덱스 사용법

MySQL 5.6 Reference Manual / 8.3.1 How MySQL Uses Indexes를 꼼꼼히 읽어 보면 주로 사용되는 경우와 추가로 사용 가능한 경우를 알 수 있습니다.

  • WHERE와 ORDER BY, GROUP BY를 신속하게 수행하기 위해 사용됨
  • 원칙적으로 테이블 하나당 1개의 인덱스가 사용됨
  • 테이블의 행 개수는 많고, 검색 결과의 행 개수는 적을 때 사용됨
  • 쿼리가 테이블에 적재된 대부분의 행에 접근할 경우에는, 디스크 탐색이 최소화되는 Full Table Scan이 인덱스 사용하는 것보다 속도가 더 빠름
    • 따라서, 행 개수가 적은 테이블에서는 Full Table Scan을 사용하는게 좋음
    • MySQL5.6 문서에 따르면 테이블 사이즈, 행 개수, I/O 블록 사이즈 등에 따라 인덱스 사용 여부가 결정됨
  • 복합 인덱스는 단일 컬럼 인덱스 대용으로도 사용 가능함
  • (col1, col2, col3)의 3가지 컬럼 인덱스가 있다면, (col1), (col1, col2), (col1, col2, col3)에 대해 인덱스를 사용할 수 있음
  • 데이터 행 참조없이 값을 취득할 수 있는 인덱스(커버링(covering) 인덱스) 사용 시 처리 속도가 향상됨
  • 쿼리에 필요한 모든 컬럼을 인덱스가 포함하고 있는 경우가 해당됨
  • 예시: column1과 column2가 인덱스에 포함되어 있는 상황일 경우
    • 아래 쿼리는 인덱스에서 모든 값을 취득할 수 있기 때문에 데이터 행 확인이 불필요함
        1. SELECT column1 FROM tbl_name WHERE column2 = 1;
    • 아래 쿼리는 데이터 행 확인이 필요함
        1. SELECT * FROM tbl_name WHERE column2 = 1;
  • 인덱스가 설정된 컬럼에 대해 MAX()나 MIN() 값을 검색하기 위해 사용됨
  • 여러 인덱스 중에서 선택할 땐, 행 개수가 가장 적은 것을 검색하는 인덱스 사용함
  • 다른 테이블과 JOIN하여 사용할 땐, 컬럼 타입과 사이즈가 동일하면 인덱스를 효율적으로 사용할 수 있음

MySQL Workbench와 VISUAL EXPLAIN 소개

MySQL Workbench는 MySQL의 GUI 클라이언트입니다. GUI에서 인덱스를 추가 및 삭제하거나 ERD(Entity Relationship Diagram)를 그리는 것도 가능하기 때문에, 시행착오를 겪으며 인덱스를 설정할 때 꽤 도움이 됩니다. 그리고 무엇보다 VISUAL EXPLAIN이 가능하다는 사실! 여기에서 ‘Download Now’ 버튼을 클릭하면 다운로드할 수 있습니다.

VISUAL EXPLAIN 실행 방법

VISUAL EXPLAIN을 실행하려면 쿼리 입력란에 SQL문을 작성하고 왼쪽 상단에 위치한 돋보기 마크나 메뉴의 Query > Explain Current Statement, 또는 단축 키 Cmd + Option + x(macOS의 경우)를 누르면 됩니다.

EXPLAIN 결과가 보고 싶을 때는 위 그림에서 중앙 왼쪽 셀렉트 박스를 변경하면 됩니다. 화면 중앙 오른쪽에 보이는 View Source 버튼을 누르면, VISUAL EXPLAIN의 원래 정보를 JSON 형식으로 확인할 수 있습니다. 사용해보면 참 편리한 기능입니다.

VISUAL EXPLAIN의 색상과 테이블 도형의 텍스트에 대한 간단한 설명

컬러풀한 사각형 오브젝트는 테이블에 어떻게 액세스하는지를 나타냅니다. 파란색에 가까울수록 비용이 낮고, 빨간색에 가까울 수록 비용이 높은 액세스입니다. 아래쪽 빨간색 두 가지, index와 ALL은 인덱스를 활용한 튜닝 대상으로 자주 거론됩니다.

색상 오브젝트 내 텍스트 EXPLAIN TYPE
Single row: constant const
Unique Key Lookup eq_ref
Non-Unique Key Lookup ref
Fulltext Index Search fulltext
Index Range Scan range
Full Index Scan index
Full Table Scan ALL

(위 표에서 Lookup은 where col = 1과 같은 동등 비교입니다.)

VISUAL EXPLAIN으로 인덱스 동작 확인하기

이제 본론으로 들어가겠습니다. 파란색에서 빨간색으로 갈수록 비용이 커진다는 점만 알고 있으면, 보는 법을 자세하게 기억해 두지 않아도 사용할 수 있습니다.

이번 테스트에 사용할 두 테이블은 어느 사용자가 컨버전했는지 저장해 두는 cv 테이블과 이와 매칭되는 광고의 ad 테이블입니다. 테스트를 위해 만든 데이터의 대략적인 특성은 다음과 같습니다.

  • 인덱스가 간단하니, 이 시점에선 PRIMARY KEY만 사용
  • 각 테이블에 적재시킨 데이터의 양은 cv가 약 100만 건, ad는 약 4000건
  • cv의 status와 ad의 type은 각각 균등하게 10종류씩 설정
  • 시간을 저장하는 컬럼은 UNIX TIME, 최근 한 달간의 데이터를 베이스로 저장

ERD는 일부러 MySQL Workbench에서 출력했습니다. Workbench는 외부 키를 지정해 두면 자동으로 relation이 도식화되는데요(참고). 이번에는 설명을 간략화하기 위해 PRIMARY KEY만 지정했기 때문에 아래 그림과 같이 표시되었습니다.

설정된 인덱스 없음

먼저, 설정된 인덱스가 없는 상태를 보시겠습니다.

  1. SELECT * FROM cv WHERE status = 2 ORDER BY created_at LIMIT 100;

이 쿼리에 대해 VISUAL EXPLAIN을 실행하면 아래 이미지가 나옵니다.

아래는 위 VISUAL EXPLAIN을 EXPLAIN으로 보았을 때 나타나는 표입니다.

화살표 방향대로 읽으면, cv 테이블 전체를 스캔해서 얻은 100만 개의 행을 ORDER BY로 정렬해서 결과를 얻었다는 것을 알 수 있습니다. 새빨간색이네요. 100만 개나 되는 행을 스캔해서 WHERE 조건에 부합하는 행을 찾고, 전체를 정렬해서 100건을 반환하기 때문에 처리 부하가 큽니다. 인덱스가 필요합니다.

WHERE 조건에 인덱스 설정

위 SQL이 보다 빠르게 처리될 수 있도록 인덱스를 추가하겠습니다.

먼저 WHERE 조건인 status에 인덱스를 지정합니다. 인덱스 덕분에 status = 2인 레코드만 가져오는 것이 쉬워집니다.

  1. ALTER TABLE cv ADD INDEX idx_status (status);

아래는 위 VISUAL EXPLAIN을 EXPLAIN으로 보았을 때 나타나는 표입니다.

cv 테이블이 초록색으로 변했습니다. 약 21만 건을 가져와서 정렬하기 때문에 앞서 나왔던 100만 건의 경우보다 처리 속도가 상당히 빨라질 듯 합니다. 여기서 21만 건이라는 수치는 MySQL의 예측치이고, 실제로 status = 2인 레코드는 10만 건 정도입니다. 적재된 데이터의 status 컬럼값이 편중되어 있어서 대부분이 status = 2인 상황이라면, 인덱스를 사용하지 않아야 속도가 더 빠르다고 판단되어 Full Table Scan이 수행될 것 같습니다(인덱스 사용법 섹션 참고). 그런 경우라면 ORDER BY에 인덱스를 설정하도록 합시다.

보충 설명: WHERE 조건에 인덱스 설정

적재된 데이터의 status 컬럼값이 편중되어 있어서 대부분이 status = 2인 상황이라면, 인덱스를 사용하지 않아야 속도가 더 빠르다고 판단되어 Full Table Scan이 수행될 것 같습니다.

위 내용을 확인하기 위해 테스트를 했습니다. 테스트는 ANALYZE TABLE cv; 를 실행한 후에 SELECT * FROM cv WHERE status = 2;를 실행하는 방식으로 진행했습니다. 그런데 cv 테이블의 레코드 수가 100만 건일 때는 대부분이 아니라 모든 레코드를 status = 2로 맞춰놓아도 Full Table Scan이 수행되지 않고 status에 지정된 인덱스가 사용되었습니다. 반면에 레코드 수를 약 1만 건으로 줄이니 동일한 쿼리에서 Full Table Scan이 수행되었습니다. 인덱스 사용법 섹션에서 언급했던 것처럼 행 개수에 따라서도 달라지는 것 같습니다.

어쨌든, 여기에서 나오는 status = 2처럼 컬럼 값이 한쪽으로 매우 편중되어 있어서 대부분의 행이 조건에 해당될 때는 인덱스를 달지 않는 것이 좋겠습니다.

ORDER BY 조건에 인덱스 설정

(앞에서 설정한 인덱스는 DROP)

  1. ALTER TABLE cv ADD INDEX idx_created_at (created_at);

아래는 위 VISUAL EXPLAIN을 EXPLAIN으로 보았을 때 나타나는 표입니다.

이번에는 ORDER가 연두색이 되었습니다. 테이블은 Full Index Scan입니다. 처리 과정을 설명드리겠습니다.

created_at 인덱스에서 순서대로 레코드를 추출하여 status = 2인지 여부를 확인합니다. 그리고 status = 2인 레코드가 LIMIT 수만큼 발견되었을 때 탐색이 종료될 수 있습니다(created_at에 대해 정렬이 완료되었기 때문). 그래서 예상 취득 레코드 개수도 100개입니다. status = 2인 레코드가 빨리 발견되면 좋겠지만, 마지막까지 발견되지 않으면 인덱스를 전부 읽어야 합니다. status = 2가 드물게 존재하는 경우엔 이런 상황이 벌어지게 되니, status에 인덱스를 설정해서 WHERE로 취득되는 레코드 수를 줄이는 것이 좋을 듯 합니다(개수가 적으면 정렬에 소요되는 비용도 적음). 이 부분은 대충 하는 MySQL 퍼포먼스 튜닝(일본어)에 알기 쉽게 정리되어 있습니다. 참고하시면 좋을 것 같습니다.

WHERE 조건과 ORDER BY 조건 중 어느 쪽에 인덱스를 지정하는 것이 좋을지는 status가 편중되어 있는 정도를 보고 판단하는 것이 좋겠습니다. 아니면 idx_status와 idx_created_at 양쪽 모두에 달아 보고 어느 쪽에서 사용되는지를 확인하는 것도 괜찮습니다. 이번 경우에는 양쪽에 지정하니 idx_created_at가 선택되었습니다. 단, ORDER BY 조건에 인덱스를 지정할 경우, LIMIT 수가 커지면 WHERE 조건에 맞는 레코드를 찾기 위해 많은 행을 읽어야 하는 상황이 벌어집니다. 반면에, WHERE 조건에 지정한 인덱스를 사용하는 경우에는 LIMIT 수가 탐색에 드는 비용에 영향을 미치지 않기 때문에 LIMIT 수가 클 때는 idx_status가 더 좋습니다. 이번 데이터를 쭉 확인해 보니, 대략 LIMIT 2100보다 커지면 idx_status가 사용되었습니다.

GROUP BY 조건에 인덱스 설정

(앞에서 설정한 인덱스는 DROP)

  1. SELECT ad_id, COUNT(*) FROM cv WHERE status = 2 GROUP BY ad_id;

ad_id별로 cv의 수를 확인하는 쿼리입니다. 인덱스가 없는 경우에는 아래 그림처럼 빨간색이 됩니다.

아래는 위 VISUAL EXPLAIN을 EXPLAIN으로 보았을 때 나타나는 표입니다.

GROUP BY 조건인 ad_id에 인덱스를 지정하면 아래 그림처럼 GROUP BY가 연두색이 됩니다.

아래는 위 VISUAL EXPLAIN을 EXPLAIN으로 보았을 때 나타나는 표입니다.

위 그림과 아래 그림을 비교하면 GROUP 밑에 있었던 tmp table, filesort가 사라졌습니다.

GROUP BY 때 왜 인덱스를 사용하는지 설명드리겠습니다. 인덱스를 지정하지 않으면 테이블 전체를 스캔한 후, 각 그룹의 모든 행이 연속적인 상태인 임시 테이블(tmp table)을 생성하여(이때 정렬(filesort)이 필요함), 이 테이블에서 그룹을 찾아내는 처리가 진행됩니다(MySQL 5.6 Reference Manual / GROUP BY Optimization). 위 그림을 보시면 인덱스가 임시 테이블의 역할을 해주어서 이와 같은 처리가 불필요해졌다는 점을 알 수 있습니다.

다만, GROUP BY 처리는 빨라지는 반면에 테이블 접근은 Full Index Scan이어서 처리 부하가 커지니, WHERE 조건에 인덱스를 지정하는 방법을 검토하는 게 좋겠습니다.

복합 인덱스 설정

(앞에서 설정한 인덱스는 DROP)

ALTER TABLE cv ADD INDEX idx_status_created_at (status, created_at);

인덱스를 설정하여 다시 아래의 SQL을 VISUAL EXPLAIN합니다.

  1. SELECT * FROM cv WHERE status = 2 ORDER BY created_at LIMIT 100;

아래는 위 VISUAL EXPLAIN을 EXPLAIN으로 보았을 때 나타나는 표입니다.

테이블 접근과 ORDER 모두 초록색으로 되어 있는 바람직한 상태입니다(표 형식의 EXPLAIN과 비교해 봤을 때, VISUAL EXPLAIN은 어떤 부분을 개선했는지 한눈에 볼 수 있다는 점이 좋습니다).

연습: 더 복잡한 예

(앞에서 설정한 인덱스는 DROP)

컨버전 수를 일주일 전부터 카운트하여, 카운트 수가 많은 순서대로 ad 테이블의 레코드를 정렬하는 쿼리를 살펴 보겠습니다. 추출하려는 데이터의 상세 조건은 cv의 status는 1, ad의 type은 2입니다.

  1. SELECT
  2. ad.id,
  3. COUNT(DISTINCT cv.user_id) as cv_count
  4. FROM
  5. ad
  6. INNER JOIN cv
  7. ON cv.ad_id = ad.id
  8. AND cv.status = 1
  9. AND cv.created_at >= UNIX_TIMESTAMP(CURDATE() – INTERVAL 7 DAY)
  10. WHERE
  11. ad.type = 2
  12. AND ad.end_at >= UNIX_TIMESTAMP(CURDATE() – INTERVAL 7 DAY)
  13. GROUP BY
  14. ad.id
  15. ORDER BY
  16. cv_count DESC
  17. LIMIT 100
  18. ;

위 쿼리를 VISUAL EXPLAIN하니 아래와 같은 이미지가 나왔습니다.

아래는 위 VISUAL EXPLAIN을 EXPLAIN으로 보았을 때 나타나는 표입니다.

조금 복잡해졌는데요. VISUAL EXPLAIN 결과는 왼쪽 아래에서 오른쪽 위 방향으로 처리가 진행되도록 그림이 그려져 있습니다. 따라서, 왼쪽 아래부터 보시면 됩니다. 이 그림을 보고 알 수 있는 점은 다음과 같습니다.

  • JOIN이 nested loop로 처리됨
  • nested loop에 대해서는 아래 참고
  • driving table 행 하나당 inner table 1행씩 스캔해서 결합 조건에 부합하는 것을 추출함
  • cv 테이블에서 읽은 행과 대응되는 ad 테이블의 행을 PRIMARY KEY를 사용해서 읽음
  • cv 테이블이 바깥쪽 루프(driving table, outer table)
  • ad 테이블이 안쪽 루프(inner table)
  • buffer_result라는 임시 테이블이 생성됨
  • 임시 테이블을 사용해서 GROUP BY 처리
  • 마지막으로 ORDER BY 처리

(아래 내용은 인덱스를 어떻게 지정할 지 고민하면서 시행착오를 겪었던 것을 적은 것인데요. 제 경험을 적은 것일 뿐이고 꼭 이렇게 해야 한다는 의미는 아닙니다. 하나의 예로 봐 주시면 좋겠습니다.)

위 그림을 보면, 우선 cv 테이블의 Full Table Scan을 중단시키고 싶습니다. 그리고 ad 테이블도 결과가 초록색이고 PRIMARY KEY를 사용하고 있긴 하지만, type이나 end_at으로 ad 테이블 자체에 조건을 걸때는 인덱스로 사용할 수 없어서 속도가 느려질 수밖에 없습니다. 따라서, ad쪽에도 인덱스를 생성하여 결과를 살펴보겠습니다.

아래와 같은 인덱스를 설정하고 과연 어떤 것이 사용되는지 확인해 보았습니다.

  • cv 테이블
  • idx_status_created_at: (status, created_at)
    • cv 테이블의 작업 범위 결정 조건으로 사용
  • idx_ad_id_status_created_at: (ad_id, status, created_at)
    • cv에 관한 조건 순으로 지정
  • ad 테이블
  • idx_type_end_at: (type, end_at)
    • ad 테이블의 작업 범위 결정 조건으로 사용

이 상태로 VISUAL EXPLAIN을 실행하면 아래와 같이 나옵니다.

아래는 위 VISUAL EXPLAIN을 EXPLAIN으로 보았을 때 나타나는 표입니다.

ad와 cv의 위치가 바뀌는 등 여러 군데가 변경되면서 개선되었습니다. 이 그림을 보고 알 수 있는 점은 다음과 같습니다.

  • ad 테이블이 driving table이 되고 type이나 end_at의 조건을 지정하기 위해 idx_type_end_at 인덱스가 사용됨
  • cv 테이블의 idx_ad_id_status_created_at을 사용하여 ad 테이블에 대응되는 행을 읽음
  • cv에 관한 status나 created_at의 조건이 이 인덱스로 지정되었는지는 알 수 없음
  • 임시 테이블 buffer_result가 삭제되고 GROUP BY의 tmp table도 삭제됨(GROUP BY는 색상도 오렌지로 바뀜)

임시 테이블인 buffer_result가 삭제된 이유는 GROUP BY 조건이 driving table(이중 루프의 바깥쪽)의 ad.id라서, 미리 정렬한 다음 루프를 실행하면 GROUP BY 조건에 인덱스 절에서 설명한 임시 테이블이 불필요해지기 때문인 것으로 생각됩니다(참고: 실제 사례로 배우는 JOIN(NLJ)이 지연되는 이유와 대처법 / 속도 향상이 어려운 JOIN SQL(일본어)).
Inner table에서 GROUP BY를 하려면 임시 테이블이 필수로 있어야 합니다. 실제로 인덱스를 설정하기 전 상태(cv가 driving table인 상태)에서 GROUP BY를 inner table의 ad.id가 아닌 cv.ad_id로 하면 임시 테이블이 생성되지 않습니다.

복합 인덱스를 사용했을 때, 복합 인덱스의 일부만 사용된다면 단일 인덱스를 지정하는 것이 더 좋습니다(예를 들어, cv 테이블에 설정한 인덱스가 ad 테이블과 대응하는 용도로만 사용되는 경우, 즉 ad_id만 사용되는 경우라면 ad_id에 인덱스를 달면 됨). 따라서, JSON 형식으로 바꿔 used_key_parts를 확인합니다. VISUAL EXPLAIN 실행 방법 섹션의 개요도에 나와 있는 대로 View Source를 클릭해서 확인할 수 있습니다.

아래는 View Source로 확인한 JSON입니다. ad 테이블의 인덱스는 전부 다 사용되고 있으며, cv 테이블의 인덱스의 경우 created_at은 사용되지 않고 ad_id와 status가 사용된다는 점을 알 수 있습니다.
즉, cv의 인덱스는 ad_id와 status만 있으면 되기 때문에, 나중에 created_at을 포함하지 않는 인덱스를 만들었습니다.

  1. {
  2. “nested_loop”: [
  3. {
  4. “table”: {
  5. “table_name”: “ad”,
  6. “key”: “idx_type_end_at”,
  7. “used_key_parts”: [
  8. “type”,
  9. “end_at”
  10. ],
  11. },
  12. {
  13. “table”: {
  14. “table_name”: “cv”,
  15. “key”: “idx_ad_id_status_created_at”,
  16. “used_key_parts”: [
  17. “ad_id”,
  18. “status”
  19. ],
  20. }
  21. }
  22. ]
  23. }

cv 테이블의 인덱스에서 created_at이 사용되지 않는 이유는, 이번 블로그 포스팅용으로 만든 데이터의 대부분이 created_at 조건을 충족하고 있어서 MySQL이 인덱스 순회보다는 직접 데이터를 보고 판정하는 것이 좋겠다고 판단했기 때문인 것 같습니다.

MySQL 5.6 Reference Manual / 8.3.1 How MySQL Uses Indexes

쿼리로 대부분의 행에 접근해야 하는 경우에는 순차적으로 읽어들이는 것이 인덱스를 이용하는 것보다 빠릅니다.

그 외에도 여러 인덱스를 설정해 봤는데요. 위의 복합 인덱스 조합(ad 테이블에 type과 end_at, cv 테이블에 ad_id와 status)이 가장 속도가 빨랐습니다.
인덱스를 지정한 후 SQL의 duration은 약 0.007sec가 되었으며, 인덱스 설정 전과 비교하면 약 40분의 1로 단축되었습니다.

참고로 같은 결과를 얻기 위해 서브 쿼리를 사용한 SQL도 테스트해 봤는데요. VISUAL EXPLAIN으로 보면 그림이 복잡하고 실행 시간도 오래 걸렸습니다.

마치며

MySQL Workbench의 VISUAL EXPLAIN을 사용해서 인덱스의 동작을 확인해 보았습니다.

VISUAL EXPLAIN은 직관적으로 이해하기 쉬워서 인덱스를 어떻게 지정해야 할지 고민될 때 사용하면 편리한 것 같습니다. 또한, 인덱스 뿐 아니라 복잡한 쿼리를 어떻게 작성해야 할지 고민될 때도, 쿼리를 작성한 뒤 VISUAL EXPLAIN 결과를 확인하여 임시 테이블이 지나치게 많이 생성되지는 않는지 등을 체크하는 용도로 활용할 수 있을 것 같습니다.

[Svn] 이클립스(eclipse)에서 특정 폴더 및 파일 버전관리 대상에서 제외하는 방법

컴파일되는 폴더나 파일들…

.classpath 같은 파일들은 각 서버나 PC의 환경이 모두 다르기 때문에 굳이 버전관리를 하지 않아도 됩니다.

이런 경우, 이클립스에서 어떻게 버전관리에서 제외하는지 알아보겠습니다.

classes 폴더 및 그 하위 폴더 및 파일들을 모두 제외하려면…

*/classes

*/classes/**

이 두 개를 패턴에 추가해 주면 됩니다.

아직 commit 전인데 폴더 앞에 ?가 안 붙어 있는 걸 확인할 수 있습니다.

버전관리에서 제외되었기 때문입니다.

Windows 10에서 Tomcat과 IIS 연동하기

ISAPI(Internet Server Application Programming Interface) 확장 모듈을 사용해서 특정 URL 또는 특정 확장자(*.jsp, *.do)에 대한 요청이 오면 Tomcat으로 요청을 돌리도록 설정하게 됩니다.

1. 연동 설정에 사용된 소프트웨어는 다음과 같습니다.

모두 64bit 또는 32bit에 맞춰야 합니다.

– Windows 10 64bit

– IIS(Internet Information System)

– JDK 10(7 이상이면 가능)

– Tomcat 8.5.32

– Tomcat Connector 1.2.43(ISAPI 모듈)

2. 기본 소프트웨어 설치하기

– IIS 의 설치는 Windows 10에 IIS 설치하기 를 참조하세요.

– JDK의 설치는 윈도우에 JDK 8 설치하기를 참조하세요.

– Tomcat의 설치는 Windows 10에 Tomcat 설치하기를 참조하세요.

3. IIS에 ISAPI 설치하기

Windows 기능 켜기/끄기 에서 “ISAPI 필터”와 “ISAPI 확장”을 체크하고 “확인” 버튼을 눌러 설치합니다. (Windows 기능 켜기/끄기의 실행은 IIS 설치 글에서 확인하세요.)

4. Tomcat Connector 설치하기

4.1. http://tomcat.apache.org 사이트 왼쪽의 Download에서 Tomcat Connectors 를 클릭해서 들어갑니다.

4.2. Tomcat Connectors JK 1.2 항목에서 “Binary Releases for selected versions” 를 클릭해서 들어갑니다.

4.3. 바이너리 인덱스 페이지에서 “windows” 를 클릭해서 들어갑니다.

4.4. 윈도우즈 바이너리 페이지에서 “tomcat-connectors-1.2.43-windows-x86_64-iis.zip” 파일을 다운로드 받습니다.

4.5. 다운받은 압축파일의 압축을 풀면 나오는 “isapi_redirect.dll” 파일이 필요한 파일 입니다.

설치는 임의의 위치를 정해서 파일을 복사해 두면 됩니다. 폴더 위치른 다음과 같이 정했습니다.(자신이 원하는 위치에 두면 됩니다.) 여기서는 D:\util 폴더 아래에 Tomcat Connector 폴더, 그 아래 bin 폴더를 만들고 dll 파일을 두었습니다.

D:\util\Tomcat Connector\bin\isapi_redirect.dll 

Tomcat Connector 폴더 아래에 그외에 설정파일을 둘 conf 폴더와 로그 파일을 둘 log 폴더를 만들었습니다.

5. Tomcat Connector 설정파일 만들기

바이너리 배포파일내에 설정 파일은 포함되어 있지 않습니다. 커넥터가 어떻게 동작할지 지정하는 설정파일을 만들어야 합니다.

5.1. isapi_redirect.properties 파일을 작성합니다.

D:\util\Tomcat Connector\bin 폴더 아래 생성합니다. 이 파일은 isapi_redirect.dll 파일과 같은 곳에 있어야 합니다. 만약 Tomcat Connector의 설치 위치를 이 글에서와 다르게 했다면 이파일의 위치는 dll 파일과 같은 폴더에 있으면 됩니다. 파일 내용중 #으로 시작하는 라인은 주석(Comment) 입니다.

# IIS가 필터를 찾는 위치 입니다. 나중에 IIS관리자에서 가상디렉토리로 /jakarta 를 만들것입니다.

extension_uri=/jakarta/isapi_redirect.dll

# 로그 파일의 저장 와 로그파일의 이름 입니다. 경로는 절대경로로 기입합니다.

log_file=D:\util\Tomcat Connector\log\isapi_redirect.log

# 로그 레벨 : debug, info, warn, error, trace

log_level=info

# IIS에서 redirect할 서버들의 정보를 지정한 파일입니다. 여러 Tomcat 서버로 부하를 분산할 수 있습니다.

worker_file=D:\util\Tomcat Connector\conf\workers.properties

# Tomcat 서버(Worker)로 요청을 보낼 URI 를 지정하는 파일 입니다.

worker_mount_file=D:\util\Tomcat Connector\conf\uriworkermap.properties

5.2. workers.properties 파일을 작성합니다.

D:\util\Tomcat Connector\conf 폴더 아래 생성합니다. 이 파일에 IIS에 연결될 Tomcat 서버들의 정보를 설정하게 됩니다. 이 글에서는 하나의 Tomcat 서버만을 사용하지만 여러개의 Tomcat 서버를 두고 부하를 분산하는 것도 가능합니다.

# Tomcat 서버를 지정합니다. 콤마로 분리해서 여러개를 지정할 수 있습니다.

worker.list=worker1

# Tomcat과 통신하는 프로토콜을 지정합니다.

worker.worker1.type=ajp13

# Tomcat 서버의 IP 주소 입니다.

worker.worker1.host=localhost

# Tomcat 서버의 ajp13 통신 포트 입니다. Tomcat의 server.xml 에 지정되어 있습니다.

worker.worker1.port=8009

workers.properties 파일의 자세한 설정방법은 workers.properties configuration 를 참조하세요.

5.3. uriworkermap.properties

D:\util\Tomcat Connector\conf 폴더 아래 생성합니다. 특정 URI로 들어온 요청을 Tomcat으로 보내도록 지정합니다.

# 확장자가 jsp인 요청을 Tomcat으로 reidrect 합니다.

/*.jsp=worker1

– uri 패턴은 대소문자를 구분합니다.

– # 으로 시작하는 행은 주석 입니다.

– 패턴과 Worker명 사이의 = 기호 앞과 뒤의 공백은 무시됩니다.

– 패턴 매칭 기호로 ?, *, |, !등을 사용할 수 있습니다.

uriworkmap.properties 파일의 자세한 설정 방법은 uriworkermap.properties configuration 를 참조하세요.

6. IIS에 테스트용 웹사이트 만들기

6.1. IIS관리자에서 왼쪽 트리에서 “사이트”를 클릭하고 화면 오른쪽 위의 작업에서 “웹 사이트 추가…“를 클릭합니다.

6.2. 사이트 정보를 입력합니다.

– 사이트 이름은 “Tomcat IIs 연동” 으로 입력합니다.

– 응용프로그램 이름은 기본값으로 사이트 이름과 같은 풀이 생성 되는데, 여기서는 “선택” 버튼을 클릭하여 DefaultAppPool을 선택했습니다.

– 실제 경로는 설치된 Tomcat의 웹 루트 폴더로 지정했습니다. 여기서는 D:\util\Apache Software Foundation\Tomcat 8.5\webapps\ROOT 입니다. IIS 웹 루트와 연동된 Tomcat의 웹 루트가 반드시 같을 필요는 없습니다.

– 포트는 8090으로 지정했습니다. Tomcat 설치시 웹 포트가 8080으로 설치 했다면 그 포트와 충돌하지 않도록 다른 포트로 지정하면 됩니다. 연동이 완료되면  Tomcat의 웹 포트는 닫아 버려도 상관이 없습니다.

– 호스트 이름은 비워 뒀습니다. 테스트용 개인 PC에 도메인이 없기 때문입니다.

– “웹 사이트 즉시 시작” 에 체크했습니다.

사이트가 생성이 되었습니다.

아직은 Tomcat연동이 완료되지 않아서 jsp 호출을 처리하지 못합니다.

7. IIS에서 Tomcat 연동 설정하기

7.1. ISAPI 및 CGI 제한 설정하기

IIS관리자의 왼쪽 컴퓨터 이름을 클릭하여 가운데 화면에서 “ISAPI 및 CGI 제한“을 더블 클릭합니다.

화면 오른쪽의 “추가…” 를 눌러 제한을 추가합니다.

– ISAPI 또는 CGI 경로 에는 isapi_redirect.dll 을 선택합니다.

– 설명에는 Tomcat 을 입력합니다.(임의로 넣어도 됩니다.)

– 확장 경로 실행 허용에 체크합니다.

7.2. 가상디렉토리 추가

Tomcat IIS 연동 사이트에 마우스 오른쪽 키를 클릭해서 팝업 메뉴에서 “가상 디렉터리 추가…“를 선택합니다.

가상 디렉터리 추가 창에서 다음과 같이 입력합니다.

– 별칭은 jakarta를 입력합니다. isapi_redirect.properties에서 지정한 이름 입니다.

– 실제 경로는 Tomcat Connector의 bin 폴더를 지정합니다.

7.3 ISAPI 필터 설정하기

Tomcat IIS 연동 사이트를 클릭하고 가운데 화면 아이콘중에서 ISAPI 필터를 더블클릭합니다.

화면 오른쪽 위의 “추가…” 버튼을 클릭하여 정보를 입력합니다.

– 필터 이름에 “Tomcat” 을 입력합니다.(임의로 입력해도 됩니다.)

– 실행파일에 isapi_redirect.dll 파일을 지정합니다.

7.4. 처리기 매핑을 설정합니다.

Tomcat IIS 연동 사이트를 클릭하고 가운데 화면 아이콘중에서 처리기 매핑을 더블클릭합니다.

사용 안 함에 있는 ISAPI-dll 을 클릭하고 화면 우측의 “기능 사용 권한 편집…“을 클릭하여 설정을 합니다.

사용권한에서 읽기, 스크립트, 실행에 모두 체크합니다. 기본적으로 읽기와 스크립트에는 체크가 되어 있을 것입니다.

8. 서비스를 재시작합니다.

시작버튼을 눌러 Windows 관리도구 -> 서비스를 실행해서 “World Wide Web Publishing 서비스“를 재시작합니다.

Tomcat 도 시작이 되어 있어야 합니다.

9. 연동 결과 확인

IIS 사이트의 포트인 8090으로 jsp 파일이 서비스 되는 것을 확인할 수 있습니다.



출처: https://offbyone.tistory.com/322 [쉬고 싶은 개발자]

윈도우 환경에서 php – mail() 함수 사용

리눅스에서의 sendmail 이 윈도우에서는 불가능 하다는겁니다.
이는 PHP에서 mail() 함수가 작동하지 않는 다는걸 의미합니다.

검색결과 윈도우에서는 EMWAC 라는 공짜 메일서버를 설치하거나
유료 솔루션을 구입해야 한다는 답변들이 많았습니다.
EMWAC의 경우에는 설치를 완료하고 설정을 이리 저리 만져 보았으나
서비스의 실행만 성공적으로 이루어지고 마지막 단계의 telnet localhost 25 를 아무리 해도
접속이 되지 않는 문제로 인해 다른 대안을 생각하게 되었습니다.

제가 찾아낸 훌륭한 대안은 “fake sendmail for windows” 였습니다.
리눅스의 sendmail 을 윈도우에서 흉내낸것입니다.(하지만 외부 smtp 서버 주소가 필요합니다.)
예전에는 SSL/TLS 보안에 대해 지원을 하지 않았지만 최근에는 지원을 하기 때문에
gmail 의 SMTP 를 사용할수 있다는 장점이 있습니다.

세팅 정보는 아래와 같습니다.

c:\windows\php.ini 파일을 열어서 아래와 같은 부분을 수정합니다.
sendmail_path = d:\sendmail -t
여기서 원래는 sendmail -t 이지만 위 사이트에서 받은 sendmail.exe 파일의 경로를 입력합니다.
마지막에 .exe 는 제거합니다.

sendmail.exe 와 같은 폴더에 위치한 sendmail.ini 를 열어 아래와 같이 수정합니다
아래는 gmail 의 세팅입니다 ^^ 참고하세요
smtp_server=smtp.gmail.com
smtp_port=587
auth_username=아이디@gmail.com
auth_password=비밀번호
force_sender=아이디@gmail.com

여기서 이상한점이 force_sender 를 지정하지 않으면 sender email 을 지정하지 않았다고 계속 오류를 뱉어낸다는겁니다. php 에서 mail함수에서 sender email 을 주어도 본문으로 들어가버리더라구요. 제가 php 를 많이 다뤄보지 않아서 정확한 이유는 모르겠습니다.

또하나 smtp_port 번호가 gmail 에서 말하기로는 465 또는 587이라고 하는데
465를 사용할경우에는 응답없음으로 나오면서 소켓 에러가 발생합니다.

위와같이 세팅을 하고 php의 mail 함수를 이용해보면 아주 잘 작동합니다 ^^

출처 : http://seapy.com/112

MsSQL에서 Oracle DB-Link걸기

1. 오라클 클라이언트 설치
http://www.oracle.com/technetwork/topics/winx64soft-089540.html
– 오라클 풀 패키지를 다운로드 받아서 클라이언트를 설치해도 되고, 클라이언트 패키지를 다운로드 받아서 클라이언트를 설치해도 됨
단 클라이언트 패키지에는 SQL Plus등이 없음
 

2. tnsnames.ora 파일복사

설치한 경로의 sample폴더에 있는 tnsnames.ora 파일을 복사하여 Admin 폴더 아래에 붙혀넣는다.
(예 c:\oracleexe/app/oracle/product/11.2.0/server/network/admin/sample/tnsnames.ora)
 

3. 연결정보 입력
– tnsnames.ora 파일에 연결정보를 입력 한다.

 

 *TNS명 =
(DESCRIPTION =
(ADDRESS = (PROTOCOL = TCP)(HOST = *IP주소)(PORT = *포트번호))
(CONNECT_DATA =
(SERVER = DEDICATED)
(SERVICE_NAME = *DB명)
)
)

 

 

예) TNS명 : ISM / DBName : MMC / IP:222.222.222.222 / 포트번호: 1521

ISM =
(DESCRIPTION =
(ADDRESS = (PROTOCOL = TCP)(HOST = 222.222.222.222)(PORT = 1521))
(CONNECT_DATA =
(SERVER = DEDICATED)
(SERVICE_NAME = MMC )
)
)
 

 

4. Inprocess허용

링크드 서버를 등록할때 다음과 같은 에러 메시지가 나올것이다.

연결된 서버 “ISM시스템’에 대한 OLE DB공급자 “OraOLEDB.Oracle”의 인스턴스를 만들 수 없습니다.
(Microsoft SQL Server, 오류:7302)

해결방법 : Inprocess 허용을 해줘야 한다.

 

연결된 서버 > OraOLEDB.Oracle 의 속성창을 연다.

 

 

 

 

5. MSSQL에 링크드서버 등록

 

 

예) 위 예제 내용을 등록

 

 

 

6. 5번까지 했는데 링크드서버가 등록되지 않는다면 재부팅을 해야한다.

오라클 드라이버가 재부팅을 해야 적용되는 경우도 있다. (방화벽도 체크 해보시길)

 

쿼리

1. 오라클 계정의 테이블 조회

SELECT * FROM openquery(MANI, ‘SELECT EMP_NMK FROM TA_EMP_MAS WHERE EMP_NMK = ”김진만” ‘)   <-작은따옴표가 앞에 두개붙음

OR

SELECT *  FROM MANI..DSERP.TA_EMP_MAS WHERE EMP_NMK = ‘김진만’

 

2.오라클 계정의 테이블 CRUD

INSERT INTO OPENQUERY(MANI, ‘SELECT * FROM MANI2 WHERE 1=0’) SELECT MANI, MANI, MANI FROM MANI

OR

INSERT INTO MANI..DSERP.MANI2 SELECT MANI, MANI, MANI FROM MANI

 

MS-SQL 날짜 형식

select convert(varchar, getdate(), 100)  –mon dd yyyy hh:miAM (or PM)

select convert(varchar, getdate(), 101)  –mm/dd/yyyy

select convert(varchar, getdate(), 102)  –yyyy.mm.dd

select convert(varchar, getdate(), 103)  –dd/mm/yyyy

select convert(varchar, getdate(), 104)  –dd.mm.yyyy

select convert(varchar, getdate(), 105)  –dd-mm-yyyy

select convert(varchar, getdate(), 106)  –dd mon yyyy

select convert(varchar, getdate(), 107)  –Mon dd, yyyy

select convert(varchar, getdate(), 108)  –hh:mm:ss

select convert(varchar, getdate(), 109)  –mon dd yyyy hh:mi:ss:mmmAM (or PM)

select convert(varchar, getdate(), 110)  –mm-dd-yyyy

select convert(varchar, getdate(), 111)  –yyyy/mm/dd

select convert(varchar, getdate(), 112)  –yyyymmdd

select convert(varchar, getdate(), 113)  –mon yyyy hh:mi:ss:mmm (24h)

select convert(varchar, getdate(), 114)  –hh:mi:ss:mmm (24h)

select convert(varchar, getdate(), 120)  –yyyy-mm-dd hh:mi:ss (24h)

select convert(varchar, getdate(), 121)  –yyyy-mm-dd hh:mi:ss.mmm (24h)

select convert(varchar, getdate(), 126)  –yyyy-mm-ddThh:mi:ss.mmm

select convert(varchar, getdate(), 130)  –dd mon yyyy hh:mi:ss:mmmAM

select convert(varchar, getdate(), 131)  –dd/mm/yyyy hh:mi:ss:mmmAM