순차 정렬(Sequential Sort) 알고리즘 

이번에는 반복적인 방법으로 해결하는 순차 정렬(Sequential Sort) 알고리즘을 살펴볼게요. 

정렬 알고리즘은 배열의 자료를 원하는 순으로 배치하는 알고리즘을 말해요. 정렬 알고리즘은 입력 인자로 정렬할 자료들이 있는 배열의 시작 주소와 원소 개수, 비교 알고리즘이 필요합니다. 그리고 수행 후에는 배열 내의 자료들은 원하는 순서로 배치한 상태여야 합니다. 

순차 정렬은 맨 앞에서부터 제일 작은 원소를 배치하게 만들어 나가는 알고리즘이예요. 이를 위해 배치할 자리에 있는 원소를 뒤쪽에 있는 원소들과 비교하면서 작은 것을 발견하면 배치할 위치의 원소와 교환해요.

순차 정렬(base:배열의 시작 주소, n: 원소 개수, compare:비교 논리)

    반복(i:=0->n)

        반복(j:=i+1->n)

            조건(compare(base[i], base[j]) > 0)

                교환(base[i],base[j])

순차 정렬 알고리즘 바로가기


버블 정렬 (Bubble Sort) 알고리즘

이번에는 반복적인 방법으로 해결하는 버블 정렬 알고리즘을 살펴봅시다. 

 정렬 알고리즘은 배열의 자료를 원하는 순으로 배치하는 것을 말합니다. 이를 위해 입력 인자로 정렬할 자료들이 있는 배열의 시작 주소와 원소 개수, 비교 알고리즘을 전달합니다. 그리고 수행 후에는 배열 내의 자료들이 원하는 순서로 보관한 상태여야 합니다. 

 이 중에 버블 정렬은 앞에서부터 이웃하는 원소의 값을 비교하여 위치를 교환하는 것을 반복합니다. 이를 끝까지 수행하면 제일 큰 값이 맨 뒤에 위치합니다. 그리고 정렬할 개수를 1 줄인 후에 다시 반복합니다. 정렬할 원소의 개수가 1이면 모든 작업을 완료합니다. 

버블 정렬(base:배열의 시작 주소, n: 원소 개수, compare:비교 논리)

    반복(i:=n;  i>1  ; i:= i-1)

        반복(j:=1; j<i ; j:=j+1)

            조건(compare(base[j-1], base[j]) > 0)

                교환(base[j-1],base[j]) 

버블 정렬 알고리즘 바로가기

선택 정렬 (Selection Sort) 

 이번에는 반복 알고리즘일 이용하는 선택 정렬 알고리즘을 알아봅시다. 

 선택 정렬 알고리즘은 제일 큰 값을 찾아 맨 뒤의 요소와 교체하는 방법을 반복하여 전체를 정렬하는 알고리즘입니다. 물론 제일 작은 값을 찾아 맨 앞의 요소와 교체하는 방법을 반복할 수도 있습니다. 

 선택 정렬 알고리즘을 의사코드(pseudo code: 논리적인 수행 흐름을 이해할 수 있게 작성한 코드)는 다음과 같습니다. 

선택 정렬(base:컬렉션,n:원소 개수,compare:비교 논리)

    반복(i:=n;  i>1  ; i:= i-1)

        반복(max=0,j:=1; j<i ; j:=j+1)

            조건(compare(base[max], base[j]) < 0)

                max := j

        temp: = base[i-1]

        base[i-1] = base[max]

        base[max] = temp

선택 정렬 알고리즘 바로가기


삽입 정렬 (Insertion Sort) 

이번에는 반복 알고리즘 중에 삽입 정렬 알고리즘을 알아봅시다. 

 삽입 정렬 알고리즘은 점진적으로 정렬 범위를 넓혀 나가는 방식으로 정렬하는 알고리즘입니다. 이를 위해 새로운 범위에 포함하는 마지막 원소를 앞으로 이동하면서 자신보다 작은 요소를 찾을 때까지 이동하면서 자리를 교환합니다. 

삽입 정렬(base:컬렉션, n:원소 개수, compare:비교 논리)

    반복(i:=1;  i<n  ; i:= i+1)

        반복(j=i; j>0 ; j:=j-1)

            조건(compare (base [j-1], base [j]) < 0)

                temp: = base [j-1]

                base[j-1] = base [j]

                base[j] = temp

            아니면

                루프 탈출

삽입 정렬 알고리즘 바로가기

쉘 정렬 (Shell Sort) 

쉘 정렬은 삽입 정렬 알고리즘을 이용하는 정렬 방식입니다.

쉘 정렬은 같은 간격에 있는 원소들을 삽입 정렬 원리로 정렬하는 것을 반복합니다. 

간격의 초기값은 배열의 크기/2이며 간격이 1일 때까지 1/2로 줄이면서 반복합니다.

퀵 정렬(Quick Sort)

퀵 정렬 알고리즘은 재귀적인 방법으로 문제를 해결하는 알고리즘입니다. 

 퀵 정렬 알고리즘은 피벗 값을 선택하여 피벗 값보다 작은 값들은 왼쪽으로 보내고 큰 값들은 오른쪽으로 보낸 후에 이들 사이에 피벗을 위치시키는 원리를 이용합니다. 이후 피벗보다 작은 값들을 재귀 호출로 정렬하고 피벗보다 큰 값들도 재귀 호출로 정렬하는 방식입니다. 

 그런데 퀵 정렬은 어떠한 요소를 피벗으로 선택하냐에 따라 성능에 차이가 납니다. 만약 전체 요소의 중간 순위의 요소를 선택하면 재귀 호출에서 반씩 나누어 정렬을 하게 되어 좋은 성능을 발휘합니다. 하지만 가장 작은 값이나 가장 큰 값을 피벗으로 선택하면 최악의 성능을 발휘합니다. 

 여기에서는 맨 앞과 맨 뒤, 그리고 중간 위치의 요소를 비교하에 세 값 중에 중간 값을 피벗으로 선택할게요. 

퀵 정렬(base:컬렉션,n: 원소 개수, compare:비교 논리)

    조건(n<=1)

        종료

    피벗을 선택한다.

    피벗과 맨 앞의 요소를 교환한다.

    big:=0

    small:=n

    반복(big<small)

            반복(big:=big+1; big<n; big:=big+1)

                조건(compare(base[0],base[big])<0)

                    루프 탈출

            반복(small:small-1;small>0;small:small-1)

                조건(compare(base[0],base[small])>0)

                    루프 탈출

            조건(big<small)

                교환(base [big], base [small])

    교환(base [0], base [small])

    퀵 정렬(base,small, compare)

    퀵 정렬(base+big, n-big, compare)

 

 퀵 정렬 알고리즘의 탈출 조건은 n이 1보다 작거나 같을 때입니다.

퀵 정렬 알고리즘 바로가기

병합 정렬(Merge Sort, 합병 정렬) 알고리즘

이번에는 병합 정렬 알고리즘을 살펴봅시다. 

 병합 정렬 알고리즘은 배열을 작은 단위의 배열로 분할한 후에 분할한 배열을 정렬하고 이들을 다시 정렬하면서 전체 배열을 정렬하는 알고리즘입니다. 

병합 정렬(base:배열의 시작 주소, n: 원소 개수, compare:비교 논리)

    ah:= n/2

    bh:= n - ah;

    조건(n 1보다 작거나 같으면) 종료

    병합정렬(base,ah,compare)

    병합접열(base+ah,bh,compare)

    tbase에 동적 메모리 할당(원소크기*원소개수)

    메모리 복사(tbase,base)

    ai:=0

    bi:=ah

    i:=0

    반복(ai ah보다 작으면서 bi n보다 작다)

        조건(tbase[ai]가 tbase[bi]보다 작거나 같으면

            base[i] := base[ai]

            ai:= ai+1

        아니면

             base[i]:= base[bi]

             bi:= bi+1

        i:=i+1

    반복(ai가 ah보다 작다)

        base[i]:= tbase[ai]

        i:=i+1

        ai:=ai+1

    반복(bi가 n보다 작다)

        base[i]:= tbase[bi]

        i:=i+1

        bi:=bi+1

병합 정렬 알고리즘 바로가기

힙 정렬(Heap Sort)

힙 정렬은 힙 트리를 이용하는 알고리즘입니다. 최대 힙을 사용하면 크기 순(Ascend)으로 정렬하고 최소 힙을 사용하면 크기 역순(Descend)으로 정렬합니다. 

힙 정렬은 먼저 힙 트리를 구성합니다. 그리고 루트의 값과 맨 마지막 값을 교환한 후에 정렬 범위를 1 줄입니다. 이와 같은 작업을 반복하여 정렬 범위가 1일 때까지 반복합니다. 

최대 힙 트리에서 루트는 최대 값을 갖습니다. 따라서 마지막 값과 교환하면 제일 큰 값이 맨 뒤로 배치할 수 있습니다. 그리고 난 후에 정렬 범위를 줄여나가면 최종적으로 정렬 상태를 만들 수 있는 것입니다. 

힙 정렬(base:배열의 시작 주소, n: 원소 개수, compare:비교 논리)

    초기 힙 구성

    루트와 맨 마지막 자손 교환

    n 1 감소

    반복(n: n->1)

        힙 구성

        루트와 맨 마지막 자손 교환

        n을 1 감소

힙 정렬 알고리즘 바로가기

14.3.1 START 트랜잭션이 COMMIT 및 ROLLBACK 구문

START 거래
    [ transaction_characteristic[, transaction_characteristic] ...]

transaction_characteristic:
    일관성 스냅 샷
  | 읽기 쓰기
  | 읽기 전용

[WORK]를 BEGIN
COMMIT [WORK]와 [NO] CHAIN] [[NO] RELEASE]
ROLLBACK [WORK]와 [NO] CHAIN] [[NO] RELEASE] 없습니다
SET의 자동 커밋 = {0 | 1}

이 문은 사용을 제어 제공하는  거래를 :

  • START TRANSACTION 또는  BEGIN 새 트랜잭션을 시작합니다.

  • COMMIT 그 변경 사항을 영구적하고, 현재의 트랜잭션 (transaction)을 범했습니다.

  • ROLLBACK 롤의 변경을 취소, 현재의 트랜잭션 (transaction)를 백업합니다.

  • SET autocommit 비활성화 또는 현재 세션에 대한 기본 자동 커밋 모드를 가능하게한다.

기본적으로 MySQL은 실행됩니다  자동 커밋  활성화 모드. 이것은 바로 디스크에 대한 업데이트 (수정) 테이블, MySQL의 저장 업데이트가 영구적으로하는 문을 실행으로 있다는 것을 의미한다. 변화는 롤백 할 수 없습니다.

문 하나의 시리즈에 대한 암시 적으로 자동 커밋 모드를 해제하려면 사용  START TRANSACTION 문 :

트랜잭션을 시작;
SELECT @A가 : 표 FROM SUM (급여) = WHERE 유형 = 1;
UPDATE 표 2 SET 요약 = @ WHERE 유형 = 1;
범하다;

으로  START TRANSACTION당신과 거래를 종료 할 때까지, 자동 커밋이 비활성화 된 상태로 남아 COMMIT 나  ROLLBACK. 자동 커밋 모드는 이전 상태로 되돌아갑니다.

START TRANSACTION 트랜잭션 특성을 제어하는 ​​여러 가지 개질제를 허용한다. 여러 수식을 지정하려면 쉼표로 구분합니다.

  • WITH CONSISTENT SNAPSHOT 수정은 시작  일관된 읽기  그것을 할 수있는 스토리지 엔진을. 이 경우에만 적용됩니다  InnoDB. 효과는 발행과 동일  START TRANSACTION a로 다음을 SELECT 어떤에서  InnoDB 테이블. 참조  절 15.5.2.3를, "일관된 Nonlocking 읽고" . WITH CONSISTENT SNAPSHOT 수정은 현재의 트랜잭션 (transaction) 변경되지 않는  격리 수준을 , 그래서 현재 격리 수준이 일관된 읽기를 허용 한 경우에만이 일관된 스냅 샷을 제공합니다. 일관된 읽기를 허용하는 유일한 격리 수준이다  REPEATABLE READ. 다른 분리 레벨의 경우,  WITH CONSISTENT SNAPSHOT절은 무시됩니다. 때 MySQL은 5.7.2로, 경고가 생성됩니다  WITH CONSISTENT SNAPSHOT 절이 무시됩니다.

  • READ WRITE 및  READ ONLY 수정 트랜잭션 액세스 모드를 설정합니다. 그들은 허용하거나 트랜잭션에 사용되는 테이블에 대한 변경을 금지합니다. READ ONLY제한은 수정하거나 다른 트랜잭션에 볼 수 있습니다 모두 트랜잭션 및 비 트랜잭션 테이블 잠금에서 거래를 방지; 거래는 여전히 수정하거나 임시 테이블을 잠글 수 있습니다.

    MySQL은에 쿼리에 대한 별도의 최적화를 가능하게  InnoDB 트랜잭션이 읽기 전용으로 알려져있다 테이블. 지정은  READ ONLY 이러한 최적화가 읽기 전용 상태가 자동으로 판별 할 수없는 경우에 적용됩니다 보장합니다. 참조  섹션 9.5.3을, "최적화 InnoDB의 읽기 전용 거래" 더 많은 정보를 얻을 수 있습니다.

    접근 모드를 지정하지 않으면, 디폴트 모드가 적용된다. 기본이 변경되지 않는 한, 그것은 읽기 / 쓰기. 모두를 지정하는 것은 허용되지 않습니다  READ WRITE 과  READ ONLY 같은 성명에서.

    읽기 전용 모드에서, 그것은으로 만든 테이블을 변경할 수 남아  TEMPORARY DML 문을 사용하여 키워드를. DDL 문으로 변경은 영구 테이블로, 허용되지 않습니다.

    기본 모드를 변경하는 방법을 포함하여 트랜잭션 액세스 모드에 대한 자세한 내용은 다음을 참조 섹션 14.3.6, "SET 트랜잭션 구문"을 .

    경우  read_only 시스템 변수가 사용 가능으로, 명시 적으로 트랜잭션을 시작  START TRANSACTION READ WRITE 요구  SUPER 권한을.

중대한

(예 : JDBC 등) MySQL 클라이언트 응용 프로그램을 작성에 사용되는 대부분의 API는 (때로는한다) 대신에 보내는 사용할 수 있습니다 트랜잭션을 시작하는 자신의 방법을 제공하는  START TRANSACTION 클라이언트에서 문을. 참조  25 장,  커넥터 및 API에 대한 자세한 내용은, 당신의 API에 대한, 또는 문서를.

명시 적으로 자동 커밋 모드를 해제하려면 다음 문을 사용합니다 :

SET의 자동 커밋 = 0;

설정에 따라 자동 커밋 모드를 해제 한 후  autocommit 제로 변수 (예에 대한 것과 같은 트랜잭션 안전 테이블의 변경  InnoDB 또는  NDB) 즉시 영구적으로하지 않습니다. 당신은 사용해야  COMMIT 디스크에 변경 사항을 저장하거나  ROLLBACK 변경 사항을 무시.

autocommit 세션 변수이며, 각 세션에 대해 설정해야합니다. 각각의 새로운 연결을 위해 자동 커밋 모드를 사용하지 않으려면의 설명을 참조하십시오  autocommit에서 시스템 변수를  제 6.1.5, "서버 시스템 변수" .

BEGIN 과  BEGIN WORK 의 별칭으로 지원되는  START TRANSACTION 트랜잭션을 시작합니다. START TRANSACTION 표준 SQL 구문이고, 임시 트랜잭션을 시작하는 좋은 방법이며, 수정 허용 BEGIN 하지 않습니다.

BEGIN 문은 사용과 다른  BEGIN 시작 키워드  BEGIN ... END 화합물 문을. 후자는 트랜잭션을 시작하지 않습니다. 참조 섹션 14.6.1를, "BEGIN ... END 복합 문 구문" .

노트

저장된 모든 프로그램 (저장 프로 시저 및 함수, 트리거, 이벤트), 파서 취급 이내  BEGIN [WORK] (a)의 시작으로  BEGIN ... END 블록. 와이 컨텍스트에서 트랜잭션을 시작  START TRANSACTION 하는 대신.

옵션  WORK 키워드에 대한 지원  COMMIT 및  ROLLBACK한,  CHAIN 그리고  RELEASE 절을. CHAIN 및 RELEASE 거래 완료 추가 제어에 사용될 수있다. 의 값은  completion_type 시스템 변수는 기본 완료 동작을 결정합니다. 참조  섹션 6.1.5, "서버 시스템 변수"를 .

AND CHAIN 절은 현재 종료하자마자 시작 새로운 트랜잭션을 발생, 새로운 트랜잭션 방금 종료 트랜잭션과 같은 분리 레벨을 갖는다. RELEASE 절은 현재 트랜잭션을 종료 한 후 현재 클라이언트 세션을 분리하도록 서버를 발생합니다. 포함  NO 키워드를 억제  CHAIN 또는  RELEASE 경우에 유용 할 수 있습니다 완료,  completion_type 시스템 변수가 체인의 원인 또는 기본적으로 완료를 해제하도록 설정되어 있습니다.

트랜잭션을 시작하면 보류중인 트랜잭션이 커밋됩니다. 참조  섹션 14.3.3, "는 암시 적 커밋 원인 문" 자세한 내용을.

트랜잭션을 시작도 함께 획득 한 테이블 잠금이 발생  LOCK TABLES 하면 실행했던 것처럼, 발매 예정을  UNLOCK TABLES. 트랜잭션을 시작하기로 취득하는 글로벌 읽기 잠금을 해제하지 않습니다  FLUSH TABLES WITH READ LOCK.

최상의 결과를 얻으려면, 거래는 하나의 거래 안전 스토리지 엔진에 의해 관리 만 테이블을 사용하여 수행해야합니다. 그렇지 않으면, 다음과 같은 문제가 발생할 수있다 :

  • 둘 이상의 트랜잭션 안전 저장 엔진 (예에서 테이블을 사용하는 경우  InnoDB) 및 트랜잭션 격리 수준되지 않습니다  SERIALIZABLE, 하나의 트랜잭션이 커밋 할 때 같은 테이블을 사용하는 다른 지속적인 거래 만 일부 변경을 볼 가능성이있다 첫 번째 트랜잭션에 의해 만들어진. 즉, 트랜잭션의 원 자성이 혼합 된 엔진과 발생할 수 있습니다 일관성을 보증 할 수 없습니다. (혼합 엔진 트랜잭션이 빈번하지 않으면 사용할 수  SET TRANSACTION ISOLATION LEVEL 로 분리 레벨을 설정할  SERIALIZABLE 필요에 따라 트랜잭션 단위에서).

  • 당신이 트랜잭션 내에서 트랜잭션 안전하지 않은 테이블을 사용하는 경우, 해당 테이블의 변경에 관계없이 자동 커밋 모드의 상태를 한 번에 저장됩니다.

  • 당신이 실행하는 경우  ROLLBACK 트랜잭션 내에서 비 트랜잭션 테이블을 업데이트 한 후 문을 ER_WARNING_NOT_COMPLETE_ROLLBACK 경고가 발생합니다. 트랜잭션 안전 테이블에 대한 변경 사항은 nontransaction 안전 테이블에 대한 변경 사항을 롤백하지만되지 않습니다.

각 트랜잭션에, 한 덩어리에 바이너리 로그에 저장됩니다  COMMIT. 롤백 트랜잭션은 기록되지 않습니다. ( 예외 :. 비 트랜잭션 테이블에 대한 수정은 롤백 할 수 없습니다 롤백 트랜잭션이 비 트랜잭션 테이블에 대한 수정이 포함 된 경우, 전체 트랜잭션이 함께 기록됩니다  ROLLBACK비 트랜잭션 테이블에 대한 수정이 복제되도록 끝에 문.) 참조  섹션 6.4.4, "바이너리 로그" .

당신은와의 거래에 대한 격리 수준 또는 액세스 모드를 변경할 수 있습니다  SET TRANSACTION 문을.참조  섹션 14.3.6, "SET 트랜잭션 구문"을 .

위로 롤링 (오류가 발생하면, 예를 들어)을 명시 적으로 요청하는 데 사용하지 않고 암시 적으로 발생할 수있는 느린 동작 할 수있다. 이 때문에,  SHOW PROCESSLIST 표시  Rolling back 에서  State 세션의 열뿐만 아니라, 명시 적으로 수행 롤백 용  ROLLBACK 문장뿐만 아니라 내재 롤백 대한.


[MySQL] alter table 명령어

※ 경고
높이뜬새의 모르는 사람이 없는 팁시리즈 입니다.
이 팁시리즈는 지면낭비라는 항의시에 즉각 중단됩니다.


alter table 명령어는 모르시는 분이 없듯이 테이블의 스키마를 변경 할 수 있게 끔 해주는 아주 유용한 명령어입니다.
alter table 명령어가 없으면 아주 끔찍한 일이 벌어질 수도 있습니다. create table...과 drop table의 남발!! 정말 끔찍하지 않습니까? 그런데 테이블의 스키마가 무엇이냐구요? 

이것 역시 모르시는 분이 단 한사람도 없겠지만 쉽게 말해서 테이블의 구조라고 이해하시면 됩니다. 관계형 데이타베이스를 이용할려면 이놈들을 속성(attribute)들로 뭉쳐진 의미있는 정보의 단위로 이끌어 내야 하는데 이러한 것을 엔티티(Entity)라고 합니다. 정확히 말하면 이 엔티티의 구조를 설명한 게 스키마입니다! 이 스키마는 논리적 스키마와 물리적 스키마로 분리해서 부르는데 특정회사의 데이타베이스에 맞게 스키마를 표현하게 되면 물리적 스키마라고 부릅니다.
엔티티가 물리적 스키마에 의해서 실제 데이타베이스에 적용된 것이 테이블입니다. 더 자세한 사항은 시중에 나와 있는 데이타베이스 설계 관련 서적을 참고하세요! 없는 실력으로 더 자세한 설명은 도저히 무리입니다.^^

alter table 명령어는 각 회사의 데이타베이스에 따라서 약간씩 차이가 있습니다. 여기서는 제가 맨날 까먹는 alter table 명령어 중 MySQL 용 몇개를 오로지 안까먹기 위해서 적어둘려고 합니다. 사실 팁은 아닙니다.^^

일단 alter table 명령어를 쓰기 위해서 간단한 테이블을 하나 만들겠습니다. 테이블 이름은 mytable이고 컬럼은 id와 name만 가지고 있는 아주 간단한 예제용 테이블입니다.
 

mysql> create table mytable ( 
        ->id varchar(12) not null, 
        ->name varchar(20) not null );
 

아주 simple 하고 좋은데 id와 name외에 주소를 뜻하는 addr 컬럼을 추가해 봐야 하겠습니다.

[테이블에 새로운 컬럼 추가하기]

형식) alter table [테이블명] add column [추가할 컬럼명] [추가할 컬럼 데이타형]
 

mysql> alter table mytable add column addr varchar(70) not null;


뒤에 not null 을 안 붙이면 null 허용으로 컬럼이 추가됩니다. 위의 명령어에 의해서 addr 이라는 컬럼이 mytable에 추가 되었습니다. 그런데 주소를 뜻하는 addr 컬럼의 자릿수가 웬지 부족해 보입니다. varchar(100)으로 늘려봐야 겠습니다.

[테이블의 컬럼 타입 변경하기]

형식) alter table [테이블명] modify column [변경할 컬럼명] [변경할 컬럼 타입]
 

mysql> alter table mytable modify column addr varchar(100) not null;


desc mytable로 확인해 보면 addr 컬럼이 varchar(100)으로 정확하게 변경되었습니다.주소 컬럼에는 맞지 않겠지만 addr 컬럼을 int 형에 null 허용으로 변경해 보고 싶습니다.
 

mysql> alter table mytable modify column addr int;


desc mytable로 확인 해보니 int 형에 null 허용으로 변경되었습니다. 주소가 int 형이라니 말이 안됩니다. int 형이라면 아무래도 나이가 맞을 것 같습니다. addr의 컬럼명 자체를 age 로 바꿔야 겠습니다. 

[테이블의 컬럼 이름 변경하기] 

형식) alter table [테이블명] change column [기존 컬럼명] [변경할 컬럼명] [변경할 컬럼 타입]
 

mysql> alter table mytable change column addr age int not null;


addr 컬럼명이 age 라는 컬럼명으로 정확히 바뀌었습니다. 그치만 애초에 age 컬럼은 필요가 없었습니다. 테이블에서 age 컬럼을 삭제 해야 하겠습니다.

[테이블 컬럼 삭제하기]


형식) alter table [테이블명] drop column [삭제할 컬럼명]
 

mysql> alter table mytable drop column age;


desc mytable로 확인해보니 age 컬럼이 삭제되어져 있습니다. 뭔가 허전합니다. id 컬럼에 인덱스(Index)를 주면 허전함이 달래질 것 같습니다.

[테이블 컬럼에 인덱스 주기]

형식) alter table [테이블명] add index 인덱스명( 인덱스를 줄 컬럼1, 인덱스를 줄 컬럼2,...)
 

mysql> alter table mytable add index myindex( id );


위에서는 myindex 라는 인덱스명으로 id 컬럼에 index 가 추가되었습니다. 인덱스를 줄 컬럼을 한개 이상 써 넣으면 복수개의 컬럼에 대해서도 인덱스를 생성할 수 있습니다. 이번에는 id에 주어진 index 를 삭제해 보겠습니다.

[테이블 컬럼에 인덱스 삭제하기]


형식 ) alter table [테이블명] drop index 인덱스명
 

mysql> alter table mytable drop index myindex;


위에서는 처음에 줬던 index 인 myindex 를 삭제하고 있습니다. 참고로 인덱스를 확인하기 위해서는 아래의 명령어를 쓰시면 됩니다. mytable은 테이블 명입니다.
 

mysql> show index from mytable;


alter table로 인덱스도 추가하고, 날려봤는데 primary key 는 못 만들까요? 당연히 만들수 있습니다. 그대신 primary key 는 조건이 있습니다. primary key를 만들려는 컬럼에 조건이겠지요!!! 레코드에 값이 추가 안 되어있을 때는 괜찮지만, 값이 들어있다면 column 에 null 값이 들어 있는지, 중복된 값이 존재하는 column 인지를 따져봐야 합니다. null 값이 없고, 중복되는 column 이 없다면 primary key를 만들 수 있습니다.


[테이블에 primay key 만들기]


형식 ) alter table [테이블명] add primary key ( 키를 줄 column명1, 키를 줄 column명2, ... );
 

mysql> alter table mytable add primary key ( id );


위에서는 mytable의 id 컬럼에 primary key 를 만들고 있습니다. primary key 는 composite key 가 가능하므로 여러개의 column을 묶어서 primary key 로 사용할 수도 있습니다.


만들어진 primary key를 지워야 할 때도 있습니다.


[테이블에 primay key 삭제하기]

형식) alter table [테이블명] drop primary key;
 

mysql> alter table mytable drop primary key;


위에서는 mytable에 만들었던 primary key를 삭제하고 있습니다. 참고로 primary를 만들때는 index가 자동으로 primary key 컬럼에 추가됩니다.

primary key도 다시 날려버리고 아주 좋습니다. 마지막으로 테이블명을 변경해 보고 싶습니다. 사실 mytable이라는 테이블명이 아주 마음에 안 들었습니다.

[테이블 명 바꾸기]


형식) alter table [원본 테이블명] rename [새로운 테이블명];

mysql> alter table mytable rename utable;


alter table에 대해서 대충 알아 봤습니다. 그 유명한 다른 primary key를 뽀려온 foreign key 에 대한 부분도 정리 했으면 좋겠는데.. 오늘은 제 시간 관계 상 여기서 정리를 끝내겠습니다.


이번 강좌에서는

  • 포인터에 대한 완벽한 이해
  • *, & 단항 연산자의 의미


  우왕~ 안녕하세요 여러분. 아마 C 언어를 배웠거나 배우고 있는 사람들은 포인터에 대해 익히 들어 보셨을 것 입니다. 이해하기 힘들기로 악명 높은 그 포인터를 말이죠. 하지만, 저와 함께 한다면 큰 무리 없이 배우 실 수 있을 것이라 생각됩니다.


  포인터를 이해하기 앞서
 

  앞서 3 강에서 이야기 하였지만 모든 데이터들은 메모리 상에 특정한 공간에 저장 되어 있습니다. 우리는 앞으로 편의를 위해, 메모리의 특정한 공간을 '방' 이라고 하겠습니다. 즉, 각 방에는 데이터들이 들어가게 되는 것 입니다. 보통 사람들은 한 방의 크기를 1 바이트 라고 생각합니다. 우리가 만약 int 형 변수를 정의한다면 4 바이트 이므로 메모리 상의 4 칸을 차지하게 됩니다. 그런데 말이죠. 프로그램 작동 시 컴퓨터는 각 방에 있는 데이터를 필요로 하게 됩니다. 따라서, 서로 구분하기 위해 각 방에 고유의 '주소(address)' 를 붙여 주었습니다. 우리가 아파트에서 각 집들을 호수로 구분하는 것 처럼 말입니다. 예를 들어 우리가 아래와 같은 int 변수 a 를 정의하였다면 특정한 방에 아래 그림 처럼 변수 a 가 정의됩니다.

int a = 123; // 메모리 4 칸을 차지하게 한다.



메모리에 123 이란 수가 있고 이 수는 메모리에 0x152839 (앞에 0x 는 이 수가 16 진수로 표시되었다는 것을 의미해요) 에 위치



이 때, 0x152839 는 제가 아무렇게나 정한 이 방의 시작 주소 입니다. 참고로, 0x 가 뭐냐고 물어보는 사람들이 있을 텐데, 이전 강좌에서도 이야기 하였지만 16 진수라고 표시한 것 입니다. 즉, 16 진수로 152839 (10 진수로 1386553) 라는 위치에서 부터 4 바이트의 공간을 차지하며 123 이라는 값이 저장되어 있게 하라는 뜻이지요.

그렇다면 아래와 같은 문장은 어떻게 수행 될까요?

a = 10;


사실 컴파일러는 위 문장을 아래와 같이 바꿔주게 됩니다. 

메모리 0x152839 위치에서 부터 4 바이트의 공간에 있는 데이터를 10 으로 바꾸어라! 

결과적으로, 컴퓨터 내부에서는 올바르게 수행되겠지요. 
참고적으로 말하는 이야기 이지만 현재 (아마 이 블로그에 접속하는 사람들 중 99% 이상이) 많은 사람들은 32 비트 운영체제를 사용하고 있습니다. 이 32 비트에서 작동되는 컴퓨터들은 모두 주소값의 크기가 32 비트 (즉, 4 바이트.. 까먹었다면 2-3 강 참조) 로 나타내집니다. 즉 주소값이 0x00000000 ~ 0xFFFFFFFF 까지의 값을 가진다는 것이지요. 어랏! 조금 똑똑하신 분들이라면 32 비트로 사용할 수 있는 주소값의 가지수는 2 의 32 승 바이트, 즉 RAM 은 최대 4 GB 까지 밖에 사용할 수 없다는 사실을 알 수 있습니다. 맞습니다. 이 때문에 32 비트 운영체제에서는 RAM 의 최대 크기가 4 GB 로 제한되지요(즉, 돈을 마이 들여서 RAM 10GB 로 만들어도 컴퓨터는 4 GB 까지 밖에 인식하지 못합니다. 어찌 이렇게 슬플수가..) 1

여기까지는 상당히 직관적이고 단순해서 이해하기 쉬웠을 것 입니다. 그런데 C 를 만든 사람은 아주 유용하면서도 골때리는 것을 하나 새롭게 만들었습니다. 바로 '포인터(pointer)' 입니다. 영어를 잘하는 분들은 이미 아시겠지만 '포인터' 라는 단어의 뜻이 '가리키는 것(가르켜지는 대상체를 말하는 것이 아닙니다)' 이란 의미를 가지고 있습니다. 

사실, 포인터는 우리가 앞에서 보았던 int 나 char 변수들과 다른 것이 전혀 아닙니다. 포인터도 '변수' 입니다. int 형 변수가 정수 데이터, float 형 변수가 실수 데이터를 보관했던 것 처럼, 포인터도 특정한 데이터를 보관하는 '변수' 입니다. 그렇다면 포인터는 무엇을 보관하고 있을 까요? 

바로, 특정한 데이터가 저장된 주소값을 보관하는 변수 입니다. 여기서 강조할 부분은 '주소값' 이라는 것 이지요. 여기서 그냥 머리에 박아 넣어 버립시다. 이전에 다른 책들에서 배운 내용을 싹 다 잊어 버리고 그냥 망치로 때려 넣듯이 박아버려요. 포인터에는 특정한 데이터가 저장된 주소값을 보관하는 변수 라고 말이지요. 크게 외치세요. '주소값!!!!!' 

암튼, 뇌가 완전히 세뇌되었다고 생각하면 다음 단계로 넘어가도록 하겠습니다. 아직도 이상한 잡념이 머리에 남아 있다면 크게 숨을 호흡하시고 주소값이라고 10 번만 외쳐 보세요.. 

자. 되었습니다. 이제 포인터의 세계로 출발해 봅시다. 뿅

  포인터
 

다시 한 번 정리하자면

"포인터" : 메모리 상에 위치한 특정한 데이터의 (시작)주소값을 보관하는 변수!

우리가 변수를 정의할 때 int 나 char 처럼 여러가지 형(type) 들이 있었습니다. 그런데 놀랍게도 포인터에서도 형이 있습니다. 이 말은 포인터가 메모리 상의 int 형 데이타의 주소값을 저장하는 포인터와, char 형 데이터의 주소값을 저장하는 포인터가 서로 다르다는 말입니다. 응?? 여러분의 머리속에는 아래와 같은 생각이 번개 처럼 스쳐 지나갈 것입니다. 

"야 이 Psi 같은 놈아. 아까 포인터는 주소값을 저장하는 거래며. 근데 우리가 쓰는 컴퓨터에선 주소값이 무조건 32 비트, 즉 4 바이트래며!! 그러면 포인터의 크기는 다 똑같은것 아냐? 근데 왜 포인터가 형(type)을 가지는 건데!! 아아아아악" 

휴우우. 진정좀 하시고. 여러분 말이 백번 맞습니다 - 단, 현재 까지 배운 내용을 가지고 생각하자면 말이지요. 포인터를 아주 조금만 배우면 왜 포인터에 형(type) 이 필요한지 알게 될 것입니다. 

C 언어에서 포인터는 다음과 같이 정의할 수 있습니다. 

(포인터에 주소값이 저장되는 데이터의 형) *(포인터의 이름);

혹은 아래와 같이 정의할 수 도 있습니다.

(포인터에 주소값이 저장되는 데이터의 형)* (포인터의 이름);

  예를 들어 p 라는 포인터가 int 데이터를 가리키고 싶다고 하면 ...

int *p; // 라고 하거나
int* p; // 로 하면 된다

라 하면 올바르게 됩니다. 즉 위 포인터 p 는 int 형 데이터의 주소값을 저장하는 변수가 되는 것 입니다. 와우! 

  & 연산자
 

그런데 말입니다. 아직도 2% 부족합니다. 포인터를 정의하였으면 값을 집어 넣어야 하는데, 도대체 우리가 데이터의 주소값을 어떻게 아냐는 말입니까? 하지만, 여러분의 이러한 욕구를 충족시키는 연산자가 C 에 (당연히) 있습니다. 바로 & 연산자 이지요. 

그런데, 아마 복습을 철저하게 잘하신 분들은 당황할 수 도 있습니다. 왜냐하면 & 가 AND 연산자이기 때문입니다. (4 강 참조) 그런데, & 연산자는 두 개의 피연산자를 필요로 했습니다. 즉,

a & b; //o.k
a & // NOT ok

와 같이 언제나 2 개가 필요 하다는 것이지요. 그런데, 여기에서 소개할 & 연산자는 오직 피연산자가 1 개인 연산자 입니다. (이러한 연산자를 단항(unary) 연산자라 합니다) 즉, 위의 AND 연산자와 완전히 다르 다는 것이지요. 이는 데이터의 주소값을 불러 옵니다. 사용은 그냥 아래와 같은 꼴로 사용해 주면 됩니다.

& (주소값을 계산할 데이터)

백설(說)이 불여일행(行). 한 번 프로그램을 짜 봅시다. 

/* & 연산자 */
#include <stdio.h>
int main()
{
    int a;
    a = 2;

    printf("%x \n", &a); 
    return 0;
}

성공적으로 컴파일 했다면


와 같이 나옵니다. 참고로, 여러분의 컴퓨터에 따라 결과가 다르게 나올 수 도 있습니다. 사실, 저와 정말 인연 이상의 무언가가 있지 않는 이상 전혀 다르게 나올 것 입니다. 더 놀라운 것은 실행할 때 마다 결과가 달라질 것입니다. 

2 번째 실행한 것


위와 같이 나오는 이유는 나중에 설명하겠지만 주목할 것은 '값' 이 출력되었다는 것 입니다. 

    printf("%x \n", &a);

위 문장에서 &a 의 값을 16 진수 (%x) 로 출력하라고 명령하였습니다. 근데요. 눈치가 있는 사람이라면 금방 알겠지만 위에서 출력된 결과는 4 바이트(16 진수로 8 자리)가 아닙니다! (여러분의 컴퓨터는 다를 수 있습니다. 아무튼..) 하지만 저는 32 비트 운영체제를 사용하고 있습니다. 그렇다면 뭐가 문제인가요? 사실, 문제는 없습니다. 단순히 앞의 0 이 잘린 것 이지요. 주소값은 언제나 4 바이트 크기, 즉 16 진수로 8 자리 인데 앞에 0 이 잘려서 출력이 안된 것일 뿐입니다. 따라서 변수 a 의 주소는 아마도 0x001EF8D4 가 될 것입니다. 

 아무튼 위 결과를 보면, 적어도 제 컴퓨터 상에선 int 변수 a 는 메모리 상에서 0x001EF8D4 를 시작으로 4 바이트의 공간을 차지하고 있었다는 사실을 알 수 있습니다. 

 자, 이제 & 연산자를 사용하여 특정한 데이터의 메모리 상의 주소값을 알 수 있다는 사실을 알았으니 배고픈 포인터에게 값을 넣어 봅시다. 

/* 포인터의 시작 */
#include <stdio.h>
int main()
{
    int *p;
    int a;

    p = &a;

    printf("포인터 p 에 들어 있는 값 : %x \n", p);
    printf("int 변수 a 가 저장된 주소 : %x \n", &a);

    return 0;
}

실행해 보면 많은 이들이 예상했던 것 처럼.... 


똑같이 나옵니다. 어찌 보면 당연한 일입니다. 

    p = &a;

에서 포인터 p 에 a 의 주소를 대입하였기 때문이죠. 참고로, 한 번 정의된 변수의 주소값은 바뀌지 않습니다. 따라서 아래 printf 에서 포인터 p 에 저장된 값과 변수 a 의 주소값이 동일하게 나오게 됩니다. 어때요. 쉽죠? 

  * 표  
 

  이제, 드디어 포인터의 핵심에 다다랐습니다. 현재 까지 우리가 배운 바로는 "포인터는 특정한 데이터의 주소값을 보관한다. 이 때 포인터는 주소값을 보관하는 데이터의 형에 * 를 붙임으로써 정의되고, & 연산자로 특정한 데이터의 메모리 상의 주소값을 알아올 수 있다" 까지 알고 있습니다. 참고로 아래에서 설명하는 * 는 앞서 포인터를 정의할 때 사용하였던 * 와 다른 의미를 지닌 다는 사실을 알고 있으세요. 

  앞서 & 연산자가 2% 부족한 부분을 채워준다고 했지만 안타깝게도 1% 가 남았습니다. 하지만 * 연산자가 이 1% 를 채워질 것 입니다.... 잠깐만. * 연산자?? 어디서 많이 들어본 것 같네요.. 맞아요. * 연산자도 & 처럼 피연산자를 2 개 가질 때 에는 곱셈 연산자로 사용됩니다. 즉

a * b; // a 와 b 를 곱한다.
a *; // Not OK

하지만 이 연산자는 위 & 처럼 1 개의 피연산자를 가지는 단항 연산자 입니다. * 연산자는 쉽게 풀이하자면

 "나(포인터)를 나에게 저장된 주소값에 위치한 데이터라고 생각해!"

  라는 의미의 연산자 입니다. 한 번 아래 예제를 봅시다. 

/* * 연산자의 이용 */
#include <stdio.h>
int main()
{
    int *p;
    int a;

    p = &a;
    a = 2;

    printf("a 의 값 : %d \n", a);
    printf("*p 의 값 : %d \n", *p);

    return 0;
}

성공적으로 컴파일 한다면 


가 됩니다. 

    int *p;
    int a;

일단 int 데이터를 가리키는 포인터 p 와 int 변수 a 를 각각 정의하였습니다. 평범한 문장 이지요. 

    p = &a;
    a = 2;

그리고 포인터 p 에 a 의 주소를 집어 넣었습니다. 그리고 a 에 2 를 대입하였습니다. 

    printf("a 의 값 : %d \n", a);
    printf("*p 의 값 : %d \n", *p);

일단 위의 문장은 단순 합니다. a 의 값을 출력하란 말이지요. 당연하게도 2 가 출력됩니다. 그런데, 아래에서 *p 의 값을 출력하라고 했습니다. * 의 의미는 앞서, "나를 나에 저장된 주소값에 해당하는 데이터로 생각하시오!" 로 하게 하는 연산자라고 하였습니다. 즉, *p 를 통해 "p 에 저장된 주소(변수 a 의 주소)에 해당하는 데이타, 즉 변수 a" 를 의미할 수 있게 되었습니다. 다시 말해 *p 와 변수 a 는 정확히 동일합니다. 즉, 위 두 문장은 아래 두 문장과 10000% 일치합니다.

   printf("a 의 값 : %d \n", a);
    printf("*p 의 값 : %d \n", a);

마지막으로 * 와 관련된 예제 하나를 더 살펴 봅시다.

/* * 연산자 */
#include <stdio.h>
int main()
{
    int *p;
    int a;

    p = &a;
    *p = 3;

    printf("a 의 값 : %d \n", a);
    printf("*p 의 값 : %d \n", *p);

    return 0;
}

성공적으로 컴파일 하였다면


아마 많은 여러분들이 예상했던 결과 이길 바랍니다^^

    p = &a;
    *p = 3;

위에서도 마찬가지로 p 에 변수 a 의 주소를 집어 넣었습니다. 그리고 *p 를 통해 "나에 저장된 주소(변수 a 의 주소)에 해당하는 데이터(변수 a) 로 생각하시오" 를 의미하여 *p = 3 은 a = 3 과 동일한 의미를 지니게 되었습니다. 어때요. 간단하지요? 이로써 여러분은 포인터의 50% 이상을 이해하신 것 입니다~~! 짝짝짝짝

 자. 그럼 '포인터' 라는 말 자체의 의미를 생각해 봅시다. int 변수 a 와 포인터 p 의 메모리 상의 모습을 그리면 아래와 같습니다.

포인터 p 도, a 도 메모리 상에 각각 존재합니다만, 그 위치에는 a 의 경우 3 이 있지만 p 의 경우 a 의 주소값이 있습니다.

참고로 주소값은 제가 임의로 정한 것 입니다.


  즉, 포인터  p 는 * 를 통해 a 를 의미 할 수 (가리 킬 수) 있게 되었지요. 흔히 많은 책들은 포인터 p 가 변수 a 를 가리키고 있다 라고 말합니다. 사실 저는 이 말이 여러분에게 어렵게 다가올 까봐 여태까지 하고 있지 않았지만 아무튼, 포인터 p 에 어떤 변수 a 의 주소값이 저장되어 있다면 '포인터 p 는 변수 a 를 가리킨다' 라고 말합니다. 참고적으로 말하지만 포인터 또한 엄연한 변수 이기 때문에 특정한 메모리 공간을 차지합니다. 따라서 위 그림과 같이 포인터도 자기 자신만의 주소를 가지고 있지요. 

 이제 여러분들은 포인터에 왜 '형(type)' 이 필요한지 이야기 할 수 있는 단계가 되었습니다. (아마 여러분들 중 반 수 이상은 이미 짐작하고 계실 것 입니다.) 다시 말해 '형' 이 없다는 것은 포인터가 자신이 가리키고 있는 대상에 대해 어떠한 정보도 가지지 않아도 된다는 것 입니다. 여기서 포인터를 선언하기 위해 pointer 라는 저 만의 키워드를 이용했다고 합시다. (실제로 이런게 사용되는 것이 아닙니다;;;) 

int a;
pointer *p;
p = &a;
*p = 4;

자. 위 명령이 올바른 결과를 출력할까요? 포인터 p 에는 명백히 변수 a 의 '시작 주소' 가 들어 있습니다. '시작 주소' 란 말입니다. 즉, *p 라고 했을 때 포인터 p 에는 자신이 가리키는 대상의 시작 주소가 있지만 대상의 크기에 대한 정보가 없습니다. 헉! 컴퓨터는 *p 라고 했을 때 메모리에서 0x12345678 로 부터 (&a 가 0x12345678 이라 가정합시다) 몇 개의 바이트를 더 읽어 들어서
값을 변경해야 할 지 모른다는 말입니다. 결국 포인터는 쓸모 없게 됩니다. 

하지만, 우리가 여태까지 해왔던 것 처럼 

int a;
int *p;
p = &a;
*p = 4;

라고 한다면 어떨 까요? 컴퓨터는 0x12345678 로 부터 포인터 p 가 int * 라는 사실을 보고 "아하. 이 포인터는 int 데이터를 가리키는 구나!" 라고 알게 되어 정확히 4 바이트를 읽어 들어 값을 바꾸게 됩니다. 따라서 정확한 값이 출력될 수 있겠지요. 

여러분이 현재 까지 배운 내용에 대해 완벽하게 이해를 하고 있다면 아래와 같은 궁금증이 생길 것 입니다. 

"야. 그런데 말야. 주소값은 무조건 4 바이트 잖아? 그러면 int *p; 처럼 귀찮게 * 을 붙이지 말고 int p; 라고 한다음에 p = &a; 라고 해도 상관 없는거 아니야? 그지? 그지? 그지? " 

  훌륭한 생각입니다. 한 번 해봅시다. 

/* 될까? */
#include <stdio.h>
int main()
{
    int p;
    int a;

    p = &a;
    a = 3;

    printf("*p 의 값? : %d \n", *p);

    return 0;
}

안타깝게도 printf 부분에서 아래와 같은 오류가 출력됩니다.

error C2100: 간접 참조가 잘못되었습니다.

사실, 우리가 현재 까지 배운 내용을 바탕으로 이해해 보자면 위에서 왜 오류가 발생하는지 이해하기 힘듦니다. 그냥 단순히 * 연산자는 포인터들에게만 적용된다는 사실만을 알아 두시면 감사하겠습니다.

/* 포인터도 변수이다 */
#include <stdio.h>
int main()
{
    int a;
    int b;
    int *p;

    p = &a;
    *p = 2;
    p = &b;
    *p = 4;

    printf("a : %d \n", a);
    printf("b : %d \n", b);
    return 0;
}

성공적으로 컴파일 하였다면



    p = &a;
    *p = 2;
    p = &b;
    *p = 4;

  사실, 이런 예제까지 굳이 보여주어야 하나 하는 생각이 들었지만 그래도 혹시나 하는 마음에 했습니다. 앞에서도 말했듯이 포인터는 '변수' 입니다. 즉, 포인터에 들어간 주소값이 바뀔 수 있다는 것이지요. 위와 같이 처음에 a 를 가리켰다가, (즉 p 에 변수 a 의 주소값이 들어갔다가) 나중에 b 를 가리킬 수 (즉 p 에 변수 b 의 주소값이 들어감) 있다는 것 이지요. 뭐 특별히 중요한 예제는 아니였습니다만. 나중에 상수 포인터, 포인터 상수에 대해 이야기 하면서 다시 다루어 보도록 하겠습니다.

마지막으로, 강의를 마치며 여러분에게 포인터에 대해 완벽히 뇌리에 꽂힐 만한 동화를 들려드리겠습니다.

옛날 옛날에 .. 대략 2 년 전에 (뭐.. 전 여러분과 옛날의 정의가 다릅니다ㅋ) 변철수, 변수철, 포영희라는 세 명의 사람이 OO 아파트에 살고 있었습니다. 

int chul, sue; 
int *young;

그런데 말이죠. 포영희는 변철수를 너무나 좋아한 나머지 자기 집 대문 앞에 큰 글씨로 "우리집에 오는 것들은 모두 철수네 주세요" 라고 써 놓고 철수네 주소를 적어 놓았습니다

young = &chul;

어느날 택배 아저씨가 영희네 집에 물건을 배달하러 왔다가 영희의 메세지를 보고 철수네에 가져다 주게 됩니다.

*young = 3; // 사실 chul = 3 과 동일하다!

영희에 짝사랑이 계속 되다가 어느날 영희는 철수 보다 더 미남인 수철이를 보게 됩니다. 결국 영희는 마음이 변심하고 수철이를 좋아하기로 했죠. 영희는 자기 대문 앞에 있던 메세지를 떼 버리고 "우리집에 오는 것은 모두 수철이네 주세요." 라 쓰고 수철이네 주소를 적었습니다.

young = &sue;

며칠이 지나 택배 아저씨는 물건을 배달하러 영희네에 왔다가 메세지를 보고 이번엔 수철이네에 가져다 줍니다.

*young = 4; // 사실 sue = 4 와 동일하다

이렇게 순수한 사랑이 OO 아파트에서 모락 모락 피어났습니다..... 끝

return 0; // 종료를 나타내는 것인데, 아직 몰라도 되요. (정확히 말하면 리턴...) 

생각해 볼 문제 

* 와 & 연산자의 역할이 무엇인지 말해보세요 (난이도 : 下)

int **a; 와 같은 이중 포인터(double-pointer) 에 대해 생각해 보세요 (난이도 : 中上)


함수에서 외부의 값(실인수)을 전달받기 위해 사용되는 변수를  형식인수라한다. 이 형식인수에 외부값(실인수)이 전달되는 방식에 따라 값에 의한 호출과 참조에 의한 호출로 나뉘어 지는데, 값호출과 참조호출로 나뉜다. 일반적으로 실인수를 형식인수에 전달시 대입연산을 하는 경우와 마찬가지 동작이 일어나, 실인수의 값을 형식인수에 대입을 한다. 메모리상에선 실인수의 값을 복사한 후 형식인수에게 할당을 한다. 그 결과 같은 값을 지닌 변수가 서로 다른 메모리 공간에 2개 존재하게 된다. 이 경우 형식인수값을 조작하더라도 실인수의 값은 어떤 영향도 받지 않다. 이런 호출방식을 값호출이라고한다.


그럼 참조에 의한 호출은 어떤 것인까?

실인수의 값을 전달하는 것이 아닌 주소를 전달하는 것이다. 사실 내부적으로 보면 값호출이나 참조호출이나 같은 동작이 일어난다. 주소도 갓이므로 주소값이 형식인수에 복사가 된다. 그런데 주소값을 전달하기 위해서는 형식인수의 타입은 실인수타입의 포인터여야 한다. 즉 참조호출은 형식인수가 포인터기 때문에, 내부적으로는 실인수의 값을 알기위해서는 형식인수(포인터)에 의한 참조 방식으로 알아야 한다. 결국 이런 전달방식은 실인수에 직접 접근이 가능하다.


그럼 값호출과 참조호출의 차이점을 간단히 예를 들어보자


값에 의한 호출

위 코드의 동작후 결과 값은 3을 출력한다. 의도한바는 a의 값을 전달받아 3을 더 더해서 6이 되길 바라는 코드였다. 그런데 위에서 설명했듯이, 값에의한 호출은 값만을 형식인수에 복사하기 때문에, 실인수 a와는 아무런 상관없이 함수내부의 형식인수가 6의 값을 갖는다. 함수내부의 a라는 값은 지역변수기 때문에 함수가 리턴될 때 바로 소멸된다.


함수 내부에서 외부의 값을 다루기 위해선 포인터에의한 참조를 통해 외부값을 조작해야한다.


참조에 의한 호출

위 코드는 참조호출로 바꾼 코드다. 결과 값은 6을 출력한다.

단순히 주소값을 전달하여 내부적으로는 실인수에 접근하여 값을 조작했다. 함수내부에서 선언된 지역변수인 형식인수 a가 소멸되지만, 소멸과 관계없이 실인수 a의 값은 조작된 후다. 



그러면 이번엔 좀 더 깊게 생각해보자.


위 코드는 동적 메모리의 함수를 우해 래퍼함수인 n_call 함수를 만든 것이다. 포인터를 전달한후 동적할다을 하여 TEST라는 값을 넣었다. 그리고 실인수의 값을 출력하는 코드이다. 그런데 이상하게 제대로 동작하지 않는다.

분명 name도 포인터 변수고, name 가리키는 공간의 주소값을 넘겨주어서 그 공간에대한 메모리의 동적 할당을 하는 것인데 왜 안될까.. 이렇게 생각 될 수도 있겠다. 그러면 다음의 그림을 보자.


값에 의한 호출이 된 경우

좀 헷갈릴 수 있겠으나, 기본적인 동작은 같다. 실인수의 동적할당을 제대로 받기 위해선 실인수의 포인터를 전달해야한다. 위 그림은 형식인수가 동적할당은 받은 후 TEST를 입력받았다. 이 경우도 함수가 리턴된후 a의 값은소멸된다. 그리고 Name은 아무런 변화가 없다. 즉 값에의한 호출이다.

 단순히 Name의 값만 바꾸려 했다면 제대로 동작 했을 것이다. 이런일이 일어나는 이유는 함수내부에 있는 malloc이 포인터의 값을 변화시키는 함수기 때문이다. 때문에 포인터를 기준으로 생각해야한다.

실제로 원하는 동작을 만들기 위해선 형식인수로 &Name을 전달해야하며, 형식인수의 타입은 char **a 가되어야 한다. 그리고 함수 내부에서 형식인수는 '*'연산으로 실인수의 실제 값을 참조해야한다. 다음 그림을 참조하길바란다.


제대로 참조에 의한 호출이 된경우



2014/02/11 - [프로그래밍/C언어] - 이중포인터에 대한 이해 C언어


이경우 C언어의 문자열을 다루는 형식의 특성상 2중포인터를 사용하긴 했으나, 실제로, 다른 참조호출의 동작과 같은 것이다. 원리 또한 동일하다. 단지 위 경우는 기준점이 포인터 Name 이었기 때문이다.


데이터 무결성이란 데이터베이스에 들어 있는 데이터의 정확성을 보장하기 위해 데이터의 변경이나 수정시 제한을 두어 안정성을 저해하는 요소를 막아 데이터 상태들을 항상 옳게 유지하는 것을 의미한다.

 

도메인 무결성

컬럼에 대한 무결성을 보장하는 것으로써 해당 컬럼이 널을 허용하거나, 혹은 데이터타입이 적절하거나, 적정한 형식의 데이터가 저장되어있는지를 확인하는것으로써 체크(Check), 디폴트(Default), 룰(Rule) 등의 제약(Constraints)들로 이러한 도메인 무결성을 보장할 수 있다.

 

주민등록번호 컬럼에 틀린 형식의 데이터가 들어가는 경우, '남'|'여'로 입력되는 성별 컬럼에 '남자', 'Male' 등의 데이터가 들어가는 것도 도메인 무결성을 위반하는 것이다.

 

개체 무결성

테이블에 저장되어 있는 컬럼의 값이 동일 개체, 즉 테이블에 저장되어 있는 다른 행의 컬럼값과 비교했을때 중복되지 않아야 하는 경우 이러한 무결성을 지키도록 하는 것이 개체 무결성이다.

사용자 테이블(TB_Member) 과 같이 모든 사용자가 구분된다는 전제 조건이 있는 테이블이라면 동일한 아이디나 주민등록번호가 저장될 경우 개체 무결성을 위한한 것이다.

이러한 개체 무결성을 지원하는 구문은 프라이머리키 제약조건(Primary key constraints)이나 유니크(Unique constraints) 등을 통해 구현할 수 있다.

 

참조 무결성

개체와 개체간에 참조관계가 있을 대 이 두 개체간에 데이터가 일관성을 가질 수 있도록 보증하는 방법을 참조 무결성이라 한다.

일반적으로 외부키(Foreign key)로 제약을 주어 무결성을 보장할 수 있고 트리거를 통해서 데이터의 변경이 있을 때 관련 값들을 재조정할 수 있다.

 

'프로그램 > 데이터베이스' 카테고리의 다른 글

ER 다이어그램 (Entity-Relationship Diagram)  (0) 2017.09.19
[oracle] 토드팁 - sql자동생성  (0) 2016.12.30
DB 기본 용어 (SQL, DDL, DML, DCL, TCL)  (0) 2016.11.28
클라이언트/서버  (0) 2016.11.28
Osi 7 layer  (0) 2016.11.28

Mysql을 처음 설치할 때 database root 계정으로 사용할 password를 설정한다. 하지만 시간이 오래 지나서 그때 설정한 password를 기억할 수 없다면 다음의 방법으로 재설정할 수 있다. (Ubuntu 12.04 기준)

Step 1. 실행중인 mysql service를 중지 시킨다.

Step 2. Password를 검사하지 않도록 mysql 환경설정 파일을 수정한다.
: /etc/mysql/my.conf file에 skip-grant-tables를 추가하면 password를 검사하지 않는다.

Step 3. 새로운 설정 값으로 mysql service를 실행한다.

Step 4. root 계정으로 mysql database를 연다.

Step 5. root password를 재설정한다.

Step 6. my.conf를 복원하고 mysql service를 재실행 시킨다. 

Database를 제어하는 명령어 중

확인 : show databases;

생성 : create database kkk;

삭제 : drop database kkk;


*desc  테이블 구조 볼때


1. 사용자 추가


1-1. mysql.user(주의할 점 시스템 반영 -> flush privileges;)

       -> insert into user (host, user, password)

                       values( 'localhost, 'kadmin', password('1111'));

1-2. create user kadmin@localhost identified by '1111';

*사용자 계정 삭제

DROP USER [user명]@[server명];
ex) drop user user1@localhost;


2. 특정 database를 특정 user가 사용할 수 있게 권한 부여


2-1. mysql.db

 -> insert into db values ('localhost','kkk','kadmin','y','y','y','y','y','y','y','y','y','y','y','y','y','y','y','y','y','y','y');

2-2 grant all on kkk.* to kadmin@localhost;



3. 마지막…….한 개인데요. 귀찮은거죠 한 줄로 끝내고 싶습니다.

     grant all on skworld.* to sksk@localhost identified by ‘ssss’;

MySQL에 root로 접속 한 뒤
use mysql;
select host,user from user;
현재 생성된 db의 사용자 계정들을 확인할 수 있다.

사용자 계정 생성
GRANT USAGE ON [database명].[table명] TO [user명]@[server명] IDENTIFIED BY [‘패스워드’];
ex) grant usage on database.* to user1@localhost identified by ‘user1’;

생성된 사용자 계정 권한 설정
GRANT ALL ON [database명].[table명] TO [user명]@[server명];    =>  모든 권한을 준다
GRANT SELECT,INSERT,UPDATE,DELETE,CREATE,DROP,INDEX,ALTER ON [database명].[tabel명] TO [user명]@[server명];   => 특정 권한을 준다
ex) grant all on database.* to user1@localhost;
grant select,insert,update,delete,create,drop.index,alter on database.* to user1@localhost;

REVOKE ALL ON [database명].[table명] FROM [user명]@[server명];    =>  모든 권한을 삭제한다
REVOKE DROP ON [database명].[table명] FROM [user명]@[server명];    => 특정 권한(drop)을 삭제한다
ex) revoke all on database.* from user1@localhost;
revoke drop,index on database.* from user1@localhost;

계정 권한을 새로 로드
FLUSH PRIVILEGES;
flush privileges;

사용자 계정 삭제
DROP USER [user명]@[server명];
ex) drop user user1@localhost;


오늘은 mysql 설치시 사용자를 추가하는 방법에 대해 남겨볼까 한다. 할때마다 잘 기억이 안나서 검색엔진에 항상 의존을 하게 되는데 다음에는 검색엔진에 의존하지 않고 바로 블로그에서 검색해서 보면 좋을 듯 하다. 


사용자를 생성하는 방법은 여러가지가 있는데 아래 방법이 가장 쉽고 심플하다. 다른 방법들은 쿼리문을 직접 던져야 하기 때문에 솔직히 말해서 좀 불편하다. 혹시나 오랜만에 해 보거나 처음해 보는 사람들을 위해서 서버에 mysql을 설치한 후 mysql에 로그인한 후 아래 명령를 실행시켜준다.


-  mysql 서버 로그인하기

 $ mysql -uroot -prootpassword mysql


- 다른 PC에서 mysql 서버로 접속을 하기 위한 사용자 추가

 mysql> create user 'userId'@'%' identified by 'userpassword';


- 위 사용자에게 모든 것을 할 수 있는 권한 주기

mysql> grant all privileges on *.* to 'userid'@'%';


- 위 사용자에게 특정 DB를 관리할수 있는 권한 주기

 mysql> grant all privileges on dbname.* to 'userid'@'%';


- 로컬PC에서 mysql로 접속하기 위한 사용자 추가

 mysql> create user 'userId'@'localhost' identified by 'userpassword';


- 위 사용자에게 모든 것을 할 수 있는 권한 주기

 mysql> grant all privileges on *.* to 'userid'@'localhost';


- 위 사용자에게 특정 DB를 관리할 수 있는 권한 주기

 mysql> grant all privileges on dbname.* to 'userid'@'localhost';



대부분 사용자를 생성하고 권한을 줄때는 특정 DB를 관리하는 계정을 따로 만들기 위해서이다. 그러므로 사용자를 생성하고 권한을 줄때는 특정 DB를 관리할 수 있는 권한만 주면 된다.

+ Recent posts