'2018/09'에 해당되는 글 4건

Programming/Microsoft SQL Server

일반적으로 텍스트 검색시 사용하는 쿼리로 Like검색을 들 수 있습니다.

 

Select *
From [Sales].[OrderLines]
Where [Description] Like '%car%';

 

그런데 위와 같은 방식은 테이블스캔작업이 발생되기에 행(Row)이 많으면 원하는 내용을 찾는데 오랜 시간이 소모될 것입니다. 물론 인덱스를 타게 할 수도 있으나 car%와 같은 방법으로 해야만 해서 내용 중간에 'car'가 있는 데이터는 찾을 수 없고 더군다나 무작위의 문자열을 다루는 열(Column)에는 특별한 경우가 아닌이상 인덱스를 걸어놓지 않습니다.

 

결국 신문기사 검색과 같은 텍스트검색을 위해서는 Like이외에 다른 방법이 필요하게 됐고 보통의 인덱스처럼 열(Column)에 인덱스를 적용하는 것이 아니라 특정 단어나 문장을 대상으로 인덱스를 적용하는 '전체 텍스트 검색'을 사용자에게 제공하게 되었습니다.

 

전체 텍스트 검색기능을 이용하려면 일단 서버에 SQL Full-text Filter Daemon 서비스가 실행중이어야 하는데

 

 

이 서비스는 SQL Server를 설치할때 'Full-Text and Semantic Extractions for Search'항목을 옵션으로 지정해 설치를 해야 합니다.

 

 

이것으로 보아 전체 텍스트 검색을 위한 엔진이 별도로 존재한다는 것을 알 수 있을 것입니다. 만약 사용자가 전체 텍스트 검색을 위한 쿼리를 실행하면 전체 텍스트 엔진은 전체 텍스트 인덱스를 검색해 해당 열을 찾아 그 결과를 SQL Server 서비스에 알려주고 서비스는 그 열의 내용을 가져와 사용자에게 반환하는 방식으로 전체 텍스트검색을 수행하게 됩니다.

 

그럼 실제 검색할 문자열이 존재하는 테이블을 대상으로 전체 텍스트 검색을 사용해 보겠습니다. 아래 예제의 대상은 WideWorldLmporters라는 Sample 데이터베이스를 사용한 것입니다.

 

전체 텍스트 검색사용을 위해서는 우선 전체 텍스트 카탈로그를 생성해야 합니다. 이는 전체 텍스트 인덱스를 저장하고 있는 저장소 역활을 수행합니다.

 

Create Fulltext Catalog SalesOrderLinesCatalog As Default;

 

카타로그는 Create Fulltext Catalog 구문을 사용하며 SalesOrderLinesCatalog라는 이름으로 카타로그를 생성하였습니다. 참고로 sys.fulltext_catalogs 테이블을 살펴보면 생성한 카타로그의 정보를 확인해 볼 수 있습니다.

 

Select *
From sys.fulltext_catalogs;

 

만약 생성한 카탈로그의 is_default값이 1이면 해당 카탈로그가 기본 카탈로그임을 의미하는 것입니다.

 

카탈로그 삭제는 Drop FullText를 사용합니다.

 

Drop Fulltext Catalog SalesOrderLinesCatalog

 

카탈로그를 생성하고 나면 이제 해당 카탈로그에 전체 텍스트검색 인덱스를 생성해야 합니다.

 

Create FullText Index On Sales.OrderLines([Description])
Key Index PK_Sales_OrderLines
On SalesOrderLinesCatalog
With Change_Tracking Auto;

 

위 예제에서 인덱스는 Sales.OrderLines 테이블의 Description 열에 생성하였으며 전체 텍스트 검색엔진이 검색후 결과를 PK_Sales_OrderLines 인덱스키로 SQL Server 서비스에 반환하도록 하였습니다. 즉, 전체 텍스트 검색엔진은 검색 후에 그 내용자체가 아닌 해당 내용이 있는 열을 가리키고 있는 키값을 반환하기 때문에 전체 텍스트 인덱스를 생성하려고 하는 테이블은 반드시 값이 중복될 수 없는 Primary Key나 Unique Key를 가지고 있어야 합니다. 참고로 Description 열은 nVarChar형인데 char, nChar, text, nText, xml등 문자열이 들어가는 거의 모든 열에 지정이 가능합니다. 또한 일반 인덱스와 다르게 전체 텍스트 인덱스는 테이블당 하나만 생성이 가능합니다.

 

혹시 인덱스 생성시 Key Index로 어떤걸 지정해야 할지 모르는 경우라면

 

Select [name]
From sysindexes
Where OBJECT_ID('Sales.OrderLines') = id;

 

처럼 특정 테이블의 개체를 확인하고 PK로 시작하는 이름을 확인하시기 바랍니다.

 

그리고 해당 인덱스는 On구문을 통하여 이전에 만들어둔 전체 텍스트 카탈로그인 SalesOrderLinesCatalog에 생성되도록 합니다. 다만 SalesOrderLinesCatalog는 현재 기본 카탈로그이므로 생략이 가능합니다.

 

그리고 마지막으로 만약 실제 테이블의 값(Sales.OrderLines의 Description열)이 변경되는 경우 이를 인덱스에도 자동으로 반영되도록 Change_Tracing 옵션을 Auto로 지정하였습니다. 성능이 문제가 되는 경우라면 Auto대신 Munual을 지정해 변경데이터를 수동으로 적용시켜 주는 방법도 있으며 Off를 통해 아예 변경내용을 업데이트하지 않도록 처리할 수도 있습니다.

 

인덱스를 생성 후 sys.fulltext_indexes 테이블을 통해 인덱스의 상황을 확인할 수 있습니다.

 

Select *
From sys.fulltext_indexes;

 

만약 결과에서 is_enabled가 1이라면 현재 해당 인덱스는 활성화중이라는 것을 의미합니다.

 

인덱스를 생성하고 나면 어떤 단어들이 인덱스로 생성되었는가를 확인할 수 있습니다.

 

Select *
From sys.dm_fts_index_keywords(DB_ID(), OBJECT_ID('Sales.OrderLines'));

 

위 결과에서 display_term은 생성된 단어들 그리고 column_id는 해당 단어가 들어간 열의 순서값(2라면 2번째열), 마지막으로 document_count는 해당 단어가 몇번이나 들어가 있는지를 나타냅니다.

 

단어를 나열해 보면 가령 필요없는 단어들까지 인덱스로 만들어놓은걸 확인할 수 있습니다. 예들 들어 'ta' 이라는 단어까지 인덱스로 만들필요가 없다면 이들 단어는 중지목록에 포함시켜 인덱스에서 제외할 수 있습니다.

 

특정 단어를 중지목록에 넣으려면 우선 중지목록자체를 생성해야 합니다. SSMS(SQL Server Management Studio)에서 Object Explorer창을 통해 원하는 DB의 Storage->Full Text Stoplists 항목을 찾아가 마우스 오른쪽 버튼을 눌러 'New Full-Text Stoplist'메뉴를 선택합니다.

 

 

Full-text stoplist name에 적절한 목록이름을 입력하고 아래 옵션중 원하는 방식으로 목록옵션을 선택합니다. 옵션중에서 Create an empty stoplist는 말 그대로 빈목록을 만들고 거기에서 시작하겠다는 의미이며 Create from the system stoplist는 이미 시스템상에 만들어져 있는 목록에서 추가하는 형태로 새로운 목록을 만들겠다는 것을 의미합니다. 마지막으로 Create from an existing full-text stoplist는 다른 곳에서 만들어진 목록을 가져와 추가하는 형태로 목록을 만드는 경우 선택하는 옵션인데 이들은 인덱스를 생성하고자 하는 목적에 따라 적절이 선택하시면 됩니다. 예제에서는 이름을 mystoplist하고 Create an empty stoplist를 선택하도록 하겠습니다.

 

 

중지목록을 만들고 나면 위에서 처럼 목록이 생성되는 것을 확인할 수 있습니다. 아이콘 모양을 언뜻보면 뭔가 중지된 느낌이 들지만 중지목록이라는 의미로 모양자체가 원래 그런것이니 신경쓰지 않아도 됩니다.

 

이 작업은 아래 쿼리를 통해서도 동일하게 처리할 수 있습니다.

 

Create FullText Stoplist mystoplist;

 

혹은 시스템 목록에서 만들고자 하는 경우

 

Create FullText Stoplist mystoplist From System StopList;

 

이제 목록에 인덱스에서 예외로 하고자 하는 단어를 추가해 봅시다. 목록에서 마우스 오른쪽 버튼을 눌러 Properties(속성)을 선택합니다.

 

 

위 화면에서 예외로 하고자 하는 단어를 입력하고 OK(확인)버튼을 누르면 해당 단어가 추가됩니다. 이 작업은 아래 쿼리를 통해서도 동일하게 처리할 수 있습니다.

 

Alter FullText StopList mystoplist Add 'ta' Language 'English';

 

이제 기존의 만들어둔 인덱스를 삭제하고

 

Drop FullText Index On Sales.OrderLines;

 

새롭게 인덱스를 구성해 보겠습니다. 쿼리는 이전과 완전히 동일하지만 중지목록에 있는 단어들은 제외하도록 지정해야 합니다.

 

Create FullText Index On Sales.OrderLines([Description])
Key Index PK_Sales_OrderLines
On SalesOrderLinesCatalog
With Change_Tracking Auto, StopList = mystoplist;

 

위에서 처럼 인덱스를 생성하고 나면 중지목록에 있는 단어는 더이상 포함되어 있지 않음을 확인할 수 있습니다.

 

인덱스를 생성하였으면 이제 테이블에서 전체텍스트 검색을 통해 데이터를 찾아보로고 하겠습니다.

 

Select *
From Sales.OrderLines
Where CONTAINS([Description], 'to');

 

Description 컬럼에 to라는 단어가 들어간 모든 데이터를 찾도록 합니다.

 

실행계획에서 FullTextMatch를 확인해 볼 수 있습니다.

 

Select *
From Sales.OrderLines
Where Contains([Description], 'to*');

 

to로 시작하는 단어를 찾습니다.

 

Select *
From Sales.OrderLines
Where CONTAINS([Description], 'to And become');

 

to 와 become 두개가 동시에 들어간 데이터를 찾습니다.

 

Select *
From Sales.OrderLines
Where Contains([Description], 'Near((to, become), 2)');

 

to와 become라는 단어가 서로 근접한것을 찾습니다. 이때 이 두단어 사이에 최대 2개까지의 단어만 포함하도록 합니다.

 

Select *
From Sales.OrderLines
Where Contains([Description], 'IsAbout (to weight (1.0), become weight (0.5))');

 

가중치를 부여한 검색을 수행합니다. 0~1까지 지정이 가능하며 숫자가 높을수록 가중치를 많이 부여합니다.

 

Select *
From Sales.OrderLines
Where CONTAINS([Description], 'to Or box');

 

to 또는 box단어가 존재하는 데이터를 찾습니다.

 

Select *
From Sales.OrderLines
Where FreeText([Description], 'press F1 to');

 

FreeText는 빗스한 내용을 찾아주는 역활을 수행합니다. 예제에서 press F1 to로 지정했는데 이와 완전히 동일하지 않더라도 비슷한 내용이 있으면 그 결과를 반환할 것입니다.

 

Select *
From ContainsTable(Sales.OrderLines, [Description], 'to');

 

ContainsTable은 지정한 열을 포함하는 키값별로 지정한 단어를 가장 많이 그리고 정확하게 포함하고 있는 가중치값을 표현합니다. 비슷한 기능으로 FreeTextTable이 있으며 개념은 FreeText와 같습니다.

'Programming > Microsoft SQL Server' 카테고리의 다른 글

파일그룹에 인덱스생성 후 삭제관련 문제  (0) 2019.05.17
인덱스(Index) - 1  (0) 2019.03.26
[SQL] 전체 텍스트 검색  (0) 2018.09.27
트랜잭션 (Transaction)  (0) 2018.09.12
분산 트랜잭션 설정  (0) 2018.07.11
[SQL] 기본언어확인및 변경  (0) 2018.01.30
0 0
Programming/.NET

UDP는 간단하게 서버와 클라이언트가 데이터를 송수신할 수 있지만 일단 데이터를 보내면 상대측이 잘 받았는지 확인할 도리가 없고 심지어 여러건의 데이터를 나누어 보내면 순서대로 받을 수 있을지 조차 장담할 수 없습니다. 따라서 신뢰성이 너무 약해 잘 쓰지 않는 통신방식입니다.

 

지금은 간단하게 C#으로 UDP통신이 가능한 서버와 클라이언트 프로그램을 구현해 보고자 합니다. 통신 방식은 클라이언트가 서버에 특정 데이터를 보내면 서버는 이 데이터를 받아 앞에 'server : ' 라는 문자열을 추가한뒤 다시 클라이언트에 보내는 방식입니다.

 

static void Main(string[] args)
{
    Thread t = new Thread(myMethod);
    t.IsBackground = true;

t.Start();

Console.Read();

}

 

static void myMethod(object o)
{
    using (Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp)) {
        IPEndPoint iEp = new IPEndPoint(IPAddress.Any, 2020);
        socket.Bind(iEp);

        byte[] bRec = new byte[1024];
        EndPoint ep = new IPEndPoint(IPAddress.None, 0);

        while (true) {
            int i = socket.ReceiveFrom(bRec, ref ep);
            string s = Encoding.UTF8.GetString(bRec, 0, i);

            byte[] bSnd = Encoding.UTF8.GetBytes("server : " + s);
            socket.SendTo(bSnd, ep);
        }
    }
}

 

우선 서버측부터 구현해 보겠습니다. 제일 먼저 필요한건 소켓입니다. Socket클래스를 통해 socket을 생성하는데 ProtocolType을 Udp로 지정해 UDP로 통신할 것임을 알려줍니다.

 

서버는 경우에 따라 다수의 IP를 가질 수 있습니다. 이 가운데 지금 통신을 위해 어떤 IP와 포트로 수신대기를 해야하는지를 정해야 하며 그 접점을 IPEndPoint클래스로 설정합니다. IPAddress.Any는 현재 시스템에 마련된 모든 IP를 대상으로 하겠다는 것을 의미하며 2020은 포트번호에 해당합니다. 만약 특정 IP를 명시적으로 지정하고자 한다면 다음과 같이 코드를 수정할 수 있습니다.

 

IPAddress ip = IPAddress.Parse("192.168.20.126");
IPEndPoint iEp = new IPEndPoint(ip, 2020);

 

접점이 마련되었으면 socket의 Bind 메서드를 통해 해당 접점정보를 전달하여 설정된 네트워크정보로 데이터를 수신대기하도록 합니다.

 

bRec는 클라이언트로 부터 데이터가 수신되면 받을 버퍼이며 ep는 클라이언트측 접점정보를 가진 개체입니다. 그런데 어떤 클라이언트로 부터 데이터가 수신될지 모르니 IPEndPoint로 인스턴스를 생성할때는 IP를 지정하지 않고 또한 포트도 0으로 설정할 수 밖에 없습니다. 특정 내용으로 설정한다 하더라도 이개체는 socket의 ReceiveFrom메서드에서 데이터가 수신될때 데이터를 보낸 접점정보를 담아 반환하게될 것이므로 초기설정정보는 그렇게 의미가 없습니다.

 

ReceiveFrom메서드에서 데이터가 수신되면 bRec버퍼에 수신된 내용을 담은 후 UTF8인코딩을 통해 문자열로 변환한 후 앞단에 'sever : '라는 내용을 붙여 SendTo메서드를 통해 받은 데이터를 다시 보냅니다. 이때 어느 클라이언트로 보낼지는 ep개체를 통해 알 수 있습니다. ep는 초기화 시에는 의미없는 내용이었지만 ReceiveForm에서 정보가 담겨지게 되므로 클라이언트의 접점정보를 알 수 있게 됩니다.

 

또한 SendTo로 데이터를 보낼때는 보낼 수 있는 데이터의 크기도 생각을 해봐야 합니다. 이론상으로는 최대 65535byte인데 이는 네트워크통신에서 패킷에 붙는 별도의 정보로 인해 크기가 줄어들 수 있을 뿐만 아니라 각종 네트워크장비에서 통과할 수 있는 데이터의 양을 제한하는 경우도 있으므로 주의해야 합니다.

 

static void Main(string[] args)
{
    Thread t = new Thread(myMethod);
    t.IsBackground = true;


    t.Start();

    Console.Read();
}

static void myMethod(object o)
{
    Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);

    IPAddress ip = IPAddress.Parse("192.168.20.126");
    EndPoint sEP = new IPEndPoint(ip, 2020);

    EndPoint cEP = new IPEndPoint(IPAddress.None, 0);

    byte[] send = Encoding.UTF8.GetBytes("hi server!!");
    socket.SendTo(send, sEP);

    byte[] rec = new byte[1024];
    int i = socket.ReceiveFrom(rec, ref cEP);
    string s = Encoding.UTF8.GetString(rec, 0, i);

    Console.WriteLine(s);

    socket.Close();
}

 

위 예제는 클라이언트측 예제로서 소켓생성방법은 서버와 같습니다.

 

클라이언트입장에서 데이터를 보낼때 어느 서버로 보낼지를 알아야 하므로 그에 대한 설정을 진행합니다. 서버의 IP는 192.168.20.126(여러분의 환경에 따라 IP는 다를 것입니다!)이며 포트 2020으로 접속할 것입니다. 또한 서버에 데이터를 보낸 후 다시 서버에서 메세지를 수신받아야 하는데 이때 필요한 접점정보도 필요합니다. 다만 이미 서버정보에 대해서는 알고 있으나 어차피 ReceiveFrom에서 데이터를 보낸 대상의 접점정보가 파악될 것이므로 굳이 IP와 포트정보를 따로 지정하지 않습니다.

 

데이터를 보낼때는 보낼 내용을 UTF8로 인코딩하여 바이트배열에 담아 SendTo메서드에 전달해 데이터를 송신합니다. 이때 서버측 접점정보도 매개변수를 통해 같이 전달합니다.

 

서버에 데이터를 송신하면 그 즉시 서버에서 특정 메세지가 회신될 것이므로 그 회신데이터를 담을 버퍼를 마련해 두고 ReceiveFrom 메서드에 전달합니다. 그리고 버퍼에 담긴 내용을 다시 UTF8로 디코딩하여 사용자에게 보내줍니다.

 

UDP통신은 앞서 말한대로 데이터를 송수신할때의 신뢰성이 떨어지지만 데이터를 보내면 데이터를 받는 쪽에서는 보낸만큼 한꺼번에 데이터를 수신받을 수 있는등 통신방식은 대단히 간결하다는 장점이 있습니다.

'Programming > .NET' 카테고리의 다른 글

[C#] 네트워크 - TCP  (0) 2018.10.17
[ASP.NET MVC] 영역(Area)  (0) 2018.10.10
[C#] 네트워크 - UDP  (0) 2018.09.20
[C#] async / await  (0) 2018.09.04
[C#] Path  (0) 2018.08.23
[ASP.NET MVC] 뷰 (View)  (0) 2018.08.14
udp
0 0
Programming/Microsoft SQL Server

MS SQL Server에서 데이터베이스(DB)를 생성하면 실제 데이터베이스파일이 만들어 지는데 일부로 변경한 경우가 아니라면 아래와 같은 위치에 파일이 생성될것입니다.

 

C:\Program Files\Microsoft SQL Server\MSSQL12.MSSQLSERVER\MSSQL\DATA

 

실제 위와 같은 위치로 들어가면 확장자가 mdf인 파일과 ldf인 파일을 확인해 볼 수 있는데, 예를 들어 abc라는 데이터베이스를 생성했다면 abc.mdf와 abc_log.ldf파일이 존재함을 알 수 있습니다. 여기서 mdf는 실제 데이터가 들어가 있는 파일이며 ldf가 트랜잭션로그파일입니다. 이들 파일은 일반적으로는 한개만 존재할 경우가 많지만 필요에 따라 여러개로 나뉘어질 수 있는데 만약 데이터파일이 나뉘어진거라면 *.ndf형식으로 데이터파일이 생성되어 있을 것입니다.

 

이번에 주목해야할 것은 ldf즉 트랜잭션로그파일입니다. 이 파일은 Update나 Insert, Delete등의 데이터 변경작업을 기록하는 파일입니다. 만약 사용자가 Select와 같은 데이터조회 쿼리를 서버에 전달하면 서버는 mdf에서 원하는 데이터를 찾아 사용자에게 돌려주게 됩니다. 당연히 이 과정에서는 따로 트랜잭션로그가 남지 않습니다.(참고로 로그를 남기는 행위는 어떻게든 성능면에서는 불이익입니다. 트랜잭션로그도 마찬가지인데 로그를 남기지 않도록 하면 데이터의 무결성을 보증하기는 힘들어지지만 성능은 훨씬 좋아질것입니다.)

 

아래 쿼리는 데이터 변경 쿼리로 트랜잭션로그를 남기게 될 것인데 여기서 로그는 변경구문 그 자체를 기록하는것을 의미합니다.

 

Update [Person].[Person]
Set Title = NULL,
    FirstName = 'Ovidiu',
    MiddleName = 'V',
    LastName = 'Cracium'
Where BusinessEntityID = 11;

Update [Person].[Person]
Set Title = 'Ms.',
    FirstName = 'Janice',
    MiddleName = 'M',
    LastName = 'Galvin'
Where BusinessEntityID = 13;

 

그런데 위 구문은 Update가 2개이므로 2번의 트랜잭션을 발생시키게 됩니다. MS SQL은 기본적으로 자동 커밋이기에 사용자가 따로 트랜잭션시작과 종료시점을 명시하지 않는다면 변경 구문 하나하나마다 별도의 트랜잭션을 자동적으로 추가하게 됩니다.

 

Begin Tran

Update [Person].[Person]
Set Title = NULL,
     FirstName = 'Ovidiu',
     MiddleName = 'V',
     LastName = 'Cracium'
Where BusinessEntityID = 11;
Commit Tran;

Begin Tran
Update [Person].[Person]
Set Title = 'Ms.',
     FirstName = 'Janice',
     MiddleName = 'M',
     LastName = 'Galvin'
Where BusinessEntityID = 13;
Commit Tran;

 

그래서 만약 여러개의 변경구문을 묶어 단 한번만 트랜잭션을 유발시키려면 Begin Tran와 Commit Tran사이에 필요한 내용을 모두 명시해야 합니다.

 

Begin Tran

Update [Person].[Person]
Set Title = NULL,
     FirstName = 'Ovidiu',
     MiddleName = 'V',
     LastName = 'Cracium'
Where BusinessEntityID = 11;

Update [Person].[Person]
Set Title = 'Ms.',
     FirstName = 'Janice',
     MiddleName = 'M',
     LastName = 'Galvin'
Where BusinessEntityID = 13;
Commit Tran;

 

여기서 Begin Tran과 Commit Tran에서의 Tran은 Transaction의 줄임표현이며 원한다면 Begin Transaction처럼 풀어써주는것도 가능합니다.

 

트랜잭션은 위에서 처럼 Begin Tran과 Commit Tran을 명시적으로 해줘야 하는 경우도 있고 오라클처럼 Begin Tran은 자동으로 붙여주되 Commit Tran이나 Rollback Tran은 명시적으로 붙여줘야 하는 방식을 취할 수도 있습니다. 이러한 방식을 암시적 트랜잭션이라고 하는데 MS-SQL에서 이 방식을 사용하려면 다음과 같이 설정을 부여해야 합니다.

 

Set Implicit_Transactions On;

 

다만 일반적으로 트랜잭션은 중복으로 실행될 수 있지만 암시적 트랜잭션은 Commit이나 Rollback을 만나기 전까지는 중복실행이 불가능합니다.

 

위에서 처럼 MS-SQL서버에 트랜잭션이 발생하게 되면 그러니까 Begin Tran이 시작되면 ldf에 Begin Tran구문을 기록하고 변경 쿼리의 내용대로 데이터를 임시로 변경하게 됩니다. 임시로 변경한다는 것은 실제 데이터를 변경하는 것이 아니라 변경이 필요한 행 데이터(페이지)를 메모리캐시에 올려놓고 올려놓은 데이터를 대상으로 변경하는 것을 의미합니다. 그리고 나서 변경 쿼리에 일련번호를 부여하여 ldf파일에 로그로 기록하게 됩니다. 이 과정을 Commit이나 Rollback이 나올때까지 변경쿼리마다 하나씩 반복합니다.

 

만약 이 과정중 Commit Tran을 만나게 되면 ldf에 로그를 기록하고 캐시에 기록된 내용대로 실제 mdf데이터에 해당 내용을 적용(Commit)하게 됩니다. 그리고 나서 ldf의 Commit Tran부분에 체크포인트를 설정합니다. 체크포인트는 지금까지의 내용이 모두 실제 mdf에 적용되었음을 표시하는 것인데 만약 CheckPoint 문을 임의로 실행하면 캐시의 내용을 실제 데이터에 적용하고 그 시점에 체크포인트를 설정할 수 있도록 합니다.

 

Begin Tran부터 변경쿼리를 통해 캐시내용을 바꾸는 과정중 어떠한 이유로 인해서 서버나 혹은 서비스가 종료되는 상황이 발생하면 메모리의 내용이 모두 사라지게 될 것입니다. 변경내용이 모두 메모리에 존재하고 있었는데 이 캐시데이터를 모두 잃어버리게 되었고 로그상 아직 Begin Tran중이었으니 처음 Begin Tran이나 혹은 이전 체크포인트까지의 처리를 모두 취소하여 데이터의 무결성을 유지하게 됩니다.

 

그런데 처리과정이 ldf에 Commit Tran을 기록하는 것까지 도달하였으나 캐시데이터가 아직 실제 데이터에 적용되기 전에 사고가 발생한 경우라면 Begin Tran시점(또는 이전 체크포인트 이후)부터 모든 쿼리에 대한 트랜잭션을 다시 재 수행하여 변경절차를 마무리하게 됩니다.

 

Commit Tran은 캐시데이터를 실제 데이터에 적용하지만 Rollback Tran은 변경이전으로 되돌리는 작업을 수행합니다. 그런데 트랜잭션이 여러개 중첩되어 사용되는 경우 Rollback Tran을 사용할때 오해할 수 있는 부분이 하나 있는데 Rollback Tran은 이전의 트랜잭션을 되돌리는 것이 아니라 지금까지의 모든 트랜잭션을 되돌린다는 것입니다.

 

Begin Tran
     Update [Person].[Person]
     Set Title = NULL,
      FirstName = 'Ovidiu1',
      MiddleName = 'V1',
      LastName = 'Cracium1'
     Where BusinessEntityID = 11;

Begin Tran
      Update [Person].[Person]

Set EmailPromotion = 1
Where BusinessEntityID = 11;

RollBack Tran;

 

Begin Tran 이후 다시 Begin Tran으로 트랜잭션을 두번 호출하고 마지막에 RollBack Tran으로 변경 데이터를 되돌립니다. 얼핏봐서는 이전의 Begin Tran만 되돌릴것 같지만 사실은 걸려있는 모든 트랜잭션을 취소합니다. 따라서 만약 특정 트랜잭션만을 명시해 취소하고자 한다면 특정 지점에서 트랜잭션을 Save하고 Save한 트랜잭션까지 취소하는 방법을 써야 합니다.

 

Begin Tran
     Update [Person].[Person]
     Set Title = NULL,
     FirstName = 'Ovidiu1',
     MiddleName = 'V1',
     LastName = 'Cracium1'
     Where BusinessEntityID = 11;
Save Tran my_tran;
Begin Tran
     Update [Person].[Person]
     Set EmailPromotion = 1
     Where BusinessEntityID = 11;
RollBack Tran my_tran;

 

중간에 Save Tran으로 트랜잭션 지점을 생성하고 RollBack Tran에서 복원지점을 명시하면 명시한 지점까지만 트랜잭션을 복원하게 됩니다. 따라서 위 쿼리의 경우 EmailPromotion을 Update하는 부분만 적용되고 그 위로는 트랜잭션이 취소되지 않습니다.

 

참고로 특정 테이블에 트랜잭션이 걸려져 있는 상태라면 해당 테이블에는 잠금(Lock)이 발생할 수 있습니다. 이 현상을 확인하려면 Begin Tran을 발생시키고 다른 세션(별도의 쿼리창)에서 해당 테이블의 Select를 시도해 보면 쉽게 확인 할 수 있습니다. 따라서 임의로 트랜잭션을 구현할때는 주의해야 하며 필요한 경우 아래 쿼리를 실행(Select가 필요한 세션안에서)하여 테이블잠김상태라도 데이터를 가져올 수 있도록 설정할 수 있습니다.

 

Alter Database [대상DB] Set Allow_Snapshot_Isolation On;
Set Transaction Isolation Level Snapshot;

 

특정 세션에서 트랜잭션을 발생시킨뒤 데이터 변경 쿼리를 실행하고 테이블을 조회해 보면 변경된 상태의 데이터가 출력됨을 확인할 수 있는데 이것은 실제 테이블의 데이터를 가져오는 것이 아니라 캐시상태의 데이터를 가져와 보여주고 있기 때문입니다.

 

Snapshot을 취소하려면 다음과 같이 수행합니다.

 

Alter Database AdventureWorks Set Allow_Snapshot_Isolation Off;

 

위에서 알아본대로 트랜잭션은 데이터의 무결성을 유지하기 위한 아주 좋은 장치입니다. 그런데 트랜잭션은 장애발생에 대한 조치용이지 모든걸(모든 장애를) 해결해 주는 것으로 오해해서는 안됩니다. 예를 들어 아래 쿼리를 보면

 

Begin Tran

Update [Person].[Person]
Set Title = NULL,
     FirstName = 'Ovidiu1',
     MiddleName = 'V1',
     LastName = 'Cracium1'
Where BusinessEntityID = 11;

Update [Person].[Person]
Set EmailPromotion = 'ABC'
Where BusinessEntityID = 11;
Commit Tran;

 

두번째 Update문에서 EmailPromotion은 Int형인데 여기에 'ABC'라는 문자열을 처리하도록 하고 있습니다. 트랜잭션에 대해 오해를 하고 있으면 여기에서 실패가 발생해 자동적으로 모든 Update문이 RollBack되리라 생각할 수 있겠지만 사실은 그렇지 않습니다. 이것은 엄밀히 말해 장애가 아닌 오류상황이기 때문입니다. 따라서 이런 경우에는 운영자가 명시적으로 Rollback이 되도록 처리를 해줘야 하며 이 때 가장 많이 쓰이는 방식은 Try Catch 구문을 활용하는 것입니다.

 

Begin Try

Begin Tran
Update [Person].[Person]
Set Title = NULL,
FirstName = 'Ovidiu1',
MiddleName = 'V1',
LastName = 'Cracium1'
Where BusinessEntityID = 11;

Update [Person].[Person]
Set EmailPromotion = 'ABC'
Where BusinessEntityID = 11;
Commit Tran;

End Try
Begin Catch
     Rollback Tran;
End Catch;

 

Begin Tran안에서 오류가 발생하면 Begin Catch에서 잡아 RollBack Tran을 실행할 것입니다.

'Programming > Microsoft SQL Server' 카테고리의 다른 글

인덱스(Index) - 1  (0) 2019.03.26
[SQL] 전체 텍스트 검색  (0) 2018.09.27
트랜잭션 (Transaction)  (0) 2018.09.12
분산 트랜잭션 설정  (0) 2018.07.11
[SQL] 기본언어확인및 변경  (0) 2018.01.30
[SQL] 스키마(Schema)  (0) 2018.01.23
0 0
Programming/.NET

특정 메서드의 비동기 호출을 시도할때는 대부분 작업을 진행하는 메서드부분과 작업을 완료하고 호출되는 메서드를 스레드로 분리하면서 이루어집니다. 하지만 async / await 를 사용하면 비동기호출시 완료처리를 위해 추가했던 별도의 메서드를 분리할 필요가 없어집니다.

 

using (FileStream fs = new FileStream(@"C:\\test.txt"FileMode.OpenFileAccess.ReadFileShare.Read)) {
    byte[] b = new byte[fs.Length];
    fs.Read(b, 0, b.Length);

    string s = Encoding.UTF8.GetString(b);
}

 

이 예제는 FileStream을 통해 파일 내용을 읽어들이는 동기식 방법입니다. 이 예제를 async / await 가 없을때 비동기식으로 바꾸면 다음과 같이 할 수 있습니다.

 

static byte[] b;
static void Main(string[] args)
{
    FileStream fs = new FileStream("test.txt"FileMode.OpenFileAccess.ReadFileShare.Read);
    b = new byte[fs.Length];
    fs.BeginRead(b, 0, b.LengthReadComplete, fs);


    Console.WriteLine("현재 스레드 완료");
    Console.Read();
}

static void ReadComplete(IAsyncResult ar)
{
    FileStream fileStream = (FileStream)ar.AsyncState;
    fileStream.EndRead(ar);

    string s = Encoding.UTF8.GetString(b);

    Console.WriteLine(s);
}

 

예제에서 BeginRead 메서드를 보시면 작업이 완료되었을때 ReadComplete메서드를 호출하는 것을 확인할 수 있습니다. 이것은 다시 async / await 를 사용해 아래와 같이 바꿔볼 수 있습니다.

 

static byte[] b;
static void Main(string[] args)
{
    fileRead();

    Console.WriteLine("현재 스레드 완료");
    Console.Read();
}

static async void fileRead()
{
    FileStream fs = new FileStream("test.txt"FileMode.OpenFileAccess.ReadFileShare.Read);
    b = new byte[fs.Length];

    await fs.ReadAsync(b, 0, b.Length);

    string s = Encoding.UTF8.GetString(b);
    Console.WriteLine(s);
}

 

보시는 것처럼 완료후의 메서드로 따로 분리되지 않았습니다. 대신 ReadAsync메서드로 바뀌었는데 이 메서드는 특별히 await구문을 사용한 비동기 호출을 지원하기 위한 것이며 앞에 await 가 따로 지정되었습니다. 이렇게 하면 await가 사용된 구문 아래에 모든 처리는 해당 메서드처리가 모두 완료되고 나서야 처리가 진행됩니다.

 

fileRead메서드에는 async가 구문이 포함되어 있는데 이는 해당 메서드안에서 await가 사용될 것임을 알려주고 있습니다. async가 필요한 이유는 본래 await는 C# 5.0이후에 추가된 것으로 그 버전이전에 변수이름같이 await가 사용된 경우를 대비하기 위해서 입니다.

 

또한 C#에는 async / await 를 사용을 좀더 간단히 하기위해서 기존의 복잡한 비동기호출이 필요한 메서드 이외에 async / await 를 사용할 수 있는 메서드를 추가했습니다.

 

static void Main(string[] args)
{
    getWebData();

    Console.WriteLine("현재 스레드 완료");
    Console.Read();
}

static async void getWebData()
{
    HttpClient hc = new HttpClient();
    string s = await hc.GetStringAsync("http://www.naver.com/");

    Console.WriteLine(s);
}

 

위 예제는 HttpClient를 사용하여 특정 웹페이지의 데이터를 가져오는 구문인데 async / await 로 비동기호출을 시도했고 이에 대응하기 위해 await가 지원되는 GetStringAsync메서드를 호출하였습니다.

 

static void Main(string[] args)
{
    doWork();
    Console.WriteLine("현재 스레드 완료");
    Console.Read();
}

static async void doWork()
{
    Task<int> t1 = work5();
    Task<int> t2 = work10();

    await Task.WhenAll(t1, t2);

    Console.WriteLine("비동기 작업 완료");

    Console.WriteLine("결과 : {0}, {1}", t1.Result, t2.Result);
}

static Task<intwork5()
{
    return Task.Factory.StartNew(() => {
        Thread.Sleep(5000);
        Console.WriteLine("5초 작업 완료");
        return 3;
    });
}

static Task<intwork10()
{
    return Task.Factory.StartNew(() => {
        Thread.Sleep(10000);
        Console.WriteLine("10초 작업 완료");
        return 10;
    });
}

 

async / await 를 활용하면 위와 같이 특정 작업을 비동기로 병렬수행하는 것이 가능합니다. 이때 WhenAll은 Task타입을 반환하도록 되어 있어서 await 를 붙일 수 있는데 비슷한 처리를 하는 WaitAll 메서드는 작업이 완료될때까지 현재 스레드에서 대기해야 합니다.

'Programming > .NET' 카테고리의 다른 글

[ASP.NET MVC] 영역(Area)  (0) 2018.10.10
[C#] 네트워크 - UDP  (0) 2018.09.20
[C#] async / await  (0) 2018.09.04
[C#] Path  (0) 2018.08.23
[ASP.NET MVC] 뷰 (View)  (0) 2018.08.14
[C#] using static  (0) 2018.07.27
0 0
1
블로그 이미지

클리엘