프로세서나 코어가 2개 이상 있고 대량 처리 작업중에서 실행계획에 ParallelSort가 있는 경우

순서가 보장되지 않는다.

MSSQL 20052008, 2008R2에서 확인했던 내용인데 2012는 아직 확인해 보지 못했다


아마도 CPU에 일을 분할 할 때 문제가 발생하는 것 같은데 버젼이 바뀌어도 계속 발견되고

운에 따라 발생 안할 경우도 있고 해서 상당히 신경 쓰이는 문제다


해결 방법은 간단하게 MAXDOP 1 을 옵션으로 줘서 병렬처리를 막으면 정상 적으로 결과가 나온다.

Posted by Mr.Jo :

MSSQL2005 부터 생긴 Cross Apply 조인


아직 많은 테스트가 필요하지만 적당한 Index가 있을 경우 처리할 데이터를 상당히 줄여줘 성능 향상에 도움이 된다

(다른 경우도 마찬가지지만 더욱 큰 폭으로)

특히 해당 유저의 최근 기록 같은 류의 정보를 추출할 때 기존 방식의 경우 이를 다시  Self Join 하거나 

문자열 조작으로 처리하곤 했는데 

Self Join 의 경우 테이블을 두번 읽게 되고 문자열 조작의 경우 그 구문이 상당히 길어질 뿐더러 복잡해서 쉽게 사용 하기 힘들다

더군다나 SQL의 경우 (C, C++, C#, Java, 등등의)프로그램 랭귀지들 보다 문자열 처리에 느린 편이다


하지만 Cross Apply의 경우 비교적 간단한 구문으로 빠르고 유용하게 사용 할 수 있다

이는 유지보수도 쉬워서 내가 아닌 다른 사람이 주석 없이도 내용 파악이 쉽고 그만큼 실수를 할 가능성도 줄어듬을 의미한다.


조심할 점은 Nested Loop Join으로 풀어 나가니 대용량 처리에는 부적합 하고 이런 저런 테스트를 꼭 해봐야 한다는 것!


--기존방식
SELECT a.UID, b.RegDate
FROM dbo.[User] AS a WITH(NOLOCK)
INNER JOIN
(
	SELECT a.UID, MAX(a.RegDate) AS RegDate
	FROM dbo.[UserLoginLog] AS a WITH(NOLOCK)
	GROUP BY a.UID
) AS b ON a.UID = b.UID


--Cross Apply
SELECT a.UID, b.RegDate
FROM dbo.[User] AS a WITH(NOLOCK)
CROSS APPLY
(
	SELECT TOP 1 b.UID, b.RegDate
	FROM dbo.[UserLoginLog] AS b WITH(NOLOCK)
	WHERE a.UID = b.UID
	ORDER BY RegDate DESC
) AS b
위의 실행 결과 비교는 다음과 같다


테스트용 데이터 이기 때문에 데이터 량이 적지만 실제 서버의 엄청난 데이터를 생각한다면 그 차이는 더더욱 클 것이라 생각된다.



ps1. Apply 구문에는 Cross Apply와 Outer Apply가 존재 하며
       이 차이는 Inner Join과 Left Outer Join혹은 Right Outer Join과 동일함

ps2. 테스트 데이터는 다음과 같이 만들었음
CREATE DATABASE TEST;
GO

USE TEST;
GO

CREATE TABLE dbo.[User]
(
	 UID int
	,Name varchar(10)

	,CONSTRAINT PK_User_UID PRIMARY KEY(UID)
);


CREATE TABLE dbo.[UserLoginLog]
(
	 UID int
	,RegDate datetime

	,CONSTRAINT FK_UserLoginLog_User_UID FOREIGN KEY(UID) REFERENCES dbo.[User](UID)
);

CREATE UNIQUE CLUSTERED INDEX UIDX_UserLoginLog_UID_RegDate ON dbo.[UserLoginLog](UID, RegDate);
GO

INSERT INTO dbo.[User](UID, Name) VALUES(1, '가나다');
INSERT INTO dbo.[User](UID, Name) VALUES(2, '라마바');
INSERT INTO dbo.[User](UID, Name) VALUES(3, '사아자');
INSERT INTO dbo.[User](UID, Name) VALUES(4, '차카타');
GO


INSERT INTO dbo.[UserLoginLog](UID, RegDate)
SELECT ROW_NUMBER() OVER(ORDER BY a.OBJECT_ID) % 4 + 1 AS UID, DATEADD(SS,  ROW_NUMBER() OVER(ORDER BY a.OBJECT_ID), GETDATE()) AS RegDate
FROM sys.objects AS a
	CROSS JOIN sys.objects AS b;
GO



Posted by Mr.Jo :

디비 서버를 관리하다 보면 여러 서버들에 일괄적으로 쿼리를 수행할 할 일이 생기곤 한다

SSMS의 등록된 서버로 그룹 서버 전체에 명령어를 날일 수도 있지만

각 서버들에 아주 약간씩 다른녀석들이 있다면 이야기가 달라진다. (예를들어 DB명이나 하드코딩 같은...)


동적 쿼리를 생성하여 실행하는 EXEC나 sq_executesql 프로시저를 이용 할 수 도 있겠지만

장문의 프로시저를 동시에 생성하는 일은 문자열 길이를 초과해 버리면 곤란한 일일 수 밖에 없다.


이럴때 개인적으로 쓰는 방법은 SQLCmd를 활용하는 방법이다.

보안 규정에 따라 삭제하는 경우도 있는데 애석하게도 그런 경우엔 다른 방법을 찾아보는것이 좋겠다.



SQLCmd의 옵션을 살펴보자


디비서버에 접속하는 방법은 각각의 환경마다 다르니 생략하고 디비가 설치된 서버에서 직접 실행하며

윈도우 인증을 사용한다면 -E옵션은 복잡한 Connection String을 작성할 일을 해결해 준다.


여기서 주목해야 할 옵션은 -q, -Q, -i, -o 옵션이다.


IT를 한다는 사람이라면 너무나 친숙한 "Hello SQL"를 찍어보자


입력한 쿼리의 결과가 콘솔창에 뿌려진다

(만약 컬럼이 많다면 putty같은 프로그램으로 tenet접속을 해서 실행시킨것이 아닌 이상 감당할 수 없는 데이터를 받게 될 것이다.)

그렇다면 PRINT문은 어떻게 나올까?


SSMS상에서는 다른 탭으로 결과와 메시지로 구분되던 데이터가 함께 나온다.

하지만 이래서는 다른 사람에게 결과를 전달 할 수 없다.

다음과 같이 파일로 만들어 보자.


명령어를 수행한 결과가 텍스트로 떨어졌다.

명령어가 매번 간단하다고 보장 할 수 없다.

파일로 입력해보자.


역시 잘 작동 된다.

이미 눈치 챈 사람이 많을 것이다 왜 자꾸 수행 결과를 보여주는지 파일로 떨구는지 파일로 입력 받았는지...

다음과 같이 결과를 다시 입력으로 할 수도 있다.



위와 같이 프로시저 조자도 생성이 가능하다.

문자열을 조작하여 만드는것은 쉬운 일이니 서버마다 서버에 있는 값으로 하드 코딩된 나쁜 프로시저를 생성해야만 하는 일이 생긴다면 사용해 볼 수 있는 무기가 우리에게 생겼다



MySQL이나 Oracle의 경우 에초에 콘솔 기반이라 저런 꼼수를 많이 썻었지만

MSSQL은 기본으로 너무 좋은 GUI 툴을 주기 때문에 저런 꼼수를 쓸 생각을 못했으나 필요에 의해 사용할 일이 있어

찾아서 정리해 본다.




ps. 혹시 각 서버에 접속해서 일일이 실행하진 않을거라 믿는다.

중앙에서 각 서버에 접속 할 수 있는 곳이 있다면 배치 파일로(혹은 파워쉘로) 쉽게 일괄 실행을 할 수 있을 것이다.


ps2. PRINT 문장을 언제 일일이 써주냐는 사람이 있다면 열 편집이 가능한 텍스트 에디터를 사용해 보길 바란다.

(찾아보면 유용한 무료 텍스트 에디터가 많다.)

몇만라인의 프로시저라 해도(그런게 있으려나;) 약간의 머리와 몇초 정도면 저런 구문으로 만들 수 있다.


Posted by Mr.Jo :

Identity 가 지정되어있는 컬럼에 임의의 값을 넣으려고 하면 에러가 나면서 넣어지지 않는다



임의의 값을 넣기 위해선 다음과 같이 명시한다면 입력이 가능하다

SET IDENTITY_INSERT [대상 테이블] ON;


입력을 위해서는 INSERT절에 해당 컬럼을 명시적으로 선언해야 한다

INSERT INTO [대상 테이블]([컬럼2], [컬럼2], [컬럼3], [컬럼4]...)
VALUES([값1], [값2], [값3], [값4]...);

INSERT INTO [대상 테이블]([컬럼2], [컬럼2], [컬럼3], [컬럼4]...)
SELECT [값1], [값2], [값3], [값4]... FROM ...;


입력 후에 다음과 같이 만들어 준다.

SET IDENTITY_INSERT [대상 테이블] OFF;

※주의사항 : 한번에 한 테이블에만 설정이 가능 하기 때문에 여러 테이블 대상일 경우 하나씩 설정 해야 함

Posted by Mr.Jo :

프로시저를 작성하다가 인자로 들어오는 스트링에 특수 문자 유무 혹은 형식에 맞추어 입력했는지 검사 할 필요가 있는데 MSSQL의 경우 제약이 조금 있기는 하지만 정규식을 사용 할 수가 있다



DECLARE @String varchar(1000);
SET @String = 'string';

IF @String Like '%[^-_.a-z0-9]%'
    PRINT '특수문자가 있음';
ELSE
    PRINT '특수문자가 없음';


Posted by Mr.Jo :

Identity 컬럼이 없던 테이블에 Identity컬럼을 추가 할 경우

임의의(의도치 않은) 순서로 번호가 매겨지게 된다

이를 특정 순서로 정렬하기 위해 여러 테스트를 하던 도중

SELECT * FROM [대상테이블]

매번 할 때와 같은 순서로 번호가 매겨지는걸 발견했다.

따라서 Identity 컬럼을 추가하기 전에 Order by를 하지 않고도 원하는 순서로 정렬되어 출력하도록 하면 되는데

이를 유도하기 위해 해당 테이블에 모든 제약조건이나 인덱스를 삭제하고 원하는 정렬 순서로 Clustered 인덱스를 만들게 되면

(Clustered Index는 테이블내 데이터를 미리 정렬해 둠으로)

Order by없이 정렬된 순서로 출력되기를 유도 할 수 있게 된다(어디까지나 유도이기 때문에 전, 후 확인이 필요하다)

작업 후에 지웠던 제약조건이나 인덱스를 만들고 확인만 하면 끝!


다음은 예제 쿼리

--정렬된 Identity 컬럼 추가
If Object_ID('dbo.Test') Is Not Null
    Drop Table dbo.Test
Go


Create Table dbo.Test
(
     Data1 Int
    ,Data2 Int
    ,Data3 Int
)
Go


Insert Into dbo.Test(Data1, Data2, Data3) Values(43, 123, 42)
Go

Insert Into dbo.Test(Data1, Data2, Data3) Values(23, 5, 52)
Go

Insert Into dbo.Test(Data1, Data2, Data3) Values(154, 12, 11)
Go

Insert Into dbo.Test(Data1, Data2, Data3) Values(23, 23, 23)
Go

Insert Into dbo.Test(Data1, Data2, Data3) Values(42, 12, 53)
Go

Insert Into dbo.Test(Data1, Data2, Data3) Values(533, 21, 521)
Go


--기존에 인덱스가 있다면 인덱스를 삭제하고 원하는 순서로 인덱스 생성하고 작업 완료후 인덱스를 정리
Create Clustered Index IDX_Test_Data1_Data2_Data3 On dbo.Test(Data1 Asc, Data2 Asc, Data3 Asc)
Go


Alter Table dbo.Test Add A Int Identity(1,1)
Go

Select *
From dbo.Test
   
Select *
From
(
    Select *,ROW_NUMBER() Over (Order By Data1 Asc, Data2 Asc, Data3 Asc) As B
    From dbo.Test
) As Test
Where A<>B
Order By A Asc,B Asc
Go


If Object_ID('dbo.Test') Is Not Null
    Drop Table dbo.Test
Go


Posted by Mr.Jo :