2013년 12월 30일 월요일

java concurrency in practice ch3

Chapter 3.Sharing Objects

동기화는 write 만 생각하기 쉬운대 사용하는 모든 쓰레드들이 해당 데이터가 변경되었을때
해당 데이터를 볼수 있는 visibility 까지 고려해야 한다.

3.1 Visibility

there is no guarantee that the reading thread will see a value written by another thread on timely basis, or even at all. in order to ensure visibility of memory writes across threads, you must use synchronization

정확한 시간에 A 쓰레드가 쓴 값을 다른 쓰레드가 읽는것은 보장되지 않는다.
쓰레드 사이에서 메모리에 적은게 보이는걸 보장하려면 반드시 synchronization 을 사용해야한다.

public class NoVisibility {
 private static boolean ready;
 private static int number;

 private static class ReaderThread extends Thread {
  public void run() {
   while (!ready)
    Thread.yield();
   System.out.println(number);
  }
 }

 public static void main(String[] args) {
  new ReaderThread().start();
  number = 42;
  ready = true;
 }
}
1
해당  예제는 리더쓰레드에서 ready 가 되면 번호를 찍는 예제이다.
문제는 메인쓰레드에서 값을 변경한다고 해서 리더 쓰레드가 변경된 값을 본다는게
보장되지 않는다.

즉 ready 같을 보지 못해 영원이 끝나지 않거나 숫자를찍을대 0 을찍을수 있다

즉 리오더링 때문에 ready 는 보았지만 넘버는 보지 못하는경우도 있다

In the absence of synchronization, the compiler, processor, and runtime can do some downright weird things to the order in which operation appear to execute, Attempts to reason about the order in which memory actions "must" happen in insufficiently synchronized multithreaded programs will almost certainly be incorrect

동기화 의 부제는 컴파일러, 프로세서 그리고 런타입에서 operation 을 실행 하는 순서를
이상하게 할 수 있다 이러한 시도는 동기화 하지 않으면 반드시 나타나게 되고 그러므로
동기화 하지않은 멀티 쓰레드 프로그램은 대부분 정확하지 않다.

피하는 방법은? 멀티 쓰레드에서 공유 변수에 접근할때는 동기화를 잘하자......어.. 그래..- -;

3.1.1 Stale Data
동기화 하지 않는다면 stale 값을 보게 된다 문제는 어떤 변수는 최신값을 또 어떤 변수는
stale 값을 볼수도 있다는거다


@NotThreadSafe
public class MutableInteger {
 private int value;

 public int get() {
  return value;
 }

 public void set(int value) {
  this.value = value;
 }
}

@ThreadSafe
public class SynchronizedInteger {
 @GuardedBy("this")
 private int value;

 public synchronized int get() {
  return value;
 }

 public synchronized void set(int value) {
  this.value = value;
 }
}


위에 꺼는 stale 데이터이다
아래꺼는 thread safe 이다 get, set 동기화를 걸었기때문에
visibility 를 보장하기 위해서 set 에만 동기화를 걸면안된도
왜냐하면 get 할때 stale 변수를 볼 수 있기 때문이다...

3.1.2 Non-atomic 64 bit Operation
쓰레드가 읽는 값은 랜덤 밸류가 아니라 다른 쓰레드가 변경한 값이다(out-of-thin-air)

out-of-thin-air safety applies to all variables, with one exception: 64-bit numeric variables

out of thin air 세이프티는 64비트 숫자형 변수를 제외한 모든 자료형에 적용된다.
volatile 키워드를 선언하지 않는이상 jvm 은 long 이나 double 을 2번의 32-bit operation 으로 처리한다. (즉 랜덤값을 읽을 수 있다)

3.1.3. Locking and Visibility

everything A did in or prior to a synchronized block is visible to B when it execute synchronized block guard by the same lock

A가 싱크로 블락 안에 또는 전에 처리한 모든 값들이 비가 싱크로 블락에 들어간 후 (락 획득) 후에는 변경된 값을 보는게 보장된다 (물런 같은 락의로 보호되는 부분)



A 가 를 y 변경하고 락을 획득 x를 변경하고 락을 품
B 가 락을 획득 이때 y, x 는 (y 락이전에 변경, x 락안에서 변경) A의에 의해 변경된 값임이
보장된다.

Locking is not just about mutual exclusion, it is also about memory visibility. To ensure that all threads see the most-up-to-date values of shard mutable variables, the reading and writing threads must synchronized on a common lock

락은 상호배제 뿐만이 아니라 메모리 보임 과도 관련이 있다
락은 모든 쓰레드가 변경가능한 변수들의 최신값을 보는것을 보장해 준다.
(같은 락에 의해서 보호 되는 애들만)

3.1.4 Volatile variables
약한 폼의 동기화 라고 생각하면된다.
만약 volatile 이라고 선언하면 변경된 값들이 예측 가능하게 다른 쓰레들에게 전파되는것을보장한다.

만약 volatile 이라고 선언하면 컴파일러나 런타임은 해당 변수는 공유 변수이기때문에
리오더링이나 캐싱하지 않는다.  즉 volatile 변수를 읽은면 가장 최신에 변경된 값이 리턴된다

A good way of think about volatile variables is to imagine that they behave roughly like the SynchronizdInteger class

volatile 변수는 SynchronizedInteger class 와 비슷 하게 생각해도 된다.
하지만 volatile 변수는 락을 사용하지 않기때문에 쓰레드가 블락되지 안는다.

(성능은 락 보다는 싸고 일반 변수보다는 조금 비싸다)

When thread A write to a volatile variables and subsequently thread B reads that same variable, the values of all variables that were visible to A prior to writing to the volatile variables become visible to B after reading volatile variable is like entering a synchronized block

A가 쓰고 바로 B가 읽는다고 해보자
 A가 volatile 변수를 쓰기 전에 A에게 보이던 모든 변수들은
B가 volatile 변수를 읽은 다음에는 B에게 보여진다.
즉 싱크 블락에서 락을 획득하는 것과 같다.

volatile 변수는 간단하게 구현하거나 동기화 정책을 확인 할때만 사용하자
(visibility  를 보장하기 위해 쓰지말자 - 남이 알아보기 힘듬)
state을 보는걸 확신 하기 위해 사용하자(즉 확인 용이지 락이나 메모리 보임을 보장하기
위한 용도로 쓰지 말자)


volatile boolean asleep;
...
   while(!asleep)
         countSomeSheep();
1

1.만약 volatile 로 선언안한다면 다른쓰레드에서 변경할때 못알아 볼 수 있다
2.물론 락으로 구현될 수 있지만 코드가 지저분해 질수도 있다

--개발 할때 반드시 jvm 옵션으로 -server를 주자 server 옵션을 줄경우 더욱 최적화 하기 때문에 클라 jvm 에서 잘 돌던게 (최적화 되지 않아서 변수 캐쉬, 리오더등) 서버에 올라가면 동작 안하는 경우가 있다. 


the most common use for volatile variables is a completion, interruption, or status flag

*count++ 같은 경우 volatile 로 선언해 두어도 read-modify-write 를 atomic 으로 하기에는 부족하다, 만약 니가 하나의 쓰레드에서만 적는다는걸 보장할수 없다면!

locking can guarantee both visibility and atomicity; volatile variable can only guarantee visibility 

락킹은 원자성과 메모리 보임을 보장하지만 volatile 변수는 메모리 보임만 보장한다.

아래의 모든 조건이 맡을 때문 volatile 변수를 사용 할 수 있다
1.write to the variable do not depend on its current value, or you can ensure that only a single thread ever updates the value;

2 the variable dose not participate in invariants with other state variables and
3 locking is not required for any other reason while the variable is being accessed;

1.변수가 이전의 값에 의존하지 않을때 또는 하나의 쓰레드에서만 변경한다는게 보장될때
2.변수가 다른 불변성을 구성하는 상태변수에 참여하지 않고
3.변수에 접근할때 어떤이유로든  락이 보장되지 않아도 될때

사용할수 있다 .


3.2 Publication and Escape

Escape?
An object that is published where it should not have been is said to have escaped
오브젝트가 아직 준비가 되지 않아 퍼블리시 되길 원하지 않을때 퍼블리싱 되는걸 escape 라고 한다.

예제
public static Set knownSecrets;

 public void initialize() {
  knownSecrets = new HashSet();
 }

publishing one object may indirectly publish other. if you add a secret to the published knowSercets set , you've
also published that secret

오브젝트를 배포하는것 간접적으로 다른 오브젝트도 배포 할 수 있다 만약 knowSercets set 에 새 secret 를 추가하면
secret 도 배포된것이다. 비슷하게 non-private 메서드에서 오브젝트를 돌려주는것도 같다.

public class UnsafeStates {
 private String[] states = new String[] { "AK", "AL" };

 public String[] getStates() {
  return states;
 }
}


위와 같은 상황에서는 어떤 콜러도 배열의 값을 변경 할 수 있으로 해당 상태 배열은 의도된 scope 에서 escaped 되었다고 할 수 있다

public class ThisEscape {
 public ThisEscape(EventSource source) {
  source.registerListener(new EventListener() {
   public void onEvent(Event e) {
    doSomething(e);
   }
  });
 ..... do a lot of things
 initSomeValue()
 }
}

위와 같은 상황에서는 this 가 같이 퍼블리시 되었다 문제는 ThisEscape 가 아직 완전이
컨스트럭트 된게 아니다.

예를 들어보자 source에 이벤트 리스트 인스턴트 등록한다.
이제 외부에서 이벤트 발행하면 해당 이벤트
리스너가 듣게 되는대 문제는 이 이벤트 리스너가 컨스트럭터의 맨 아래 부분의 initSomeValue()에서 set 하는 변수를 사용한다고 하면
ThisEscpe 가 do a lot of thing 를 하는 동안 실행 될 수 있다

즉 컨스트럭터에서 외부에서 완전이 만들어지지 않은 this 를 사용 할 수 있게 배포한것이다.

(쓰레드를 컨스트럭터에서 실행 할때 위 와 같은 상황이 많이 발생된다.)


public class SafeListener {
 private final EventListener listener;

 private SafeListener() {
  listener = new EventListener() {
   public void onEvent(Event e) {
    doSomething(e);
   }
  };
 }

 public static SafeListener newInstance(EventSource source) {
  SafeListener safe = new SafeListener();
  source.registerListener(safe.listener);
  return safe;
 }
}
1
만약 생성자에서 쓰레드를 생성한 후 시작 시키거나 이너 클래스를 등록하고 싶으면 위와 같은 방법으로 공개적인 init or start 메서드를 만들고 private 팩토리 메서드에서 쓰레드를 생성한 후 시작할 수 있게 하자
이렇가 한다면 생성되다 만 오브젝트가 배포 되지 않는다

ThisEscape illustrates an important special case of escape when the this references escapes during construction.
When the inner EventListener instance is published, so is the enclosing ThisEscape instance. But an object is in a
predictable,consistent state only after its constructor returns,so publishing an object from with in its constructor can
publish an incompletely constructed object.This is true even if the publication is the last statement in the constructor.If
the this reference escapes during construction,the object is considered not properly constructed.[8]
[8]More specifically,the this reference should not escape from the thread until after the constructor returns.The this reference can be stored
somewhere by the constructor as long as it is not used by another thread until after construction.Safe Listener in Listing 3.8 uses this technique.
Do not allow the this reference to escape during construction.

A common mistake that can let the this reference escape during construction is to start thread from a constructor.
When an object create a thread from its constructor,it almost always shares its this reference with the new thread,
either explicitly(by passing it to the constructor)or implicitly(because the Thread or Runnable is an inner class of the
owning object).

The new thread might then be able to see the owning object before it is fully constructed. There's
nothing wrong with creating a thread in a constructor,but it is best not to start the thread immediately.Instead,expose
a start or initialize method that starts the owned thread. (See Chapter 7 for more on service lifecycle issues.)
Calling an overrideable instance method(one that is neither private nor final) from the constructor can also allow the
this reference to escape.
If you are tempted to register an event listener or start a thread from a constructor, you can avoid the improper
construction by using a private constructor and a public factory method,as shown in SafeListener in Listing 3.8.


3.3 Thread Confinement
3.4.1 Final fields
파이널 필드를 사용하면 initialization safety 를 보장 할 수 있다

Just as it is a good practice to make all fields private unless they need greater visibility
it is a good practice to make all fields final unless they need to be mutable

공게될 필요가 없으면  private 을 변경될 필요가 없으면  final 을 사용하는건 좋은 코딩 습관이다

하나라도 mutable variables 를 줄이는게 많은 것보다 관리하기 훨씬 용이하다.

3.4.2 Example: Using Volatile to Publish Immutable Object
immutable object can sometimes provide a weak form of atomicity
불변 오브젝트는 약한 원자성을 제공 할 수 도 있다

Whenever a group of related data item must be acted on atomically, consider creating an immutable holder class for them

언제든 여러개의 액션이 원자적으로 이루어 져야 한다면 불변 홀더 클래스를 만드는걸 생각해보자자

with an immutable one, once a thread acquires a reference to it, it need never worry about another thread modifying its state. if the variables are to be updated, a new holder object is created, but any threads working with the previous holder still see it in a consistent state

불변 홀더를 사용한다고 해보자 만약 쓰레드가 해당 홀더의 레퍼런스를 같는다면
다른 쓰레드가 홀더의 상태를 변경하는걸 걱정하지 않아도 된다

왜냐하면 상태를 변경하기 위해서는 홀더 자체를 새로 생성 해야 하기 때문이다
또한 생성한다고 해도 이전홀더를 보고 있던해들은 이전 홀더를 보기 때문에 일관된 상태를
볼수 있다( 원자적으로 변경되어야 되는 변수들이 동시에 움직인다.)



@Immutable
class OneValueCache {
 private final BigInteger lastNumber;
 private final BigInteger[] lastFactors;

 public OneValueCache(BigInteger i, BigInteger[] factors) {
  lastNumber = i;
  lastFactors = Arrays.copyOf(factors, factors.length);
 }

 public BigInteger[] getFactors(BigInteger i) {
        if (lastNumber == null || !lastNumber.equals(i))
}
}

@ThreadSafe
public class VolatileCachedFactorizer implements Servlet {
    private volatile OneValueCache cache =
        new OneValueCache(null, null);
    public void service(ServletRequest req, ServletResponse resp) {
        BigInteger i = extractFromRequest(req);
        BigInteger[] factors = cache.getFactors(i);
        if (factors == null) {
            factors = factor(i);
            cache = new OneValueCache(i, factors);
        }
        encodeIntoResponse(resp, factors);
    }
}

// Unsafe publication
public Holder holder;
public void initialize() {
    holder = new Holder(42);
}
1.volatile 이기 때문에 변경 될 경우 다른 쓰레드에서 볼수 있다
2.copy를 사용했기 때문에 내부 상태를 변경 할 수 없다.
3.코드를 로직에서 한번만 참조하기 때문에 문제가 생길일이 없다.

즉 락을 걸지 않아도 thread-safe 하게 할 수 있다

맨아래의 예제는 컨스트럭션은 잘되었지만 volatile 이아니기 때문에 문제가 발생된다.


public class Holder {
    private int n;
    public Holder(int n) { this.n = n; }
    public void assertSanity() {
        if (n != n)
} }

위와 같이 된다면 다른 쓰레드에서 부를때 assertSanity 가 실패 할수 있다
왜냐하면 Object class 생성자에서 일단 모든 필드에 디펄트값을 넣는다.
그후 서브클래스 생성자가 실행 되기 때문이다.

즉 쓰레드가 처음에는 0 값을 보고 그다음에 서브 생성자에서 넣은 최신값을 본다면
assertSanity 가 실패 할 수 있다.

3.5.2 Immutable Objects and Initialization Safety

Immutable objects can be used safely by any thread without additional synchronization, even when synchronization is not used to publish them

불변 오프젝트는 추가적인 동기와작업 없이 tread safe 일수 있다 심지어 publish 가 적절이
이루어 지지 않았어도

불변 오브젝트가 thread safety 를 보장 받기 위해서는
unmodifiable state, all fields are final , proper construction
변경 불가능 상태, 모든 변수가 파이널, 적절한 생성자 인대

생성자가 적절하지 않아도 보장 받을때도 있단다.

그냥 다 잘 쓰자
물런 파이널 필드가 mutable  오브젝트를 가지고 있으면 동기화에 신경써야 한다.
(예를 들어 copy 를 쓰던가 등의)

3.5.5. Safe Publication Idioms
이번에는 퍼블리싱 된 후 참조하는 쓰레드에서 변경된 값을 바로 바로 보는것에 집중해 보자

To publish an object safely, both the reference to the object and the object's state must be visible to other threads at the same time. A properly constructed object can be safely published by:
1. Initializing an object reference from a static initializer;
2. Storing a reference to it into a volatile field or AtomicReferecne
3. Storing a reference to it into a final field of properly constructed object or
4. Storing a reference to it into a field that is properly guarded by a lock

오브젝트를 안전하게 퍼블리싱하기 위해서는, 오브젝트의 참조 포인터 그리고 그 오브젝트의 상태가 다른 모든 쓰레들에게 동시에 보여지는걸 보장해야 한다.
적절하게 생성된 오브젝트는 안전하게 배포될수 있다
아래 중 하나를 만족시키면된다.
1. 오브젝트의 초기화를 static initializer 에서 한다.(필드를 스테틱으로 선언)
2. 레퍼런스를 volatile 또는 AtomicReference 에 저장 한다.
3. 적절하게 생성된 오브젝트를 final 필드에 저장하거나
4. 락으로 방어되는 필드에 저장한다.

thread safe collection 은 아래와 같은 걸 보장한다.
1.HashTable, sychronizedMap, or Concurrent-Map 에 값이나 벨류를 넣는것은
적절하게 퍼블리시 되고 모든 쓰레드가 그 맵에서 안전하게 볼수 있다
2.vactor, copyOnWrtierArrayList, Copy-OnWrite-ArraySet, SynchronizeList,SynchronizedSet 에 엘리먼트를 넣고 보는건 모두 안전하다.
3. BlockingQueue or a ConcurrentLinkedQueue 에 서 엘리먼트를 넣고 보는건 안전하다.

static 변수에서 초기화하는건 언제나 안전하다.
public static Holder holder = new Holder(42);

static initializer are executed by the JVM at class initialization time, because of internal synchronization in the JVM

3.5.4 effectively Immutable Object
 mutable object 이지만 생성된 후 변경되지 않는걸 로직상에서 보장 할 수 있는걸 effectively Immutable Object 라고 하며 당연이 추가적인 동기화 작업이 필요하지 않다.

3.5.5 Mutable objects
만약   mutable object 라면 safe publication 은 단지  배포 되었을때의 상태 값만을 보장 한다.  그 이후 object 에 접근하는 모든 작업은 락에 의해 보호 되거나 thread safe 임을 보장 할수 있어야한다.

3.5.6 Sharing Objects Safely
만약 니가 object 레퍼런스를 얻는다면 그것같다 읽기 를 할건지 쓰기를 할건지 알고 있어야한다.
그리고 오브젝트가 어떻게 접근되어야 하는지 잘 적혀야 한다.

병렬프로그래밍에서 공유 오브젝트를 사용하는 가장 좋은 정책
1.thread-confied 하나의 쓰레드에 갇혀 있게 사용
2.shared read only 말그대로 읽기만함
3.shared thread-safe 내부에서 동기화 하기 때문에 여러 쓰레드가 추가의 동기화 코드 없이 자유롭게 사용
4.Guarded 락에의해 보호 되서 해당 쓰레드에 접근하기 위해서는 락을 소유해야함


2013년 12월 29일 일요일

3_3_글로벌_SEQ생성예제

REPLACE INTO 문 사용
*주의사항


*myIsam 으로 하고 많이 사용된다면 시퀀스당 하나씩 테이블을 설정해라
 innodb를 쓸경우 잠금때문에 성능 저하가 발생 할 수 있다.
  why
   하나의 클라이언트가 번호를 가져갈며 트랜 잭션을 걸면 다른 클라이언트가 그 트랜잭션이 끝날때까지 대기 하여야함
*복제를 사용할 경우 nextVal()의 펑션을 사용하면 안된다.
 why?
  복제에서 가져갈때
  nextVal()을 호출 함으로 시퀀스가 두번 증가

*select * from TEST.compare a where id not in (select id from TEST.test1 b);
 빈번호 찾는 쿼리



http://code.flickr.net/2010/02/08/ticket-servers-distributed-unique-primary-keys-on-the-cheap/

 * replace 문은 atomic 이 보장되는 문장이다.
 
 1.테이블 생성
  CREATE TABLE `Tickets64` (
    `id` bigint(20) unsigned NOT NULL auto_increment,
    `stub` char(1) NOT NULL default '',
    PRIMARY KEY  (`id`),
    UNIQUE KEY `stub` (`stub`)
  ) ENGINE=MyISAM

 2.
  SELECT * FROM Tickets64;

  REPLACE INTO Tickets64 (stub) VALUES ('a');

  SELECT LAST_INSERT_ID();

 3.
  -- --------------------------------------------------------------------------------
  -- Routine DDL
  -- Note: comments before and after the routine body will not be stored by the server
  -- --------------------------------------------------------------------------------
  DELIMITER $$

  CREATE FUNCTION `testNextVal` ()
  RETURNS INTEGER
  BEGIN
   REPLACE INTO Tickets64 (stub) VALUES ('a');
   RETURN LAST_INSERT_ID();
  END
 4.
  select testNextVal ();
 
 6.테스트 테이블
  CREATE TABLE test1 (
       id INT NOT NULL ,
       name CHAR(30) NOT NULL,
       PRIMARY KEY (id)
  ) ENGINE=INNODB;

 7.테스트 프로시저
   -- --------------------------------------------------------------------------------
  -- Routine DDL
  -- Note: comments before and after the routine body will not be stored by the server
  -- --------------------------------------------------------------------------------
  DELIMITER $$

  CREATE  PROCEDURE `insertsValAtTableWithNTimes`(
      IN  i_table_name      VARCHAR(255)
  ,   IN  i_n_times     INT
  )
  BEGIN
         DECLARE x  INT DEFAULT 1;
       SET @v_table_name :=  i_table_name;
       SET @v_sql := 'INSERT INTO @p_table_name(id, name) VALUES(? ,?);';
       SET @v_sql              :=  REPLACE(@v_sql,'@p_table_name',@v_table_name);
       

       PREPARE stmt FROM @v_sql;   
       
     WHILE x  <= i_n_times DO
       SET @v_id := 0;
       SELECT testNextVal () INTO @v_id;
       SET @v_name := CONCAT(x,'test');
       EXECUTE stmt USING @v_id, @v_name;
       SET x = x+1;
     END WHILE;
         
     DEALLOCATE PREPARE stmt;
        END

 8테스트
  SELECT * FROM Tickets64;

  TRUNCATE TABLE Tickets64;

  SELECT * FROM test1;

  TRUNCATE TABLE test1;


  call insertsValAtTableWithNTimes('test1',10);

 비교테이블
  delimiter $$

  CREATE TABLE T_COMPARE (
    `id`  bigint(20) unsigned NOT NULL auto_increment,
    `name` char(30) NOT NULL,
    PRIMARY KEY (`id`)
  ) ENGINE=MyISAM DEFAULT CHARSET=utf8$$
 
 비교 프로시저
   -- --------------------------------------------------------------------------------
  -- Routine DDL
  -- Note: comments before and after the routine body will not be stored by the server
  -- --------------------------------------------------------------------------------
  DELIMITER $$

  CREATE PROCEDURE `createTestData`(
      IN  i_table_name      VARCHAR(255)
  ,   IN  i_n_times     INT
  )
  BEGIN
         DECLARE x  INT DEFAULT 1;
       SET @v_table_name :=  i_table_name;
       SET @v_sql := 'INSERT INTO @p_table_name(name) VALUES(?);';
       SET @v_sql              :=  REPLACE(@v_sql,'@p_table_name',@v_table_name);
       

       PREPARE stmt FROM @v_sql;   
       
     WHILE x  <= i_n_times DO
       SET @v_name := CONCAT('test');
       EXECUTE stmt USING @v_name;
       SET x = x+1;
     END WHILE;
         
     DEALLOCATE PREPARE stmt;
  END
 
 테스트 데이터 만들기
  call createTestData('t_compare',1000000);
  
  확인

  select max(id) FROM t_compare;

  SELECT count(*) FROM t_compare;

 테스트
  창뛰우고

  call insertsValAtTableWithNTimes('test1',333333);


  show processlist; 애들이 경쟁하는게 보인다.

 잘못된 데이터 있나 체크 
  select count(*) from t_compare b where b.id not in (select id from test1);

 마스터-마스터 구조로 만들경우
  TicketServer1:
  auto-increment-increment = 2
  auto-increment-offset = 1

  TicketServer2:
  auto-increment-increment = 2
  auto-increment-offset = 2
 
 Flickr에서 현재 사용중



 


insert update문

 1.테이블 생성
  CREATE TABLE `TEST`.`sequence_data` (

  `sequence_name` varchar(100) NOT NULL,

  `sequence_increment` int(11) unsigned NOT NULL DEFAULT 1,

  `sequence_min_value` int(11) unsigned NOT NULL DEFAULT 1,

  `sequence_max_value` bigint(20) unsigned NOT NULL DEFAULT 18446744073709551615,

  `sequence_cur_value` bigint(20) unsigned DEFAULT 1,

  `sequence_cycle` boolean NOT NULL DEFAULT FALSE,

  PRIMARY KEY (`sequence_name`)
  ) ENGINE=MyISAM;


 2.시퀀스 입력
  INSERT INTO TEST.sequence_data (sequence_name) VALUE ('sq_my_sequence');

  INSERT INTO TEST.sequence_data(sequence_name, sequence_increment, sequence_max_value)VALUE('sq_sequence_2', 10, 100)

 3.프로시저 생성
  -- --------------------------------------------------------------------------------
  -- Routine DDL
  -- Note: comments before and after the routine body will not be stored by the server
  -- --------------------------------------------------------------------------------
  DELIMITER $$

  CREATE FUNCTION TEST.`nextval` (`seq_name` varchar(100))
  RETURNS bigint(20) NOT DETERMINISTIC
  BEGIN
   DECLARE cur_val bigint(20);
   
   SELECT
    sequence_cur_value INTO cur_val
   FROM
    TEST.sequence_data
   WHERE
    sequence_name = seq_name;

   IF cur_val IS NOT NULL THEN
    UPDATE
     TEST.sequence_data
    SET
     sequence_cur_value = IF ((sequence_cur_value + sequence_increment) > sequence_max_value,
              IF (sequence_cycle = TRUE,sequence_min_value,NULL),
              sequence_cur_value + sequence_increment)
    WHERE
     sequence_name = seq_name;
   END IF;

  RETURN cur_val;

  END
 4.getnextVal()
  SELECT TEST.nextval('sq_my_sequence');

 5.널이 리턴 될수 있는 상황
  둘다 에러 상황임
   When the sequence doesn’t exist
   When current sequence value out of range

 http://www.microshell.com/database/mysql/emulating-nextval-function-to-get-sequence-in-mysql/

 6.테스트 테이블
  CREATE TABLE TEST.test1 (
       id INT NOT NULL ,
       name CHAR(30) NOT NULL,
       PRIMARY KEY (id)
  ) ENGINE=INNODB;

 7.테스트 프로시저
   -- --------------------------------------------------------------------------------
  -- Routine DDL
  -- Note: comments before and after the routine body will not be stored by the server
  -- --------------------------------------------------------------------------------
  DELIMITER $$

  CREATE  PROCEDURE TEST.`insertsValAtTableWithNTimes`(
      IN  i_table_name      VARCHAR(255)
  ,   IN  i_n_times     INT
  )
  BEGIN
         DECLARE x  INT DEFAULT 1;
       SET @v_table_name :=  i_table_name;
       SET @v_sql := 'INSERT INTO TEST.@p_table_name(id, name) VALUES(? ,?);';
       SET @v_sql              :=  REPLACE(@v_sql,'@p_table_name',@v_table_name);
       

       PREPARE stmt FROM @v_sql;   
       
     WHILE x  <= i_n_times DO
       SET @v_id := 0;
       SELECT TEST.nextval('sq_my_sequence') INTO @v_id;
       SET @v_name := CONCAT(x,'test');
       EXECUTE stmt USING @v_id, @v_name;
       SET x = x+1;
     END WHILE;
         
     DEALLOCATE PREPARE stmt;
        END

 8테스트
  SELECT * FROM TEST.test1;

  TRUNCATE TABLE `TEST`.`test1`;


  mysql -h  -u hoeuser -p 
  use test;

  call TEST.insertsValAtTableWithNTimes('test1',1000);


 비교테이블
  delimiter $$

  CREATE TABLE `compare` (
    `id` int(11) NOT NULL,
    `name` char(30) NOT NULL,
    PRIMARY KEY (`id`)
  ) ENGINE=MyISAM DEFAULT CHARSET=utf8$$


  UPDATE `test`.`sequence_data` SET `sequence_cur_value`='1' WHERE `sequence_name`='sq_my_sequence';

  call TEST.insertsValAtTableWithNTimes('compare',60000);



Myisam 테이블을 사용해서 여러개가 그룹별 seq 만들기 
 Myisam table has a feature where , if a primary key contains 2 columns where one is an autoincrement and other is a varchar , then for each value of the text column the autoincrement column will create separate sequence of autoincrement numbers.

 CREATE TABLE IF NOT EXISTS `vt_sequences` (
   `type` varchar(32) COLLATE utf8_unicode_ci NOT NULL,
   `id` int(11) NOT NULL AUTO_INCREMENT,
   `name` varchar(32) COLLATE utf8_unicode_ci NOT NULL,
   `created_on` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
   PRIMARY KEY (`type`,`id`)
 ) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=1 ;


 In the above table type and id columns form the primary key. If the following insert is made :

 INSERT INTO `vt_sequences` (`type`) VALUES
 ('A'),
 ('A'),
 ('B'),
 ('B'),
 ('A');

 A 1 2011-10-22 15:29:04
 A 2 2011-10-22 15:29:04
 B 1 2011-10-22 15:29:04
 B 2 2011-10-22 15:29:04
 A 3 2011-10-22 15:29:04

 So the id column has a 1 for A and B separately. So now if we want autoincrement numbers resetting every month we could insert 2011-10 and 2011-11 in the type field and the autoincrement numbers would be independant.

 Another very useful thing about this technique is that if a particular entry is deleted , say A 3 is deleted , then next time when A is insert 3 would be reused , so autoincrement numbers get reused.

 and use get_last_inserted_id

  CREATE TABLE component_core ( 
      component_id INT AUTO_INCREMENT, 
      PRIMARY KEY(component_id) 
  ); 
   
  CREATE TABLE component_history ( 
      component_id INT NOT NULL, 
      version_id INT AUTO_INCREMENT, 
      DATA VARCHAR(20), 
      version_start_date DATETIME NOT NULL, 
      version_end_date DATETIME NOT NULL, 
      PRIMARY KEY(component_id,version_id) 
  ) ENGINE=MYISAM; 
   
  INSERT INTO component_core () VALUES ();
  INSERT INTO component_core () VALUES ();
   
  INSERT INTO component_history 
   (component_id, DATA, version_start_date, version_end_date)
   VALUES (1, 'data', NOW(), NOW());
  INSERT INTO component_history 
   (component_id, DATA, version_start_date, version_end_date)
   VALUES (1, 'data', NOW(), NOW());
  INSERT INTO component_history 
   (component_id, DATA, version_start_date, version_end_date)
   VALUES (1, 'data', NOW(), NOW());
   
  INSERT INTO component_history 
   (component_id, DATA, version_start_date, version_end_date)
   VALUES (2, 'data', NOW(), NOW());
  INSERT INTO component_history 
   (component_id, DATA, version_start_date, version_end_date)
   VALUES (2, 'data', NOW(), NOW());
  INSERT INTO component_history 
   (component_id, DATA, version_start_date, version_end_date)
   VALUES (2, 'data', NOW(), NOW());


앞에 키를 두어서 pk를 잡는거 어차피 샤딩 되니까 상관없다
 create table some_sharded_table(
  shard_key int,
  some_child_key bigint auto_increment not null,
  key(some_child_key),
  primary key (shard_key, some_child_key),
  data1 int,
  data2 char(10),
  …
 )

 You can safely move records between shards, because the primary key includes the directory provided key. The directory server maps keys to shards.

 You can then partition `some_sharded_table` nicely:
 alter table some_sharded_table partition by hash(shard_key) partitions 16;

 All the values related to one shard_key are now in one physical server, and the values are then partitioned again locally for improved performance through smaller indexes. This helps alleviate the overhead of the increased length of the secondary key used for the auto_increment value.

 You can use the primary key index to get the most recent N rows very efficiently for any shard_key, something that is very common in most sharded applications that I see.


last insert id 를 써서 사용하는 예제
 /* For this example, we'll put the sequences table in the test database. */
 USE test;
 /* Create a sequence table */
 CREATE TABLE IF NOT EXISTS sequences
 (name CHAR(20) PRIMARY KEY,
 val INT UNSIGNED);
 DROP FUNCTION IF EXISTS nextval;
 DELIMITER //
 /* The actual sequence function. Call nextval('seqname'), and it returns the next value. */
 /* If the named sequence does not yet exist, it is created with initial value 1. */
 CREATE FUNCTION nextval (seqname CHAR(20))
 RETURNS INT UNSIGNED
 BEGIN
 INSERT INTO sequences VALUES (seqname,LAST_INSERT_ID(1))
 ON DUPLICATE KEY UPDATE val=LAST_INSERT_ID(val+1);
 RETURN LAST_INSERT_ID();
 END
 //
 DELIMITER ;
 /* Let's now use a sequence in a test table... */
 CREATE TABLE IF NOT EXISTS data
 (id int UNSIGNED NOT NULL PRIMARY KEY DEFAULT 0,
 info VARCHAR(50));
 DROP TRIGGER nextval;
 /* The trigger only generates a new id if 0 is inserted. */
 /* The default value of id is also 0 (see the create table statement) so that makes it implicit. */
 CREATE TRIGGER nextval BEFORE INSERT ON data
 FOR EACH ROW SET new.id=IF(new.id=0,nextval('data'),new.id);
 TRUNCATE TABLE data;
 INSERT INTO data (info) VALUES ('bla');
 INSERT INTO data (info) VALUES ('foo'),('bar');
 SELECT * FROM data;

4_4_week기준

week 를 계산하여 파티셔닝 테이블을 만들경우 아래와 같은 문제점이 생길수 있습니다. 

SELECT YEARWEEK('2013-01-01',0); ->201253
SELECT YEARWEEK('2013-01-01',1); ->201301

또한 크론등의 os 기준으로 위크를 구할경우 다른 결과 값이 나옵니다.

위크 기준 파티셔닝 테이블을 만들때 고려 해야 할 사항같습니다.

INTERVAL -5 HOUR 등을 사용시 실제 week 구하는 메서드 소스를 까서 확인 할것

------------------상세------------------------------------------------------


1.한주의 시작을 일요일 또는 월요일로 볼것이냐에 따라 주차계산이 다릅니다.

mysql api 입니다.

0 to 53 or from 1 to 53.

Mode First day of week Range Week 1 is the first week …
0 Sunday 0-53 with a Sunday in this year
1 Monday 0-53 with more than 3 days this year
2 Sunday 1-53 with a Sunday in this year
3 Monday 1-53 with more than 3 days this year
4 Sunday 0-53 with more than 3 days this year
5 Monday 0-53 with a Monday in this year
6 Sunday 1-53 with more than 3 days this year
7 Monday 1-53 with a Monday in this year

https://dev.mysql.com/doc/refman/5.5/en/date-and-time-functions.html#function_yearweek

0 을 사용할 경우 1년의 주 0~52
SELECT WEEK('2013-01-01',0); ->0
SELECT WEEK('2013-12-31',0); ->52

1 을 사용할 경우 1년의 주  1~53
SELECT WEEK('2013-01-01',1); -> 1
SELECT WEEK('2013-12-31',1); -> 53

4_3_행열변환

CREATE TABLE COPY_T (
  NO INT(11) NOT NULL AUTO_INCREMENT,
  PRIMARY KEY (NO)
) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8

CALL USP_ADMIN_COPY_T_C(10000);

SELECT * FROM COPY_T

ALTER
    PROCEDURE `ecs`.`USP_ADMIN_COPY_T_C`()
    BEGIN
 DECLARE v_i INT DEFAULT 0;
 WHILE (v_i < 20) DO 
  INSERT INTO copy_t VALUES();
  SET v_i = v_i + 1; 
 END WHILE;



    END$$

DELIMITER ;


SELECT 
 F.reward_info_no
 , F.reward_type
 , F.reward_item_index
 , F.reward_count
 , F.desc_file_index
 , F.upt_time
 ,   F.content
FROM 
 EVENT__REWARD_INFO F,
 (SELECT DISTINCT 
  CASE no 
   WHEN 1 THEN 
    reward_info_no_0 
   WHEN 2 THEN 
    reward_info_no_1 
   ELSE 
    reward_info_no_2 
  END AS reward_info_no 
 FROM 
  (SELECT no
   , A.event_no
   , reward_info_no_0
   , reward_info_no_1 
   , reward_info_no_2 
  FROM 
   EVENT__LIST A
   , EVENT__DETAIL_INFO B
   ,(SELECT no 
    FROM 
     COPY_T 
    WHERE no < 4
     ) C 
  WHERE A.event_no = B.event_no 
  AND A.begin >= '2013-02-20' 
  AND A.end <= '2013-04-28'
  ) D
 ) E
WHERE E.reward_info_no  = F.reward_info_no

4_2_커서예제

DELIMITER $$
DROP PROCEDURE IF EXISTS `디비명`.`프로시저명` $$
CREATE PROCEDURE `디비명`.`프로시저명_merge`( IN 변수1 VARCHAR(32), IN 변수2 INT )
BEGIN

DECLARE m_필드값1 VARCHAR(32);
DECLARE m_필드값2 VARCHAR(32);

DECLARE m_Done INT DEFAULT 0;



/* 여기에 커서를 정의 합니다. */
DECLARE m_Cursor CURSOR FOR
 SELECT 필드1, 필드2
 FROM 테이블명
 WHERE 필드1 = 변수1 AND 필드2 = 변수2;


/* 데이터가 없으면 m_Done에 1 */

DECLARE CONTINUE HANDLER FOR NOT FOUND SET m_Done=1;
OPEN m_Cursor;

/* 반복합니다. */
REPEAT

 /* 반환된 필드값을 변수에 담습니다. */

FETCH NEXT FROM m_Cursor INTO m_필드값1, m_필드값2;

IF NOT m_Done THEN

   /* 수행할 쿼리리 여기에 작성합니다. */
   INSERT INTO 테이블2( 필드명1, 필드명2 ) VALUES( m_필드값1, m_필드값2 );

END IF;



/* m_Done이 1이 될때까지 반복 합니다. */
UNTIL m_Done END REPEAT;

CLOSE m_Cursor;

END $$

DELIMITER ;

4_1_배열예제

SET v_insert_friend_idxs = CONCAT('1,2' , ',');

WHILE (LOCATE(',', v_insert_friend_idxs) > 0)
DO
 SET @v_friend_idx = SUBSTRING(v_insert_friend_idxs, 1, LOCATE(',',v_insert_friend_idxs)-1);
 SET v_insert_friend_idxs = SUBSTRING(v_insert_friend_idxs, LOCATE(',', v_insert_friend_idxs) + 1);
 SELECT @v_friend_idx;
END WHILE;

3_2_Create예제

DELIMITER $$

DROP PROCEDURE IF EXISTS `WebProtocol_User_C`$$

CREATE PROCEDURE `WebProtocol_User_C`(
  IN  i_user_no    BIGINT  UNSIGNED
,     IN  i_pass_wd    VARCHAR(100)
,  OUT o_sp_rtn    INT
,  OUT o_rtn_message VARCHAR(100)
)
BEGIN
/******************************************************************************
Name        : WebProtocol_User_C
Description  : 유저 생성하기   Ver 1.0
Return     : 0 = 등록성공
    -1 = 유저생성에 실패했습니다.
    
    -998 = 중복키 오류가 발생 했습니다.

     
Example     :
 
SET @i_user_no     := 30  -- 유저번호
,    @i_pass_wd    := 'asds'  -- 패스워드

,    @o_sp_rtn              := -1
,    @o_rtn_message    := ''
;

CALL WebProtocol_User_C(
      @i_user_no
     , @i_pass_wd
     , @o_sp_rtn
     ,    @o_rtn_message   
     );
SELECT  @o_sp_rtn ,    @o_rtn_message

SELECT * FROM User_0  WHERE user_no = 30;


Reference   : 
History   :
Ver        Date        Author           Description
---------  ----------  ---------------  ------------------------------------
1.0        2013-12-31 LEE yh 1. Create.
******************************************************************************/
DECLARE v_effect_row   INT           DEFAULT 0;
DECLARE v_error_number   INT           DEFAULT 0;
DECLARE v_row_count       INT       DEFAULT 0;


 DECLARE EXIT HANDLER FOR SQLEXCEPTION
 BEGIN
  ROLLBACK;
  SELECT -999     INTO o_sp_rtn;
  SELECT 'SQL 내부 오류'   INTO o_rtn_message;
 END;
 
 DECLARE EXIT HANDLER FOR 1062 /* Duplicate key*/
 BEGIN
  ROLLBACK;
  SELECT -998      INTO o_sp_rtn;
  SELECT '중복키 오류가 발생 했습니다.'  INTO o_rtn_message; 
 END;
 
 FLOW_START:
 BEGIN
  DECLARE v_group_no    TINYINT      DEFAULT 0;
  -- STEP 1. 필요한 정보를 가져와 변수에 담는다
  -- SELECT
  -- 칼럼
  -- INTO
  --  v_변수
  -- FROM 테이블
  -- WHERE 조건

 -- STEP 2. 유저정보 등록
 
  SET v_group_no = RIGHT(i_user_no,1);
  SET @v_sql  :=  '';
  SET @v_table_name :=  ''; 

  SET @v_table_name :=  'USER_';
  SET @v_table_name :=  CONCAT(@v_table_name,v_group_no);
  SET @v_sql  :=  '
           INSERT INTO @p_table_name
      (
       user_no
      , pass_wd
      ) 
      VALUES 
      (
       ?
      , ?
      )
     ';

  SET   @v_sql  :=  REPLACE(@v_sql,'@p_table_name',@v_table_name);

  SET   @v_user_no :=  i_user_no;
  SET   @v_pass_wd :=  i_pass_wd;
    
  START TRANSACTION;
  
  PREPARE stmt FROM @v_sql;   
  EXECUTE stmt USING @v_user_no,@v_pass_wd;
  SELECT ROW_COUNT() INTO v_effect_row;
  DEALLOCATE PREPARE stmt; 
  
  IF (v_effect_row=0) THEN
   ROLLBACK; 
   SELECT -1        INTO o_sp_rtn;
   SELECT '유저 생성에 실패했습니다.'  INTO o_rtn_message;
   LEAVE FLOW_START;
  END IF;
  
  COMMIT;
  SELECT 0     INTO o_sp_rtn;
  SELECT '등록성공'   INTO o_rtn_message;

 END;  -- FLOW_START:

END$$

DELIMITER ;

3_1_Read예제

CREATE TABLE `USER_0` (
  `user_no` int(11) NOT NULL,
  `pass_wd` varchar(45) DEFAULT NULL,
  PRIMARY KEY (`user_no`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

INSERT INTO `USER_0` (`user_no`, `pass_wd`) VALUES ('10', '123');

DELIMITER $$

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

DROP PROCEDURE IF EXISTS `WebProtocal_USER_R`$$

CREATE PROCEDURE `WebProtocal_USER_R`(
 IN   i_user_no   BIGINT UNSIGNED
, OUT  o_sp_rtn    INT
, OUT  o_rtn_message VARCHAR(100)
)
BEGIN
/******************************************************************************
Name        : WebProtocal_USER_R
Description  : 유저 기본정보 가져오기
Return     : 0 = 조회성공
      -1 = 해당 유저정보가 존재하지 않습니다.
    
     
Example     :
 
SET  @i_user_no     := 1
,    @o_sp_rtn             := -1
,    @o_rtn_message   := ''
;


CALL WebProtocal_USER_R(@i_user_no,@o_sp_rtn,@o_rtn_message);

SELECT  @o_sp_rtn, @o_rtn_message;

SELECT * FROM USER_0;



Reference    :
    1. USER_0~9
History   :
Ver        Date        Author           Description
---------  ----------  ---------------  ------------------------------------
1.0        2013-12-30 LEE yh  1. Create.
******************************************************************************/
DECLARE v_effect_row   INT              DEFAULT 0;
DECLARE v_error_number   INT              DEFAULT 0;
DECLARE v_row_count       INT        DEFAULT 0;

DECLARE v_group_no    TINYINT      DEFAULT 0;


 FLOW_START:
 BEGIN
   
  SET v_group_no = RIGHT(i_user_no,1);
   
  -- STEP 1. 유저정보가져오기
  SET @v_sql    :=  '';
  SET @v_table_name :=  ''; 

  SET @v_table_name :=  'USER_';
  SET @v_table_name :=  CONCAT(@v_table_name,v_group_no);
  SET @v_sql  :=  '
           SELECT
       user_no
       , pass_wd
           FROM @p_table_name
           WHERE user_no  = ?
     ';

  SET   @v_sql  :=  REPLACE(@v_sql,'@p_table_name',@v_table_name);
  SET   @v_user_no :=  i_user_no;
   
  SET   @v_effect_row :=  -1;
  SET   @v_error_number :=  -1;


  PREPARE stmt FROM @v_sql;   
  EXECUTE stmt USING @v_user_no;
  SELECT FOUND_ROWS() INTO v_row_count ;  
  DEALLOCATE PREPARE stmt;
  
  IF (v_row_count<>1) THEN
   ROLLBACK; 
   SELECT -1      INTO o_sp_rtn;
   SELECT '해당 유저정보가 존재하지 않습니다.' INTO o_rtn_message;
   LEAVE FLOW_START;
  END IF;
  
  SELECT 0   INTO o_sp_rtn;
  SELECT '조회성공' INTO o_rtn_message;

 END;  
END$$

DELIMITER ;

2_2_ROWCOUNT()_LAST_INSERTED_ID()

 MYSQL 프로시저는 마지막에 실행된문이 영향을 끼친 로우 수를 반환 합니다.
 INSERT, UPDATE 의 경우 변한 로우수 반환 SELECT 의 경우 0,
 COMMIT, ROLLBACK 의 경우 0

 저희 테스트 프로시저가 맨 마지막에 COMMIT , ROLLBACK 을 날려서

 해당 SQL에 영향 받은 로우수가 0으로 리턴 되는 거네요

* START TARNSACTION 과 COMMIT, ROLLBACK 구문 제외하면 로우수 대로 나옴
 
DROP PROCEDURE `spTest`;

DELIMITER $$

CREATE DEFINER=`dpk`@`124.137.204.78` PROCEDURE `spTest`(
)
BEGIN
 DECLARE v_effected_row  INT DEFAULT 0;
 DECLARE v_error_cnt   INT DEFAULT 0;
 DECLARE v_flow_error_cnt INT DEFAULT 0;
 DECLARE v_insert_id INT DEFAULT 0;

 DECLARE EXIT HANDLER FOR SQLEXCEPTION
 BEGIN
  ROLLBACK;
  SELECT -999;
 END;

 DECLARE EXIT HANDLER FOR SQLWARNING
 BEGIN
  ROLLBACK;
  SELECT -998;
 END;
 
 FLOW_START:
  BEGIN
   START TRANSACTION;

    INSERT INTO TEST (num)VALUES (1);
    
    SET v_insert_id = LAST_INSERT_ID();

    SELECT ROW_COUNT(), @@error_count INTO v_effected_row, v_error_cnt;
    IF(v_effected_row != 1 OR v_error_cnt <> 0 ) THEN
     SELECT -1;
    END IF;

    UPDATE TEST SET num = 2 WHERE idx = v_insert_id;

    SELECT ROW_COUNT(), @@error_count INTO v_effected_row, v_error_cnt;
    IF(v_effected_row != 1 OR v_error_cnt <> 0 ) THEN
     SELECT -2;
    END IF;
 
  IF(v_flow_error_cnt > 0) THEN
   ROLLBACK;
   LEAVE FLOW_START;
  ELSE
   SELECT 0;
   COMMIT;
  END IF;
 END;

END $$
DELIMITER ;


delimiter $$

CREATE TABLE `TEST` (
  `idx` bigint(20) NOT NULL AUTO_INCREMENT,
  `num` bigint(20) NOT NULL,
  PRIMARY KEY (`idx`)
) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8$$

참고
The documentation says:  

the most recently generated ID is maintained in the server on a per-connection basis. It is not changed by another client. 

Re: LAST_INSERT_ID() on which Connection?
Feb 26, 2012 08:30 PM|LINK

Andy22
each time my web sever connects to the mysql sever it's considerd a different connection correct?
Correct.

Andy22
Will different instances of the above connection example be considered as different connections by MySQL?
Yes.
Andy22
if somehow my different web sessions use the same connection
Can't happen.

2_1_autocommit_vs_transaction

mysql 은 auto commit 이 default 로 켜져있다
하지만 start transaction 하게 되면 트랜잭션 모드로 들어간다.

http://dev.mysql.com/doc/refman/5.0/en/commit.html
By default, MySQL runs with autocommit mode enabled. This means that as soon as you execute a statement that updates (modifies) a table, MySQL stores the update on disk to make it permanent.



To disable autocommit mode implicitly for a single series of statements
, use the START TRANSACTION statement:

START TRANSACTION;
SELECT @A:=SUM(salary) FROM table1 WHERE type=1;
UPDATE table2 SET summary=@A WHERE type=1;
COMMIT;

확인 방법
select @@autocommit;

1_mysql 스토어 기본 문법

스토어드 프로그램 장단점
 장점
  데이터베이스 보안향상
   1.프로그램 단위로 실행 권한 부여가능
    조합하면 칼럼단위의 권한 조절 가능
   2.SQL-injection 등에 강함
?  기능의 추상화
   여러 프로그램에서 사용하는 일련번호 생성기
  네트워크 소요 시간 절감
  절차적 기능 구현
  개발 업무 구분

 단점
  낮은 처리 성능
   문자열 조작이나 숫자 계산등의 연산을 할경우 엄청 느리다
   시간당 처리 횟수의 배수 
    c/c++ 88.6배
    java 60.9배
    oracle 2.1배
    mySql 1.0
  
  애플리케이션 코드의 조각화
문법
 헤더 
  정의부 
   이름
   입출력값
   보안
   작동방식 옵션 
 본문
  실행되는 부분

*기본설정
 함수와 괄호 사이의 공백 무시 
  IGNORE_SPACE
 thrad_stack = 512K

스토어드 프로시저
 데이터를 주고 받아야 하는 여러쿼리를 하나의 묶음으로 사용하는것
 *스토어드 프로시저는 반드시 독립적으로 생성
 select or update문에서 프로시져 참조 불가

 프로시저는 내용은 변경 못한다. drop 후 crate 해야한다.
  ALTER 프로시저의 경우 속성 변경에 사용
  

 레이블 신택스
  [begin_label:] BEGIN
      [statement_list]
  END [end_label]

 DELIMITER
  프로시저의 시작과 종료를 알리기 위해 ';' 대신 사용
  사용후 다시 바꿔야 한다.

  EX)
   DELIMITER $$
    프로시저..
   END$$

   DELIMITER ;
 
 ex)
  DELIMITER $$

  CREATE PROCEDURE `test`.`sp_num` (IN param1 Integer, IN param2 Integer, OUT param3 Integer)
  BEGIN
   SET param3 = param1 + param2;
  END
 
 실행
  SET @result :=0;
  SELECT @result;

  CALL test.sp_num(1,2,@result);
  SELECT @result;
 
 *OUT, INOUT 파라미터는 세션변수를 만들어 넘겨주어야 한다.
 *프로그램 언어의 경우 세션 변수 없이 받아 올수 있음

 *커서는 그냥 SELECT 할 경우 CLINET 언트에 전송된다(출력된다)
  1.디버깅 용으로 사용 할 수 있음
  2.두개 이상의 결과 셋을 반환 할수 도 있다 .
  ex1)
   CREATE PROCEDURE `employees`.`sp_selectEmps` (IN in_emp_no INTEGER)
   BEGIN
    SELECT * FROM employees WHERE emp_no = in_emp_no;
   END

  ex2) 실행

   SET @result := 0;
   CALL sp_sum(1,2,@result);
   SELECT @result;

   프로시저
    
   CREATE PROCEDURE `employees`.`sp_sum` (IN in_int1 INTEGER, IN in_int2 INTEGER, OUT out_result INTEGER)
   BEGIN
    SELECT '> START' AS debug_message;
     SELECT CONCAT(' > P1', in_int1) AS debug_message;
     SELECT CONCAT(' > P2', in_int2) AS debug_message;
     
     SET out_result = in_int1 + in_int2;
     
     SELECT CONCAT(' > result', out_result) AS debug_message;

    SELECT '> END' AS debug_message;
   END
 

스토어드 함수
 sql문장 일부에서 참고 가능

 문법
  1.입력 파람은 모두 읽기 전용이라 IN, OUT 을 선언 할수 없다.
  2.return 값을 선언 해야 한다.
  3.함수 본문에서 return 을 해야 한다.

 함수에서 사용못하는 문법
  1.prepare, execute 명령을 이용한 프리페어 문
  2.명시적, 묵시적 RollBack/commit
  3.재귀호출
  4.함수 안에서 프로시져 호출
  5.결과 셋을 반환하는 SQL안됨
 
 *SELECT 를 사용 하면 클라이언트에 결과 셋이 반영되므로 INTO 를 꼭 쓰자

 ex)
  CREATE FUNCTION `employees`.`sf_sum` (num1 Integer, num2 Integer)
  RETURNS INTEGER
  BEGIN
   DECLARE result INTEGER DEFAULT 0;
   SET result = num1 + num2;
   
   RETURN result;
  RETURN 1;

트리거
 성능에 부하 걸림
 5.16 이전 버전 슈퍼만 생성가능
 5.x  하나의 이벤트에 최대 2개의 트리거 

 P659

이벤트(스케쥴러)
 아래와 같이 하면 start 시간에 맞춰 실행된다. 

 DELIMITER $$

 ALTER EVENT `EVENT_DAILY_ARENA_RANK_USER_INFO_C` ON SCHEDULE EVERY 1 DAY STARTS '2013-04-03 17:36:00' 
 DO  
 BEGIN
  SET @o_sp_rtn           := -1;
  CALL USP_BatchOperation_DAILY_ARENA_RANK_USER_INFO_C( @o_sp_rtn );
 END$$

 DELIMITER ;

 *이벤트를 inable 시켜줘야 된다. 
 SELECT @@event_scheduler;
 SET GLOBAL event_scheduler = 1;


 주어진 특성 시간에 스토어드 프로그램을 실행
 스케쥴 처리 스래드를 뛰어 스래드가 처리함

 파라미터 설정파일
  event_scheduler = ON (or 1)

 *따로 로그를 만들지 않고 가장 최근 정보만 기록함
  INFORMATION_SCHEMA.EVENTS

시작시간 확인 SELECT * FROM information_schema.`EVENTS`
 *실행 이력이 필요한 경우 테이블 생성 후 이벤트 처리 로직에서 기록
 기록하는게 좋다

 생성
  반복성 이벤트
   매일 01:00:00에 처리됨

   CREATE EVENT daily_ranking
    ON SCHEDULE EVERY 1 DAY STARTS '2013-01-22 01:00:00' ENDS '2013-01-25 12:59:59'
   DO
    INSERT INTO test1 VALUE(1,201);


  일회성 이벤트
   * 현재 시간으로 부터 1시간뒤에 처리됨
   
   CREATE EVENT one_time_job
    ON SCHEDULE AT CURRENT_TIMESTAMP + INTERVAL 1 HOUR
   DO
    INSERT INTO test1 VALUE(1,201);
 본문
  DO
   단순한 SQL문 실행시
   아니면
   DO
    BEGIN
     내용..
    END;;
  ON COMPLETION 완정이 종료되면 삭제됨(DEFAULT)
  ON COMPLETION NOT PRESERVE 로 하면 삭제 안됨
 확인
  등록된
   INFORMATION_SCHEMA.events에서 확인가능
  마지막 실행된
   mysql.event
    last_executed 마지막으로 실행된
  *information_schema.event 와 mysql.event 는 관리되는 시간의 TIME_ZONE
  이 다르기 때문에 시간이 조금 다르다. 같게 하려면 타임존 변환을 하면됨
   P666확인

본문 문법
 BEGIN..END 로 시작하며 BEGIN..END 가 중첩 될수 있다

 mysql에서 트랜잭션 시작방법
  BEGIN
  START TRANSACTION
  
  *하지만 프로시저류에서는 BEGIN 문법은 안된다.
   스토어드 프로시저나 이벤트 본문에서만 사용가능 한 문법
 
 *트랜잭션이 중일 경우 라도 프로시저에서 내부에서 COMMIT;을 하게되면 외부 트랜잭션까지 같이 종료됨
  *즉 PROPERGATION 이 없음(전파 되지 않음)

 트랜잭션 예제
  CREATE PROCEDURE `employees`.`sp_hello` ()
  BEGIN
   START TRANSACTION;
    INSERT INTO test value(1,2);
   COMMIT;
  END
 *단 프로시저에서 트랜잭션을 완료 할경우 APP에서의 트랜잭션 시작도 완료 
 되어 버린다.:) APP에서 할지 PROCEDURE에서 할지 확실이 결정 하자 

 * 많은 사용자가 프로시저에서 트랜잭션을 완료 할거라고 생각하지 않는다 고려하자!!

 변수 선언(BEGIN..END 에서 사용하는)
  스토어드 프로그램 로컬 변수 -> 로컬 변수
  프리페어 스테이트 먼트를 사용하려면 반드시 사용자 변수를 사용
 
  *사용자 변수보다 로컬 변수가 빠르다 
   사용자 변수를 너무 많이 쓰면 느려질 수 있다.
  선언
   DECARE v_name 자료형 DEFAULT 'value'
  값 넣기
   SET v_name = 'value', v_email = 'value@'

   SELECT emp_no, first_name, last_name INTO v_empno, v_firstname, v_lastname
   FROM employees 
   WHERE emp_no =  10001 
   LIMIT 1;

    SELECT..INTO 는 반드시 하나의 레코드에만 사용 할 수 있다
     없다거나 두개 이상이면 에러 발생
 IF..ELSE IF ..ELSE ..END IF
  CREATE FUNCTION EMPLOYEES.`sf_greatest` (p_value1 INT, p_value2 INT)
  RETURNS INT
  BEGIN
   IF p_value1 IS NULL THEN
    RETURN p_value2;
   ELSEIF p_value2 IS NULL THEN
    RETURN p_value1; 
   ELSEIF p_value1 >= p_value2 THEN
    RETURN p_value1; 
   ELSE
    RETURN p_value2;
   END IF;
  END
 
 CASE WHEN..TEHN..ELSE.. END CASE
  동등비교 에서
   CASE 변수
    WHEN 비교1 THEN 처리내용1
    WHEN 비교2 THEN 처리내용2
    ELSE 처리내용3
   END CASE
  
  일반
   CASE
    WHEN 비교조건식1 THEN 처리내용1
    WHEN 비교조건식2 THEN 처리내용2
    ELSE 처리내용3
   END CASE

  *BREAK 문 필요 없음
  
  ex)
   CREATE FUNCTION `employees`.`sf_greatest1` (p_value1 INT, p_value2 INT)
   RETURNS INT
   BEGIN
    CASE 
     WHEN p_value1 IS NULL THEN
      RETURN p_value2;
     WHEN p_value2 IS NULL THEN
      RETURN p_value1; 
     WHEN p_value1 >= p_value2 THEN
      RETURN p_value1; 
     ELSE
      RETURN p_value2;
    END CASE;
   END
 루프문
  WHILE
  LOOP
  REPEAT

  LOOP문 예제
   CREATE FUNCTION `employees`.`sf_loop` (p_max INT)
   RETURNS INTEGER
   BEGIN
    DECLARE v_factorial INT DEFAULT 1;

    factorial_loop : LOOP
     SET v_factorial = v_factorial * p_max;
     SET p_max = p_max -1;
     IF p_max <= 1 THEN
      LEAVE factorial_loop;
     END IF;
    END LOOP;

   RETURN v_factorial;
   END
  
  REPEAT문 예제
   CREATE FUNCTION `employees`.`sf_repeat` (p_max INT)
   RETURNS INTEGER
   BEGIN
    DECLARE v_factorial INT DEFAULT 1;

    REPEAT 
     SET v_factorial = v_factorial * p_max;
     SET p_max = p_max -1;
    
    UNTIL p_max <= 1 
    END REPEAT;

   RETURN v_factorial;
   END

  WHILE문 예제
   CREATE FUNCTION `employees`.`sf_while` (p_max INT)
   RETURNS INTEGER
   BEGIN
    DECLARE v_factorial INT DEFAULT 1;

    WHILE p_max > 1 DO
     SET v_factorial = v_factorial * p_max;
     SET p_max = p_max -1;
    END WHILE;

   RETURN v_factorial;
   END

핸들러와 컨디션을 통한 에러 핸들링
 에러No ,sql state
 에러 No 
  Mysql에러 번호
 sql state
  ansi 기준 에러 번호
 *sql state 가 좀더 추상화 되어있다 그러므로 핸들링 할때 sql state 로 하는게 좋다.
  P679

 핸들러
  문법
   DECLARE handler_type HANDLER
    FOR condtion_value [, condtion_value] ..
    handler_statements
   
   handler_type
    CONTINUE
     핸들러 구문 실행 후 프로그램의 다음 구문 실행
    
    EXIT 
     핸들러 구문 실행 후 선언된 BEGIN..END 종료
     *반드시 함수의 반환 타입에 맞는 코드를 반환 하는 코드가 있어야함
   condtion_value
    SQLSTATE
     *error no 는 버젼마다 다르니 sqlstat를 사용하자
     00 정상처리
     01 경고
     02 not found
    SQLEXCEPTION
     NOT FOUND 와 SQLWARRING을(00,01,02) 제외한  으로 시작하는 모든값
    
    *00000 은 사용말자

   handler_statements
    핸들러로 플로우가 넘어올 경우 처리 로직 작성
    
 컨디션
  ?
   에러 번호는 해석이 힘드니까 에러 번호를 문자에 맵핑 하는것

  문법
   DECLARE condition_name CONDITION FOR condition_value
    
   condtion_value
    mysql error 번호일 경우 바로 기록
    SQLSTATE 일경우 SQLSTATE를 입력하고 값을 입력
   
   ex)
   DECLARE dup_key CONDTION FOR 1062;

   ex)
   CREATE FUNCTION sf_testfucn()
   RETURNS BIGINT
   BEGIN
    DECLARE dup_key CONDITION FOR 1062
    DECLARE EXIT HANDLER FOR dup_key
     BEGIN
      RETURN -1;
     END;
    INSERT INTO tb_test VALUES(1);
    RETURN 1;
   END;;
 시그널
  사용자가 에러 생성 후 던짐(throw)
   5.5미만 버전은 해당 문법이 없어 존재하지 않는 테이블을 일부로 셀렉트해서 에러 던지는 꽁수 사용
  P685
  
  주의 사항
   1.SQLSTATE 사용
   2.SQLSTATE 는 정수가 아닌 5자리 문자열이다 '' 사용하자
   3.경고도 발생 시킬수 있다.
   4.에러 코드 
    00은 정상이므로 쓰면안된다.
    01은 경고 이므로 종료안시킴
    그외의 값 에러 발생
   5.일반적으로 유저 에러임으로 45로 시작하는 값을 사용하자
  ex)p686
  
  BEGIN
   DECLARE null_divisor CONDTION FOR SQLSTATE '45000'

   IF p_divisor IS NULL THEN
    SIGNAL null_divisor SET MESSAGE_TEXT = 'divisor can not be null', MYSQL_ERRNO=9999;
   ELSEIF p_divisor = 0 THEN
    SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'divisor can not be null', MYSQL_ERRNO=9999;
   ELSE
    SIGNAL SQLSTATE '01000' SET MESSAGE_TEXT = 'divisor can not be null', MYSQL_ERRNO=9997;
    RETUNR 0;
   END IF;
  
  *핸들러에서 에러를 받아 다시 집어 던지는것도 가능하다.p689


커서
 JDBC의 리절트셋 과 비슷

 커서 제한사항
  1.스토어드 프로그램의 커서는 전방향(전진) 읽기만 가능
  2.칼럼을 바로 업데이트하는것이 안됨
 
 DBMS 커서의 종류
  insensitive
   레코드를 임시테이블로 복사해서 가지고 있는 형태
   (복사해야 되기때문에 느리다)
   2.복사했기 때문에 변경이나 삭제가 불가능하다
  sensitive
   레코드에 대한 포인터를 가지고 있다
   변경할경우 바로 적용된다.

 insensitive + sensitive = Asensitive
 
 MySql은 두개를 복합적으로 사용함
 하지만 유저는 어느 것인지 알 수 없기 때문에 커서를 사용해 레코드에 대한 변경이나 삭제는 불가능 하다.

 사용 순서
  1.커서 정의 후 
  2.오픈한면 실제로 실행되고 
  3.페치 명령으로 레코드를 읽을수 있으며 
  4.클로우즈로 닫는다.
 ex)

  DELIMITER ;;

  CREATE FUNCTION sf_emp_count(p_dept_no VARCHAR(10))
  RETURNS BIGINT
  BEGIN
    /* 사원 번호가 20000보다 큰 사원의 수를 누적하기 위한 변수 */
    DECLARE v_total_count INT DEFAULT 0;
    /* 커서에 더 읽어야 할 레코드가 남아 있는지 없는지 여부를 위한 플래그 변수 */
    DECLARE v_no_more_data TINYINT DEFAULT 0;
    /* 커서를 통해서 SELECT된 사원번호를 임시로 담아 둘 변수 */
    DECLARE v_emp_no INTEGER;
    /* 커서를 통해서 SELECT된 사원의 입사 일자를 임시로 담아 둘 변수 */
    DECLARE v_from_date DATE;

    /* v_emp_list 라는 이름으로 커서 정의 */
    DECLARE v_emp_list CURSOR FOR
   SELECT emp_no, from_date FROM dept_emp WHERE dept_no=p_dept_no;
    /* 커서로부터 더 읽을 데이터가 있는지 없는지 플래그 변경을 위한 핸들러 */
    DECLARE CONTINUE HANDLER FOR NOT FOUND SET v_no_more_data = 1;

    /* 정의된 v_emp_list 커서를 오픈 */
    OPEN v_emp_list;

    REPEAT
      /* 커서로부터 레코드 한 개씩 읽어서 변수에 저장 */
      FETCH v_emp_list INTO v_emp_no, v_from_date;
      IF v_emp_no > 20000 THEN
        SET v_total_count = v_total_count + 1;
      END IF;
    UNTIL v_no_more_data END REPEAT;

    /* v_emp_list 커서를 닫고 관련 자원을 반납 */
    CLOSE v_emp_list;

    RETURN v_total_count;
  END ;;

  DELIMITER ;
  
  * 핸들러 사용 
   커서를 통해 읽다가 더이상 없으면 NOT FOUND 에러가 발생 하고 
   핸들러로 에서 no_more_date =1로 셋하고 continue 하기 때문에
   REPEAT 문을 빠져나옴
  
  *반드시 선언은 아래의 순서로 되어야 한다.

  1.로컬변수와 컨디션
  2.CURSOR
  3.HANDLER


스토어드 프로그램의 권한과 옵션
 DEFINDER = 소유자
 SECUERTITY = DEFINDER, INVOKER
  DEFINDER
   해당 프로시저를 생성한 사람의 권한으로 실행
   (호출자가 테이블에 권한이 없어도 돌아감)
  IVCOKER
   해당 프로시저를 호출 하는 자의 권한으로 실행
   (호출자가 테이블에 권한 없으면 에러)
 *정의 하지 않으면 SECUERTITY = DEFINDER 로 default로 설됨 

 스토어드 프로시저(함수) 권한 
  SUPER
   생성, 실행 가능
  CREATE ROUTINE
   함수 생성 가능
  ALTER ROUTINE
   조회, 삭제 가능
  EXECUTEE
   실행 가능

 *권한은 DB나 OBJECT 단위로 부여가능



DETERMINISTIC, NOT DETERMINISTIC
 함수에 정의

 DETERMINISTIC
  입력값이 같은면 결과는 늘 같다.
 NOT DETERMINISTIC
  입력값이 같아도 시간에 따라 결과는 다르다.
 
 DEFAULT NOT DETERMINISTIC
  * 디펄트가 늘 변함 임으로 속도가 느려질수 있다 꼭 구분해서 사용하자
 
 DETERMINISTIC
  쿼리가 실행되면 그때 한번 호출하고 그 쿼리 안에서 상수값을 돌려줌
  다시 쿼리가 호출되면 다시 실행됨



스토어드 프로그램에서 문자 깨지는 오류가 생기면
 P700확인

사용자 변수
 1.세션단위로 생김
 2.로컬변수보다 느리다.
  프리페어문이 아니라면 로컬 변수를 쓰자
 *3.사용하기전에 초기화 해주자
  세션 변수임으로 덮어 쓸 수 있다
 4.타입이 없어서 위험 할수있다.
로컬변수
 1.프로그램 실행시 생성
 

재귀호출 설정
 max_sp_recursing_depth = 0 으로 되어있음 재귀 호출 쓸수 없다.

중첩커서 쓸때 블락안에서 블락을 만들어서 처리하자 그게 더 깔금하다 

mysql_procedure 강의 계획

아래는 2013-12월달에 협력회사를 상대로 강의한 내용을 정리한 것입니다~ :)


mysql  프로시저 설명
-대상
mysql이 아닌 다른 DB를 사용했던 프로시저 개발자
RDB 아키텍처 이해
mysql 을 제외한 프로시저 작성경험 유

-목록
1_mysql_스토어_기본_문법
mysql 스토어 프로시저 기본 문법
*real mysql 의 스토어 프로시저 요약 본

2 다른 DB와 mysql이 다른 특이사항
2_1_autocommit_vs_transaction
mysql은 디펄트로 autocommit을 사용함
2_2_ROWCOUNT()_LAST_INSERTED_ID()
프로시저에서 에러 검정용 과  SEQ 반환을 위해 사용하는 두가지 펑션의 특이점
3 기본적인 MYSQL 프로시저 예제
3_1_READ_예제
READ의 기본폼 과 동적쿼리 사용예제
3_2_Create예제
트랜잭션 처리, 에러 핸들링, 로직 관련 예제
3_3_글로벌 시퀀스예제
글로벌 시퀀스 예제
4.자주 사용되는 폼 예제
4_1_배열예제
mysql에서는 배열을 지원하지 않음 stirng 으로 흉내 가능
*temptable 도 가능 하나 성능이슈가 너무 큼

4_2_커서예제
기본적인 커서예제
4_3_행열변환
copy_t 를 사용한 행렬 변환 예제
4_4_week 관련
파티션시 사용하는 mysql week function 에대한 상세





2013년 12월 27일 금요일

java concurrency in practice ch1

ch1 - introduction

멀티 쓰레드 프로그램은 복잡하다 근대 왜 써야 하는가?

멀티 프로세서에서 가장 쉽게 확장 할 수 있음으로


1.1 brief history of concurrency
in the acient past,
running only a single program at a time was an inefficient use of expensive and scarce computer resource

os evolved and run more than one program to run at once
running individual programs in process
if they need to, processes can communicate with one another

Resouce utilization
it it more efficient to use that wait time to let another program run

Fairness
let them(users, processes) share the computer via finger-grained time slicing

Convenience
wrtie serverel programs that each perform a single task and have them coordinate wite each other


"virtual von Neumann computer it had a memory space storing both instructions and data
, executing instructions sequentially" mean sequential programming model

Threads allow multiple streams of program control flow to coexist within a process
thread share process-wide resource such as memory and file handler
thread not share program counter, stack, local valiables

multiple threads within the same program can be scheduled simultaneouly on multiple CPUs


most moderan os treat threads as the basic unit of scheduling
thread share memory so it allows finer-grained data sharing than inter-process
but modify variables that another thread is in the middle of using, with unpreditable results

프로세스 는 여러개의 쓰레드로 이루어질수 있음
쓰레드는 프로세스의 메모리공간 공유
쓰레드의 스텍, 로컬 변수, 프로그램 카운터는 따로씀

하나의 프로그램을 쉽게 여러개의 코드로 분리 한 후 서로 협동하게 만들 수 있음
또한 멀티 cpu에도 쉽게 배분할 수 있음

문제는.. 쓰레드 관리가 힘들다는거!

1.2 Benefits of Threads
thread make it easier to program, by turing asynchronous workflow into mostly sequetial ones

1.2.1 Exploiting Multiple Processors
single thread program run at just one processor if 100 cpus than 99% of processor remains idels

1.2.3. simplicity of modeling
it is better assinging a thread to each type of task than manaing multiple different types of task at once
thread is used by framewoks such as servlets or RMI(servlet writers do not need to worry about mulitple user connections)

1.2.3 simplified Handling of Asynchronous Events
1.2.4 More Responsive User Interfaces
if long-running taks is executed in a serperate thread , the event thread remains free to prcess UI events, making thre UI more responsive

장점
1.여러개의 코어 사용가능
2.하나의 쓰레드만 노출하고 그 쓰레드와 작동하는 다른 쓰레드들을 감춤으로서 쉽게 프로그램 개발가능
(framework 개발에사용)
복잡한 일을 몇개의 쓰레드로 분리해서 작성 후 서로 연동하게함
3.비동기 이벤트 다루는대 좋음
nio
4.UI에서 오래 걸리는 일을 다른 쓰레드에서 실행 시킴으로서 유저의 입력에 대한 반응을 계속 할수 있음


1.3 Risks of Thread
thread is more esoteric, concurrency was an "advancded" topic

1.3.1 Safety Hazards

// Comment

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

// Comment
// Comment
public static String putDash(String[] strArr)
    {
        return join(strArr, '-');
    }