● 교착상태


한 작업에서 잠근 리소스를 다른 작업에서 잠그려고 하여 둘 이상의 태스크가 서로 영구적으로 차단하면 교착 상태가 발생합니다. 예를 들면 다음과 같습니다.


  • 트랜잭션 A가 1행에 대한 공유 잠금을 획득합니다.

  • 트랜잭션 B가 2행에 대한 공유 잠금을 획득합니다.

  • 트랜잭션 A가 2행에 대한 배타적 잠금을 요청하고 트랜잭션 B가 2행에 대해 소유하고 있는 공유 잠금을 종료 및 해제할 때까지 트랜잭션 A가 차단됩니다.

  • 트랜잭션 B가 1행에 대한 배타적 잠금을 요청하고 트랜잭션 A가 1행에 대해 소유하고 있는 공유 잠금을 종료 및 해제할 때까지 트랜잭션 B가 차단됩니다.


트랜잭션 B가 완료되어야 트랜잭션 A도 완료될 수 있지만 트랜잭션 B는 트랜잭션 A에 의해 차단된 상태입니다. 이러한 상태를 순환 종속 관계라고 합니다. 트랜잭션 A는 트랜잭션 B에 종속되고 트랜잭션 B는 트랜잭션 A에 종속된 형태로 순환됩니다.


교착 상태의 트랜잭션은 둘 다 외부 프로세스에서 교착 상태를 해제할 때까지 기다립니다. MicrosoftSQL Server 데이터베이스 엔진 교착 상태 모니터는 교착 상태에 있는 태스크가 있는지 주기적으로 검사합니다. 순환 종속 관계가 발견되면 모니터는 두 작업 중 처리하지 않을 태스크를 하나 선택하고 해당 트랜잭션을 오류와 함께 종료합니다. 이렇게 하여 다른 태스크가 해당 트랜잭션을 완료할 수 있습니다. 오류와 함께 종료된 트랜잭션의 응용 프로그램은 해당 트랜잭션을 다시 시도하며 이 트랜잭션은 대개 교착 상태의 다른 트랜잭션이 완료된 후에 끝납니다.


응용 프로그램에 특정 코딩 규칙을 사용하여 응용 프로그램에서 교착 상태를 일으킬 가능성을 줄일 수 있습니다. 자세한 내용은 교착 상태 최소화를 참조하십시오.


교착 상태는 종종 일반적인 차단과 혼동됩니다. 트랜잭션이 다른 트랜잭션에서 잠근 리소스에 대한 잠금을 요청하면 잠금이 해제될 때까지 잠금을 요청한 트랜잭션이 기다립니다. 기본적으로 LOCK_TIMEOUT이 설정되지 않은 한 SQL Server 트랜잭션 시간은 제한되지 않습니다. 잠금을 요청하는 트랜잭션은 잠금을 소유하는 트랜잭션을 차단하기 위한 작업을 수행하지 않으므로 교착 상태에 빠지지 않고 차단됩니다. 결국 잠금을 소유하는 트랜잭션이 완료되고 잠금을 해제하면 잠금을 요청하는 트랜잭션에 잠금이 허가되고 트랜잭션이 진행됩니다.


교착 상태는 deadly embrace(치명적인 포옹)라고도 합니다.


교착 상태는 관계형 데이터베이스 관리 시스템뿐만 아니라 다중 스레드를 사용하는 어느 시스템에서나 발생할 수 있으며 데이터베이스 개체에 대한 잠금 이외의 리소스에 대해 발생할 수 있습니다. 예를 들어 다중 스레드 운영 체제의 스레드는 메모리 블록과 같은 하나 이상의 리소스를 획득할 수 있습니다. 획득하려는 리소스를 현재 다른 스레드가 소유하고 있으면 대상 리소스가 해제될 때까지 첫 번째 스레드가 기다려야 할 수 있습니다. 이렇게 대기 중인 스레드는 해당 리소스에 대해 리소스를 소유하는 스레드에 종속됩니다. 데이터베이스 엔진의 인스턴스에서 세션은 메모리나 스레드 등의 데이터베이스가 아닌 리소스를 획득할 때 교착 상태에 빠질 수 있습니다.


트랜잭션 교착 상태를 보여 주는 다이어그램


이 그림에서 트랜잭션 T1은 Part 테이블 잠금 리소스에 대해 트랜잭션 T2에 종속됩니다. 마찬가지로 스레드 T2는 Supplier 테이블 잠금 리소스에 대해 트랜잭션 T1에 종속됩니다. 이러한 종속 관계는 순환적이므로 스레드 T1과 T2 간에 교착 상태가 발생합니다.


테이블이 분할되고 ALTER TABLE의 LOCK_ESCALATION 설정이 AUTO로 설정된 경우에도 교착 상태가 발생할 수 있습니다. LOCK_ESCALATION이 AUTO로 설정되면 데이터베이스 엔진에서 TABLE 수준이 아니라 HoBT 수준에서 테이블 파티션을 잠그도록 허용하여 동시성이 증가합니다. 그러나 개별 트랜잭션이 테이블에 파티션 잠금을 보유하고 다른 트랜잭션 파티션에서 잠금을 원하면 교착 상태가 발생합니다. 이런 유형의 교착 상태는 LOCK_ESCALATION을 TABLE로 설정하면 방지할 수 있습니다. 하지만 이 설정으로 인해 테이블 잠금을 기다리도록 파티션에 대규모 업데이트가 강제 적용되어 동시성이 감소됩니다.


----------------------------------------------------------------------------------------------------------------------------------------


위 내용은 MSDN에서 참조한 내용이다.


출처 : http://msdn.microsoft.com/ko-kr/library/ms177433(v=sql.100).aspx



하나의 테이블에 여러 트랜잭선에서 동시다발적으로 UPDATE/DELETE 등을 실행할 경우 교착상태(DEADLOCK)이 발생한다.
보통 이런경우 아래와 같은 에러메세지가 출력한다.


SqlException:System.Data.SqlClient.SqlException (0x80131904): 트랜잭션(프로세스 ID 95)이 잠금 리소스에서 다른 프로세스와의 교착 상태가 발생하여 실행이 중지되었습니다. 트랜잭션을 다시 실행하십시오.


이런 증상을 최소화 하기 위해서는 몇가지 방법이 있다.


1. 인덱스를 설정한다. 인덱스가 없는 경우 DEADLOCK 이 발생할 확률이 넓어진다.
2. 트랜젝션을 가급적 짧고 단순하게 만든다.
3. Transaction Isolation Level 을 "Read UnCommitted"로 설정한다.
4.
LOCK_ESCALATION 을 "Disable", ALLOW_PAGE_LOCKS 를 "OFF"로 설정한다.


위의 4가지 항목들을 모두 설정해야 오류가 발생하지 않는것 같다.



▷ Isolation Level 설정 방법


SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED


쿼리문 상단에 위 설정구분을 넣어두면 된다.



▷ LOCK_ESCALATION 설정 방법


ALTER TABLE dbo.tb_ExTable SET (LOCK_ESCALATION = DISABLE )


LOCK_ESCALATION 은 AUTO, TABLE, DISABLE 로 설정할 수 있다.(기본값 TABLE)
설정된 LOCK_ESCALATION 값을 보고 싶은경우 아래와 같은 쿼리를 실행하면 된다.


SELECT lock_escalation, lock_escalation_desc FROM sys.tables WHERE name IN ('tb_ExTable')



▷ ALLOW_PAGE_LOCKS 설정 방법


EXEC SP_INDEXOPTION tb_ExTable, DISALLOWPAGELOCKS, 1;


ALLOW_PAGE_LOCKS 는 테이블 생성할 때 WITH 문에 "ALLOW_PAGE_LOCKS = OFF"를 추가 하거나
위와 같이 테이블 생성 이후 DISALLOWPAGELOCKS 를 "1"로 설정하여 값을 변경할 수 있다.
설정된 ALLOW_PAGE_LOCKS 값을 보고 싶은 경우 아래와 같은 쿼리를 실행하면 된다.


SELECT ALLOW_PAGE_LOCKS FROM sys.indexes
WHERE OBJECT_ID IN (OBJECT_ID('tb_ExTable'))



위 항목들을 모두 설정하였는데도 에러가 발생한다면;;;;
다른 방법을 찾아보기 바란다.
SP 또는 해당 Update/Delete 작업을 최소한으로 수정하여 처리하는 방법밖에 없을것 같다. 




저작자 표시 비영리 동일 조건 변경 허락
신고
크리에이티브 커먼즈 라이선스
Creative Commons License

WRITTEN BY
WizCody
WizCody 의 Tistory

트랙백이 하나이고 , 댓글이 없습니다.
secret
파티션된 테이블을 관리하다 보면 오래된 정보들은 사용을 많이 안하게 된다.
이러한 정보들을 따로 관리하기 위해 기존 파티션 테이블에서 빼내서 새로운 파티션 테이블에 추가하는 방법이 파티션 스위칭(슬라이딩 윈도우) 이다.

우선 월별로 나누어져 있는 파티션 테이블이 있다고 가정하에 설명하겠다.

  기존 파티션 테이블
 2009-04
 2009-05
 2009-06
 2009-07
 2009-08
 2009-09
 빈 파티션

1) 파티션 분할
기존 파티션 테이블에서 오래된 항목을 새로운 파티션으로 나누어 만든다.

ALTER PARTITION FUNCTION Partition_Function
SPLIT RANGE (Value);

Partition_Function : 파티션 함수명
Value : 새로운 구분값

위 문법을 적용한 예제는 아래와 같다.

ALTER PARTITION FUNCTION MarsterPF()
SPLIT RANGE ('2009-10');

위와 같이 기존 파티션을 나누면 2009-10 이후의 파티션이 새로 하나 생성이 된다.

마찬가지로 History 파티션도 새로 추가할 파티션을 하나 새로 생성한다.

HIstory 파티션 테이블
 2009-03
 빈 파티션

ALTER PARTITION FUNCTION HistoryPF()
SPLIT RANGE ('2009-04');

여기에는 기존 파티션에 가장 오래된 파티션의 기준값과 동일하게 정한다.

2) 파티션 스위칭
각각의 파티션 테이블에 새로운 파티션을 생성했으니 이제는 기존 파티션 테이블에서 History 파티션 테이블로
데이타를 옴겨야 한다.

ALTER TABLE Master_Table_Name
SWITCH PARTITION Marster_Partition_Number to
History_Table_Name PARTITION History_Partition_Number

Marster_Table_Name : 기존 파티션 테이블명
Marster_Partition_Number : History 파티션 테이블로 보낼 파티션 번호
History_Table_Name : History 파티션 테이블명
History_Partition_Number : History 파티션 테이블에 추가될 파티션 번호

위 문법을 적용한 예제는 다음과 같다.

ALTER TABLE [tempdb].[TestCalender]
SWITCH PARTITION 1 to
[tempdb].[HIstoryTestCalender] PARTITION 2

위와 같이 기존 파티션 테이블에서 가장 오래된 1 번 파티션을 History 파티션 테이블의 2번 파티션으로 옮긴다.
실제 옮길때는 메타데이타만 이동하기 때문에 시간이 오래 걸리지는 않는다.

3) 파티션 병함
마지막으로 기존 파티션 테이블에서 History 파티션 테이블로 이동한 파티션은 비워져있기 때문에 해당 파티션을 병합한다.

ALTER PARTITION FUNCTION Partition_Function
MERGE RANGE (Value);

Partition_Function : 파티션 함수명
Value : 오래된 구분값

위 문법을 적용한 예제는 아래와 같다.

ALTER PARTITION FUNCTION MarsterPF()
MERGE RANGE ('2009-04');

위와 같이 가장 오래된 파티션 테이블을 병합하면 해당 파티션은 사라진다.

마찬가지로 History 파티션도 오래된 파티션은 병합을 한다. 하지만 History 파티션 테이블도 기준값(월별)별로 관리를 할려면
병합을 하지 않아도 된다.
병합을 하지 않은경우 파티션 스위칭 시 History 파티션 번호를 잘 확인해야 한다.

병합전에는 항상 sys.partition_range_values 라는 테이블로 항상 파티션 테이블을 확인해보고 병합한다.
저작자 표시 비영리 동일 조건 변경 허락
신고
크리에이티브 커먼즈 라이선스
Creative Commons License

WRITTEN BY
WizCody
WizCody 의 Tistory

받은 트랙백이 없고 , 댓글이 없습니다.
secret

테이블 파티션을 사용하면 데이터 하위 집합을 빠르고 효율적으로 관리 및 액세스하는 동시에 데이터 컬렉션의 무결성을
유지할 수 있으므로 큰 테이블 또는 인덱스를 보다 편리하게 관리할 수 있습니다. 테이블 파티션을 사용하면 이전 릴리스에서는 몇 분 내지 몇 시간이 걸렸던 작업(예: OLTP에서 OLAP 시스템으로 데이터 로드)이 몇 초 안에 끝납니다.
데이터 하위 집합에서 수행되는 유지 관리 작업도 보다 효율적으로 수행할 수 있습니다. 전체 테이블 대신 필요한 데이터만
대상으로 삼기 때문입니다.

참고 분할 테이블 및 인덱스는 Microsoft SQL Server 2005 Enterprise Edition 및 Developer Edition에서만 지원됩니다.

분할된 테이블 및 인덱스의 데이터는 데이터베이스에서 두 개 이상의 파일 그룹으로 분할될 수 있는 단위로 나누어집니다. 행 그룹이 개별 파티션에 매핑되도록 데이터는 수평적으로 분할됩니다. 데이터에서 쿼리나 업데이트가 수행되면 테이블이나 인덱스는 단일 논리적 엔터티로 처리됩니다. 단일 인덱스나 테이블의 모든 파티션은 동일 데이터베이스에 상주해야 합니다.

분할된 테이블 및 인덱스는 제약 조건, 기본값, ID 및 타임스탬프 값, 트리거 등 표준 테이블 및 인덱스를 디자인하고 쿼리하는 작업과 관련된 모든 속성과 기능을 지원합니다. 따라서 한 서버의 로컬에만 있는 분할된 뷰를 구현하려는 경우에는 분할된 테이블을 대신 구현할 수도 있습니다.

현재 테이블의 크기, 증가된 테이블의 크기, 테이블의 사용 방법, 테이블이 사용자 쿼리 및 유지 관리 작업을 얼마나 잘 수행하는지에 따라 분할을 구현할지 여부를 결정합니다.

일반적으로 큰 테이블은 다음의 두 조건이 모두 충족될 때 분할에 적합합니다.

    ▷ 테이블에 서로 다른 방법으로 사용되는 데이터가 많거나 많아질 것으로 예상됩니다. 
    ▷ 테이블에 대한 쿼리나 업데이트가 의도 대로 수행되지 않거나 유지 관리 비용이 미리 정의된 유지 관리 기간을 초과합니다.

예를 들어 데이터가 이번 달의 경우 주로 INSERT, UPDATE 및 DELETE 작업에 사용되는 반면 이전 달의 경우 SELECT 쿼리에 사용된 경우에는 테이블을 월별로 분할하는 것이 관리하기에 더 수월할 수도 있습니다. 테이블의 정기적인 유지 관리 작업에서 데이터 하위 집합만을 대상으로 해야 하는 경우에 이러한 이점이 특히 부각됩니다. 테이블이 분할되어 있지 않으면 이러한 작업이 전체 데이터 집합에서 많은 리소스를 소비하게 됩니다. 예를 들어 분할을 사용하면 인덱스 다시 작성 및 조각화 모음 같은 유지 관리 작업을 단일 월의 읽기 전용 데이터에서 수행할 수 있습니다. 이때에도 읽기 전용 데이터는 여전히 온라인 액세스가 가능합니다.

이 예를 좀 더 확장하여 분석을 위해 1개월의 읽기 전용 데이터를 이 테이블에서 데이터 웨어하우스 테이블로 이동하려는 경우를 가정해 보십시오. 분할을 사용하면 데이터 하위 집합을 오프라인 유지 관리를 위한 준비 영역으로 빠르게 나눈 다음 기존 분할 테이블에 파티션으로 추가할 수 있습니다. 이때 이러한 테이블은 모두 동일한 데이터베이스 인스턴스에 있다고 가정합니다. 이전 릴리스에서 몇 분에서 몇 시간까지 걸렸던 이러한 작업은 일반적으로 몇 초밖에 걸리지 않습니다. 

테이블 파티션을 만들기 위해서는 다음과 같은 순서로 작업하면 데이타를 분할해서 테이블에 저장할 수 있습니다.
일반적으로 작업하는 경우에는 테이블을 생성하고 Insert를 계속 수행하면 단순하게 입력됩니다. 이런 일반 테이블과는 달리 파티션 테이블에서는 사전 작업이 필요합니다.
필요하다면 파일 그룹을 생성해서 서로 다른 디스크 드라이브에 저장 공간을 할당하고 파티션 함수와 파티션 스키마를 적용시켜서 필요한 규칙대로 데이터가 분할되어서 저장되게 합니다.

1) 파일그룹 생성

우선 테이블을 나누기 휘한 파티션 그룹이 필요하다.

USE TP_Test
GO
ALTER DATABASE TP_Test ADD FILEGROUP fg1
ALTER DATABASE TP_Test ADD FILEGROUP fg2
ALTER DATABASE TP_Test ADD FILEGROUP fg3
ALTER DATABASE TP_Test ADD FILEGROUP fg4

ALTER DATABASE TP_Test
ADD FILE
( NAME = data1,
  FILENAME = 'c:\Program Files\Microsoft SQL Server\MSSQL.1\MSSQL\Data\Test1.ndf',
  SIZE = 1MB,
  MAXSIZE = 100MB,
  FILEGROWTH = 1MB)
TO FILEGROUP fg1

ALTER DATABASE TP_Test
ADD FILE
( NAME = data1,
  FILENAME = 'c:\Program Files\Microsoft SQL Server\MSSQL.1\MSSQL\Data\Test2.ndf',
  SIZE = 1MB,
  MAXSIZE = 100MB,
  FILEGROWTH = 1MB)
TO FILEGROUP fg2

ALTER DATABASE TP_Test
ADD FILE
( NAME = data1,
  FILENAME = 'c:\Program Files\Microsoft SQL Server\MSSQL.1\MSSQL\Data\Test3.ndf',
  SIZE = 1MB,
  MAXSIZE = 100MB,
  FILEGROWTH = 1MB)
TO FILEGROUP fg3

ALTER DATABASE TP_Test
ADD FILE
( NAME = data1,
  FILENAME = 'c:\Program Files\Microsoft SQL Server\MSSQL.1\MSSQL\Data\Test4.ndf',
  SIZE = 1MB,
  MAXSIZE = 100MB,
  FILEGROWTH = 1MB)
TO FILEGROUP fg4

하나의 데이타 베이스에 FILEGROUP 을 fg1~fg4까지 4개로 생성을 합니다.

2) PARTITION FUNCTION 생성


파티션 생성 함수 문법은 다음과 같습니다.

CREATE PARTITION FUNCTION Partition_Function (InputType)
AS RANGE [LEFT | RIGHT]
FOR VALUES (Value1,[Value2],...)

테이블 파티션 작업을 하기 위해서는 위와 같이 우선 테이블 파티션 생성 함수가 필요합니다.

Partition_Function : 파티션 함수명
InputType : 기준이 되는 파라메터 타입
Range : 구분 기준(기본값은 LEFT)
VALUES : 구분값(1개이상은 적용해아 분할이 됨)

위 문법을 적용한 예제는 아래와 같습니다.

CREATE PARTITION FUNCTION CalenderPF (NVARCHAR(4))
AS RANGE LEFT
FOR VALUES ('4월','7월','10월')

위와 같이 함수를 생성하면 월별로 입력받는다고 가정을 한다면
1~3월까지는 첫번째 파티션에,
4~6월까지는 두번째 파티션에,
7~9월까지는 세번째 파티션에,
10~12월까지는 네번째 파티션에 저장됩니다.

모두를 합하면 전체 데이타가 되고 각각 파티션 별로 분기별 데이타가 됩니다.
그리고 AS RANGE LEFT 는 각 파티션별 구분값을 앞에서 비교할것인지 뒤에서 비교할것인지를 확인 하는 기준값입니다.

3) PARTITION SCHEME 생성

파티션 스키마 생성 문법은 다음과 같습니다.

CREATE PARTITION SCHEME Partition_Schema
AS PARTITIOIN Partition_Function
[ALL] TO (File_Group1|[PRIMERY],[File_Group2],...)

파티션 함수가 생성되면 그다음은 위와 같이 스키마를 생성해야 합니다.

Partition_Schema : 파티션 스키마명
Partition_Function : 파티션 함수명
FileGroup : 파티션을 나눌 파일 그룹

위 문법을 적용한 예제는 다음과 같습니다.

CREATE PARTITION SCHEME CalenderPS
AS PARTITIOIN CalenderPF
TO (fg1, fg2, fg3, fg4)

위와 같이 스키마를 생성하면 파일 그룹별로 파티션이 생성됩니다.
파일 그룹을 동일 드라이브로 잡아도 되고 그룹별로 다른 드라이브를 잡아서 작업을 하셔도 됩니다.

4) 파티션 테이블 생성

마지막으로 테스트할 파티션 테이블을 생성하면 됩니다.

CREATE TABLE tempdb.TestCalender
(     Seq INT,
      Month NVARCHAR(4))
ON CalenderPS ( Month )

테이블 생성시 위에서 만들어준 파티션 스키마와 파티션을 나눌 파라메터 값을 등록합니다.

이제 모든 준비는 끝났습니다.
테스트를 위해 데이타를 등록해 보겠습니다.

INSERT tempdb.TestCalender VALUES(1, '2월')
INSERT tempdb.TestCalender VALUES(1, '4월')
INSERT tempdb.TestCalender VALUES(1, '8월')
INSERT tempdb.TestCalender VALUES(1, '12월')

데이타를 모드 등록하고 등록된 정보를 조회하면 파티션 별로 데이타가 등록된걸 볼수 있습니다.

SELECT Month, $partition.CalenderPF(Month) FileGroup
FROM tempdb.TestCalender

위와 같이 SELECT 조회를 하면 데이타 별로 입력된 파티션 정보가 나타납니다.

저작자 표시 비영리 동일 조건 변경 허락
신고
크리에이티브 커먼즈 라이선스
Creative Commons License

WRITTEN BY
WizCody
WizCody 의 Tistory

받은 트랙백이 없고 , 댓글이 없습니다.
secret

MSX 서버에서 온 작업이나 그단계 또는 일정을 추가 업데이트 삭제할 수 없습니다

해결

 

/*srvid = 0 srvname='LEETEST'*/
use master
select * from sysservers

/*originating_server='WEBTEST' 를 'LEETEST'로 수정해서 해결함.' */
use msdb
select * from sysjobs

 

EXEC sp_configure 'allow updates', 1

Update sysjobs
set originating_server = 'LEETEST'

EXEC sp_configure 'allow updates', 0

 

 

이러한 메시지가 뜰 것입니다.
"MSX 서버에서 온 작업이나 그단계 또는 일정을 추가 업데이트 삭제할 수 없습니다."
"DEVPIA에서 찾은 QNA를 발췌하겠습니다.

========================================================
이 문제는 SQL2000의 특성으로 인해 발생하는 문제입니다.

모든 scheduling job들은 ‘msdb..sysjobs’ 시스템 테이블에 저장됩니다.
sysjobs 테이블에는 ‘originating_server’ 칼럼이 존재하고 이 칼럼에는 job이 생성된 server의 이름이 저장됩니다.
SQL7.0에서는 ‘originating_server’ 칼럼에 항상 ‘(local)’ 값이 저장되기 때문에 Server의 이름을 변경해도 영향을 받지 않습니다. 하지만, SQL2000에서는 Multiple Instance가 지원되기 때문에 ‘originating_server’ 칼럼에 실제 SQL Server의 instance명이 저장되게 됩니다.
만일, Server의 이름이 변경되면 현재의 Server명과 msdb..sysjobs’ 테이블에 저장된 ‘originating_server’의 값이 다르기 때문에 job 변경/삭제시 14274에러가 발생하게 됩니다.

 

다음의 쿼리를 실행하여 jon 변경/삭제시 발생하는 에러를 없앨 수 있습니다.


update msdb..sysjobs set originating_server = ‘<new server>’
where originating_server = ‘<old server>’


만일, Named Instance를 사용하는 job이라면 다음의 쿼리를 실행합니다.

update msdb..sysjobs set originating_server = ‘<new server>\<instance>’
where originating_server = ‘<old server>\<instance>’


참고적으로 ‘select * from msdb..sysjobs’ 쿼리를 실행한 다음 originating_server 칼럼을 확인하여 모든 job들에 대해 설정되어 있는 SQL Server명을 확인할 수 있습니다.
========================================================

이렇게 수행하셔도 되고
EM을 열고 직접 msdb 가셔서 모든 행 반환해서 수정하셔도 같은 결과가 나올겁니다.
 


신고
크리에이티브 커먼즈 라이선스
Creative Commons License

WRITTEN BY
WizCody
WizCody 의 Tistory

트랙백  2 , 댓글이 없습니다.
secret