(라라벨+MSSQL) 7405 오류 나는 쿼리 실행하기

엽토군·2020년 9월 14일
0

1분 코드 스니펫

목록 보기
2/9

tl;dr

이렇게 실행해야만 결과가 나오는 쿼리라면

USE foo;
SET ANSI_NULLS ON; SET ANSI_WARNINGS ON;
SELECT * FROM bar;

이렇게 쓴다.

// 이 코드블록을 코드#1 이라 부르겠음
// 실행 잘됨
$pdo = DB::connection('foo')->getPdo();
$pdo->query('SET ANSI_NULLS ON; SET ANSI_WARNINGS ON;');
$st = $pdo->prepare("SELECT * FROM bar");
$st->execute();
$result = $st->fetchAll(\PDO::FETCH_OBJ);

사연

2개 이상의 데이터베이스를 무식하게 DB link 걸어 JOIN 붙여 쓰고있는 우리 회사.. ㅎㅎ 덕분에 강제로 이것저것 배우는데 그중의 하나는 SQL 7405 오류이다. 자세히는 나도 모르니까 대충 이 정도만 알고 지나가자:

ANSI_NULLS 옵션이 ON인 경우, WHERE column_name = NULL을 사용하는 SELECT 문은 column_namenull 값이 있어도 0행을 반환합니다. WHERE column_name <> NULL을 사용하는 SELECT 문은 column_nameNull 이외의 값이 있어도 0행을 반환합니다.
(중략) 분산 쿼리를 실행할 때는 ANSI_NULLSON으로 설정해야 합니다.
출처

(ANSI_WARNINGS를) ON으로 설정한 경우 SUM, AVG, MAX, MIN, STDEV, STDEVP, VAR, VARP, COUNT 등의 집계 함수에 NULL 값이 있으면 경고 메시지가 생성됩니다. OFF로 설정한 경우에는 경고가 발생하지 않습니다.
(중략) 중요! 분산 쿼리를 실행할 때는 ANSI_WARNINGSON으로 설정해야 합니다.
출처

암튼.. 지금 나는 내 DBeaver에서 잘만 돌아가는 쿼리가 PHP 소스에서 돌아가질 않아서 돌아버리든지 돌아가실 지경이고..

// 이 코드블록을 코드#2 라고 부르겠음
// 실행않됌
$result = DB::connection('foo')->select(
    "SET ANSI_NULLS ON; SET ANSI_WARNINGS ON;
    SELECT * FROM bar"
);

// 아래 코드블록을 코드#3이라고 부르겠음
// 역시 실행 안됨
$db = DB::connection('foo');
$db->query('SET ANSI_NULLS ON; SET ANSI_WARNINGS ON;');
$result = $db->select("SELECT * FROM bar");

어떡하나.. 하다가 설마?? 싶어서 DB 조회의 원점으로 돌아가 PDO를 꺼내서 직접 쿼리를 때리는 방식을 시도해 보았다.

어... 된다.

왜 (안) 되느냐???

상기 코드#2 는 라라벨에 의해 대충 다음과 같이 동작한다.

  1. foo 커넥션이 맺어진다.
  2. SET 부터 FROM bar까지가 한꺼번에 커넥션을 통해 날아간다.
  3. MSSQL 서버 입장에서는, 주어진 SET을 실행해서, 이번 커넥션에 또 추가로 들어오는 질의가 있으면 그때 적용해 줘야지, 하고 기억해둔다.
  4. MSSQL 서버 입장에서는, 근데 이번 커넥션의 이번 질의문에 아직 처리 안한 SELECT 구간이 남아 있으므로, 그걸 실행하게 된다.
  5. 자연히 7405 오류가 터진다. (이번 커넥션의 다음 질의에서는 안 터지겠지만.)
  6. foo 커넥션은 보람찬 하루일 끝마치고서 자랑스럽게 닫힌다. (이제 아까의 SET은 헛수고 가 되었다.)
  7. 개발자는 복장이 터진다.

상기 코드#3 은 어느 훌륭한 개발자분의 질문 댓글 덕분에 테스트해본 것인데 커넥션이 2번 맺어진다는 점만 빼면 상술한 코드#2 동작과 거의 비슷하다. 제1행에서 생성된 $dbfoo 커넥션을 열어놓고 있는 것이 아니다. 단지, 이후 query()select()를 실행해야 할 때 foo 커넥션을 1회 사용한다는 '방침'을 갖고 있을 뿐인 것이다. 따라서 제2행과 제3행은 각각의 커넥션으로 실행되고, 혹시나 했지만 역시나 제3행에서 라라벨은 어김없이 QueryException: General error 7405를 반환한다.

요컨대 SET ANSI_NULLS ON 같은 SET 문들은, 그걸 적용시킬 다른 쿼리들과 같은 커넥션에서 미리 실행되어야 하는데, DBeaver, SQL Studio 같은 툴들은 이런 걸 알아서 잘 처리해주기 때문에 사용자는 미처 그걸 눈치채지도 못하고, Illuminate 쿼리 빌더는 그런 걸 허용하지 않는다는 것이다. 커넥션 설정값과 쿼리를 받아서 통째로 1번의 커넥션 1번의 질의로 단 한 번 실행하고 종료시키기 때문에, 코드#2와 코드#3의 노력은 숲으로 돌아간다.

반면 코드#1 은 순수 PHP PDO에 의해 다음과 같이 동작한다.

  1. foo 커넥션이 맺어진다.
  2. 해당 커넥션에 대해 SET ANSI_NULLS ON; SET ANSI_WARNINGS ON; 구문이 질의되어 잘 처리된다.
  3. 해당 커넥션에 대해 SELECT * FROM bar 구문이 질의되어 잘 처리된다.
  4. 십년 묵은 체증이 내린다.

교훈

  • 하다 하다 안 되면 원점으로 돌아가서 생각하는 것도 방법이다.
  • MSSQL은 대략 좇치 안타.
profile
4년차 PHP 개발자입니다.

2개의 댓글

comment-user-thumbnail
2020년 9월 23일

재밋게 잘 봤습니다~
혹시 SET 구문이 SELECT 문에 독립적으로(?) 선행되지 않는게 문제면 PDO까지 안가고 쿼리 빌더로 각각 실행하는 방식으로는 해결이 안될까요? (직접 안해보고 질문 남기는 게으름 사과드립니다 :) )

1개의 답글