Storage

  • 리비전 슬러그: Storage
  • 리비전 제목: Storage
  • 리비전 아이디: 100070
  • 제작일시:
  • 만든이: Jeongkyu
  • 현재 리비전인가요? 아니오
  • 댓글 /* SQLite Locking */

리비전 내용

{{template.Fx_minversion_header(2)}}

저장소(Storage)SQLite 데이터베이스 API입니다. 이는 신뢰할 수 있는 호출자에게 제공되는데 여기에는 확장과 Firefox 콤포넌트만 해당합니다. 데이터베이스 연결 인터페이스의 모든 메소드와 속성에 대한 전체 레퍼런스는 mozIStorageConnection를 참고하십시오.

API는 현재 "확정되지 않은 상태(unfrozen)"로서 언제든지 바뀔 수 있습니다. Firefox 2와 Firefox 3 사이에 API가 바뀔 가능성이 높습니다.


참고: 저장소는 웹 페이지가 영속적인 데이터를 저장하는데 사용하는 DOM:Storage 기능이나 (확장이 사용하기 위한 XPCOM 저장소 유틸리티인) Session store API 기능과는 다릅니다.


시작하기

이 문서는 mozStorage API와 sqlite의 몇 가지 특성에 대해 다룹니다. SQL이나 "일반적인" sqlite에 대해서는 다루지 않습니다. 하지만, 참고 섹션에서 유용한 링크를 찾을 수 있습니다. mozStorage API에 대한 도움을 얻으려면 news.mozilla.org 뉴스 서버의 mozilla.dev.apps.firefox 그룹으로 질문을 올릴 수 있습니다. 버그를 신고하려면 Bugzilla (product "Toolkit", component "Storage")를 이용하십시오.

이제 시작하겠습니다. mozStorage는 많은 다른 데이터베이스 시스템과 유사하게 설계되었습니다. 사용을 위한 전반적인 절차는 다음과 같습니다.

  • 선택한 데이터베이스로 접속을 엽니다.
  • 접속에서 실행할 구문을 생성합니다.
  • 필요한 경우에 매개 변수를 구문에 대입합니다.
  • 구문을 실행합니다.
  • 오류를 확인합니다.
  • 구문을 초기화합니다.

접속 열기

C++ 사용자: 저장소 서비스의 첫 초기화는 주 스레드에서 해야 합니다. 다른 스레드에서 처음으로 저장소를 초기화하면 오류가 발생합니다. 그러므로, 스레드에서 서비스를 사용하려면 주 스레드에서 getService를 호출하여 서비스가 생성된 것을 확실하게 합니다.

사용자 프로파일 디렉토리의 "asdf.sqlite"로 접속을 여는 C++ 예제:

nsCOMPtr<nsIFile> dbFile;
rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
                            getter_AddRefs(dbFile));
NS_ENSURE_SUCCESS(rv, rv);
rv = dbFile->Append(NS_LITERAL_STRING("asdf.sqlite"));
NS_ENSURE_SUCCESS(rv, rv);

mDBService = do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv, rv);
rv = mDBService->OpenDatabase(dbFile, getter_AddRefs(mDBConn));
NS_ENSURE_SUCCESS(rv, rv);

MOZ_STORAGE_SERVICE_CONTRACTID 는 {{template.Source("storage/build/mozStorageCID.h")}}에서 정의하고 있습니다. 그 값은 "@mozilla.org/storage/service;1"입니다.

자바스크립트 예제:

var file = Components.classes["@mozilla.org/file/directory_service;1"]
                     .getService(Components.interfaces.nsIProperties)
                     .get("ProfD", Components.interfaces.nsIFile);
file.append("my_db_file_name.sqlite");

var storageService = Components.classes["@mozilla.org/storage/service;1"]
                        .getService(Components.interfaces.mozIStorageService);
var mDBConn = storageService.openDatabase(file);
참고: OpenDatabase 함수는 바뀔 수 있습니다. 문제가 발생하지 않도록 개선되고 단순하게 될 가능성이 높습니다.

데이터베이스의 이름을 sqlite database를 의미하는 ".sdb"로 끝나게 하고 싶을 수도 있지만 이는 바람직하지 않습니다. 윈도우는 이 확장자를 "애플리케이션 호환성 데이터베이스(Application Compatibility Database)"를 위한 알려진 확장자로 특별하게 간주하여 시스템 복구 기능의 일부로서 변경 사항을 자동으로 백업하게 됩니다. 이는 부하가 매우 큰 파일 작업을 야기합니다.

구문

아래 단계에 따라 SQLite 데이터베이스에 구문을 생성하고 실행할 수 있습니다. 전체 레퍼런스는 mozIStorageStatement를 참고하십시오.

구문 생성

구문을 생성하는 데에는 두 가지 방법이 있습니다. 매개 변수가 없고 구문이 어떠한 데이터도 반환하지 않는 경우에는 mozIStorageConnection.executeSimpleSQL를 사용하십시오.

C++:
rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING("CREATE TABLE foo (a INTEGER)"));

JS:
mDBConn.executeSimpleSQL("CREATE TABLE foo (a INTEGER)");

그렇지 않은 경우, mozIStorageConnection.createStatement을 사용하여 구문을 준비해야 합니다.

C++:
nsCOMPtr<mozIStorageStatement> statement;
rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING("SELECT * FROM foo WHERE a = ?1"),
                              getter_AddRefs(statement));
NS_ENSURE_SUCCESS(rv, rv);

JS:
var statement = mDBConn.createStatement("SELECT * FROM foo WHERE a = ?1");

이 예제에서는 나중에 대입할 매개 변수에 대하여 "?1"라는 플레이스홀더를 사용하고 있습니다(다음 섹션을 참고하십시오).

구문의 준비를 마치면 매개 변수를 대입하여 실행하고 초기화하는 것을 계속 반복할 수 있습니다. 구문을 여러 번 실행하는 경우, 미리 컴파일한 구문을 사용하면 SQL 질의를 매번 분석할 필요가 없으므로 현저한 성능의 향상을 얻을 수 있습니다.

여러분이 sqlite에 대해 잘 알고 있다면, 데이터베이스의 스키마가 바뀌는 경우 준비된 구문이 무효가 된다는 것을 알고있을 지도 모릅니다. 다행히 mozIStorageStatement는 이 오류를 감지하여 필요하다면 구문을 다시 컴파일합니다. 그러므로 구문을 생성하고 나면 스키마를 변경할 때 걱정할 필요가 없습니다. 모든 구문은 투명하게 계속 작동하게 됩니다.

매개 변수 대입

일반적으로 실행 중에 매개 변수를 포함한 SQL 문자열을 생성하는 것보다 모든 매개 변수를 별도로 대입하는 것이 가장 좋은 방법입니다. 다른 무엇보다도 이는 SQL 주입 공격을 방지할 수 있는데, 대입한 매개 변수는 절대 SQL로 실행되지 않기 때문입니다.

여러분은 플레이스홀더를 포함한 구문에 매개 변수를 대입합니다. 플레이스홀더는 색인으로 참조하는데, "?1"로 시작하고 그 다음 "?2"...와 같습니다. 플레이스홀더를 대입하려면 구문 함수 BindXXXParameter(0) BindXXXParameter(1)... 를 사용합니다.

주의: 플레이스홀더의 색인은 1부터 시작합니다. 대입 함수로 전달하는 정수는 0부터 시작합니다. 이는 "?1"가 매개 변수 0에 대응하고 "?2"가 매개 변수 1에 대응한다는 것을 의미합니다.

"?xx" 대신 ":example"와 같은 이름있는 매개 변수를 사용할 수도 있습니다.

플레이스홀더는 하나의 SQL 구문에 여러 번 나타날 수 있으며 모든 인스턴스는 대입한 값으로 대체합니다. 대입하지 않은 매개 변수는 NULL로 해석합니다.

아래 예제는 bindUTF8StringParameter()bindInt32Parameter()만 사용하고 있습니다. 모든 대입 함수 목록은 mozIStorageStatement를 참고하시기 바랍니다.

C++ 예제:

nsCOMPtr<mozIStorageStatement> statement;
rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING("SELECT * FROM foo WHERE a = ?1 AND b > ?2"),
                              getter_AddRefs(statement));
NS_ENSURE_SUCCESS(rv, rv);
rv = statement->BindUTF8StringParameter(0, "hello"); // "hello" will be substituted for "?1"
NS_ENSURE_SUCCESS(rv, rv);
rv = statement->BindInt32Parameter(1, 1234); // 1234 will be substituted for "?2"
NS_ENSURE_SUCCESS(rv, rv);

자바스크립트 예제:

var statement = mDBConn.createStatement("SELECT * FROM foo WHERE a = ?1 AND b > ?2");
statement.bindUTF8StringParameter(0, "hello");
statement.bindInt32Parameter(1, 1234);

이름있는 매개 변수를 사용하는 경우에는 getParameterIndex 메소드를 사용하여 이름있는 매개 변수의 색인을 얻어야 합니다. 자바스크립트 예제는 다음과 같습니다.

var statement = mDBConn.createStatement("SELECT * FROM foo WHERE a = :myfirstparam AND b > :mysecondparam");

var firstidx = statement.getParameterIndex(":myfirstparam");
statement.bindUTF8StringParameter(firstidx, "hello");

var secondidx = statement.getParameterIndex(":mysecondparam");
statement.bindInt32Parameter(secondidx, 1234);

같은 질의에 이름있는 매개 변수와 색인된 매개 변수를 혼합할 수도 있습니다.

var statement = mDBConn.createStatement("SELECT * FROM foo WHERE a = ?1 AND b > :mysecondparam");

statement.bindUTF8StringParameter(0, "hello");
// you can also use
// var firstidx = statement.getParameterIndex("?1");
// statement.bindUTF8StringParameter(firstidx, "hello");

var secondidx = statement.getParameterIndex(":mysecondparam");
statement.bindInt32Parameter(secondidx, 1234);

IN ( value-list ) 표현식과 함께 WHERE 절을 사용하는 경우에 대입은 동작하지 않게 됩니다. 대신 문자열을 생성하시기 바랍니다. 사용자 입력을 처리하는 경우가 아니라면 보안 문제는 없습니다.

var ids = "3,21,72,89";
var sql = "DELETE FROM table WHERE id IN ( "+ ids +" )";

구문 실행

구문을 실행하는 주 방법은 mozIStorageStatement.executeStep입니다. 이 함수는 구문이 생성하는 모든 결과 행을 나열할 수 있도록 해주고 더 이상 결과가 없을 때를 알려줍니다.

executeStep를 호출할 후에 mozIStorageValueArray에서 적절한 getter 함수를 사용하여 결과 행의 값을 얻을 수 있습니다(mozIStorageStatement는 mozIStorageValueArray를 구현합니다). 아래의 예제는 getInt32()만 사용하고 있습니다.

값의 형식은 지정한 열의 형식을 반환하는 mozIStorageValueArray.getTypeOfIndex로 구할 수 있습니다. 그러나, 주의하십시오. sqlite는 형식있는 데이터베이스가 아닙니다. 열에 선언한 형식과 무관하게 모든 셀에 아무 형식이나 입력할 수 있습니다. 다른 형식을 요청하면 sqlite는 최선을 다하여 그것을 변환하고 변환이 불가능하면 기본 값으로 처리합니다. 그러므로 형식 오류를 얻을 수 없으며 이상한 데이터 출력을 얻을 수도 있습니다.

C++ 코드는 AsInt32, AsDouble과 같은 함수를 이용할 수도 있는데, 이는 더 편리한 C++ 반환 값으로 값을 반환합니다. 하지만, 색인이 잘못된 경우에도 오류가 발생하지 않으므로 주의하십시오. 다른 오류가 발생하는 것도 불가능한데, sqlite는 사리에 맞지 않는 경우에도 항상 형식을 변환하기 때문입니다.

C++ 예제:

PRBool hasMoreData;
while (NS_SUCCEEDED(statement->ExecuteStep(&hasMoreData)) && hasMoreData) {
  PRInt32 value = statement->AsInt32(0);
  // use the value...
}

자바스크립트 예제:

while (statement.executeStep()) {
  var value = statement.getInt32(0); // use the correct function!
  // use the value...
}

mozIStorageStatement.execute()는 구문에서 얻을 데이터가 없는 경우에 편리한 함수입니다. 이는 구문을 한 번 실행하고 초기화합니다. 이는 삽입 구문에 대해 유용한데 코드를 매우 간단하게 하기 때문입니다.

var statement = mDBConn.createStatement("INSERT INTO my_table VALUES (?1)");
statement.bindInt32Parameter(52);
statement.execute();

파일 Image:TTRW2.zip은 데이터베이스에 대하여 SQL SELECT를 실행하는 간단하지만 완전한 자바스크립트입니다.

구문 초기화

더 이상 사용하지 않는 구문을 초기화하는 것은 중요합니다. 초기화되지 않은 쓰기 구문은 테이블에 잠금을 유지하여 다른 구문이 테이블을 접근하는 것을 막게 됩니다. 초기화되지 않은 읽기 구문은 쓰기를 막게 됩니다.

구문 개체가 해제되면 해당 데이터베이스 구문은 닫힙니다. C++을 사용 중인 경우, 모든 참조가 소멸된다는 것을 알고 있다면 따로 구문을 초기화할 필요가 없습니다. 또한 mozIStorageStatement.execute()를 사용하는 경우에도 따로 구문을 초기화할 필요가 없습니다. 이 함수는 여러분을 대신하여 구문을 초기화합니다. 나머지 경우에는 mozIStorageStatement.reset()를 호출하십시오.

자바스크립트 호출자는 확실하게 구문을 초기화해야 합니다. 특히 예외에 대해서 주의하십시오. 예외가 발생하거나 데이터베이스에 접근하는 것이 불가능해진 경우에도 구문을 초기화하는 것을 확실하게 해야 합니다. 구문 초기화는 비교적 가벼운 작업이고 이미 초기화된 경우에도 아무런 문제가 발생하지 않기 때문에 불필요한 초기화에 대해서 걱정할 필요는 없습니다.

var statement = connection.createStatement(...);
try {
  // use the statement...
} finally {
  statement.reset();
}

C++ 호출자도 같은 일을 해야 합니다. {{template.Source("storage/public/mozStorageHelper.h")}}에는 mozStorageStatementScoper라고 불리우는 유효 영역이 있는 개체가 있는데, 이 개체는 둘러싼 영역을 빠져 나갈 때 주어진 구문이 초기화되는 것을 보장합니다. 가능하면 이 개체를 사용하는 것이 바람직합니다.

void someClass::someFunction()
{
  mozStorageStatementScoper scoper(mStatement)
  // use the statement
}

최종 insert 아이디

연결의 lastInsertRowID 속성을 이용하면 데이터베이스의 마지막 INSERT 작업에서 할당한 아이디(rowid)를 구할 수 있습니다.
이는 여러분이 테이블에 INTEGER PRIMARY KEYINTEGER PRIMARY KEY AUTOINCREMENT로 지정된 열을 가지고 있을 때 유용한데, 이 경우 SQLite는 여러분이 값을 제공하지 않으면 삽입하는 각 행에 대하여 자동으로 값을 할당합니다. 반환 값은 자바스크립트에서는 number 형식이고 C++에서는 long long입니다.

lastInsertRowID를 이용하는 자바스크립트 예제는 다음과 같습니다.

var sql = "INSERT INTO contacts_table (number_col, name_col) VALUES (?1, ?2)"
var statement = mDBConn.createStatement(sql);
    statement.bindUTF8StringParameter(0, number);
    statement.bindUTF8StringParameter(1, name);
    statement.execute();
    statement.reset();
    
var rowid = mDBConn.lastInsertRowID;

트랜잭션

mozIStorageConnection는 트랜잭션을 시작하고 끝내는 함수를 가지고 있습니다. 명시적으로 트랜잭션을 사용하지 않으면 각 구문에 대하여 암시적인 트랜잭션이 생성됩니다. 이는 성능과 밀접한 관계가 있습니다. 각 트랜잭션에 대해 부하가 걸리는데 특히 커밋에 대해서 그렇습니다. 그러므로 하나의 행에서 여러 구문을 실행할 때 하나의 트랜잭션으로 처리하면 커다란 성능 향상을 얻을 수 있습니다. 성능에 대한 자세한 정보는 Storage:Performance를 참고하십시오.

다른 데이터베이스 시스템과의 주요한 차이점은 sqlite가 중첩 트랜잭션을 지원하지 않는다는 것입니다. 이는 하나의 트랜잭션을 열면 다른 트랜잭션을 열 수 없다는 뜻입니다. mozIStorageConnection.transactionInProgress를 확인하면 현재 진행 중인 트랜잭션이 있는지 알 수 있습니다.

SQL 구문으로 "BEGIN TRANSACTION"과 "END TRANSACTION"을 직접 실행할 수도 있습니다(이는 함수를 호출할 때 연결에서 실행하는 것입니다). 하지만 mozIStorageConnection.beginTransaction와 관련 함수를 사용하는 것이 바람직한데, 트랜잭션의 상태를 연결에 저장하기 때문입니다. 그렇지 않으면 transactionInProgress 속성은 잘못된 값을 갖게 됩니다.

sqlite는 다음과 같은 트랜잭션 형식을 가지고 있습니다.

  • mozIStorageConnection.TRANSACTION_DEFERRED: 기본 값. 필요할 때(보통 트랜잭션의 구문을 처음으로 실행할 때) 데이터베이스 잠금을 얻습니다.
  • mozIStorageConnection.TRANSACTION_IMMEDIATE: 곧바로 데이터베이스에 대한 읽기 잠금을 얻습니다.
  • mozIStorageConnection.TRANSACTION_EXCLUSIVE: 곧바로 데이터베이스에 대한 쓰기 잠금을 얻습니다.

이 트랜잭션의 형식을 mozIStorageConnection.beginTransactionAs로 전달하여 여러분에게 필요한 트랜잭션의 종류를 지정할 수 있습니다. 다른 트랜잭션이 이미 시작되었다면 이 작업은 성공하지 못한다는 것을 잊지 마십시오. 보통 기본 TRANSACTION_DEFERRED 형식으로 충분하며 다른 형식이 필요한 이유를 제대로 알지 못한다면 사용해서는 안됩니다. 더 자세한 정보는 BEGIN TRANSACTIONlocking에 대한 sqlite 문서를 참고하십시오.

var ourTransaction = false;
if (!mDBConn.transactionInProgress) {
  ourTransaction = true;
  mDBConn.beginTransactionAs(mDBConn.TRANSACTION_DEFERRED);
}

// ... use the connection ...

if (ourTransaction)
  mDBConn.commitTransaction();

C++ 코드에서는 {{template.Source("storage/public/mozStorageHelper.h")}}에 정의된 mozStorageTransaction 도우미 클래스를 사용할 수 있습니다. 이 클래스는 유효 범위에 들어오면 지정한 연결에서 지정한 형식의 트랜잭션을 시작하고 유효 범위를 벗어나면 트랜잭션을 커밋하거나 롤백합니다. 트랜잭션이 이미 진행 중이라면 트랜잭션 도우미 클래스는 어떤 작업도 하지 않습니다.

또한 명시적으로 커밋하는 함수도 가지고 있습니다. 전형적인 용법은 롤백을 기본으로 하는 클래스를 생성하고 나서 처리가 성공하면 명시적으로 트랜잭션을 커밋하는 것입니다.

nsresult someFunction()
{
  // deferred transaction (the default) with rollback on failure
  mozStorageTransaction transaction(mDBConn, PR_FALSE);

  // ... use the connection ...

  // everything succeeded, now explicitly commit
  return transaction.Commit();
}

데이터베이스를 손상하는 경우

  • strcmp로 비교하면 정확하게 같은 이름은 아니지만 실제 동일한 파일로 하나 이상의 연결을 엽니다. "my.db"와 "../dir/my.db" 또는 (대소문자 구별이 없는) 윈도우에서 "my.db"와 "My.db"가 여기에 포함됩니다. Sqlite는 많은 경우를 처리하려고 시도하지만 여러분은 그것에 의존하면 안됩니다.
  • 심볼릭 링크나 하드 링크로 데이터베이스를 접근합니다.
  • 하나 이상의 스레드에서 같은 데이터베이스로 연결을 엽니다(아래의 "스레드 안전성" 참고).
  • 하나 이상의 스레드에서 연결이나 구문을 접근합니다(아래의 "스레드 안전성" 참고).
  • 데이터베이스가 열려 있는 동안 외부 프로그램에서 데이터베이스를 엽니다. 우리의 캐시는 이 작업을 안전하게 처리할 수 있도록 하는 sqlite의 일반 파일 잠금을 방해합니다.

SQLite 잠금

SQLite는 전체 데이터베이스를 잠급니다. 즉, 읽기 동작 중인 경우에 쓰기 시도는 SQLITE_BUSY를 반환하고, 쓰기 동작 중인 경우에 읽기 시도는 SQLITE_BUSY를 반환합니다. 구문은 첫 번째 step()부터 reset() 호출 때까지 동작 중인 것으로 간주합니다. execute()는 하나의 실행으로 step()과 reset()을 호출합니다. 흔한 문제는 step()하기를 마친 후에 reset() 구문을 빠뜨리는 것입니다.

주어진 SQLite 연결은 여러 구문을 동시에 열 수 있지만, 잠금 모델은 이 구문들이 동시에 처리할 수 있는 작업(읽기 또는 쓰기)을 제한합니다. 사실 여러 구문이 동시에 읽는 것은 가능합니다. 그러나 여러 구문이 같은 테이블을 동시에 읽고 쓰는 것은 불가능합니다. 이는 같은 연결에서 동작하더라도 마찬가지입니다.

SQLite는 연결 수준과 테이블 수준의 2층 잠금 모델을 가지고 있습니다. 많은 사람들이 연결(데이터베이스) 수준 잠금 모델에 대해서 잘 알고 있습니다. 이는 읽는 작업은 여럿이지만 쓰는 작업은 단 하나입니다. 테이블 수준(B-트리) 잠금은 가끔 헷갈리는 것입니다. (내부적으로 데이터베이스의 각 테이블은 자신의 B-트리를 가지고 있으므로 "테이블"과 "B-트리"는 기술적으로 동의어입니다).

테이블 수준 잠금

하나의 연결만 가지고 있고 그것이 쓰기 작업을 위하여 데이터베이스를 잠궜다면 원하는 작업을 처리하기 위해 여러 구문을 사용할 수 있다고 생각할 지도 모릅니다. 전적으로 그렇지는 않습니다. 여러분은 데이터베이스를 탐색 중인 구문 핸들(예를 들어, 열려 있는 SELECT 구문)이 관리하는 테이블(B-트리) 수준 잠금에 대해서 알아야 합니다.

일반적인 규칙은 다음과 같습니다. 구문 핸들은 다른 구문 핸들이 읽고 있는(열려 있는 커서가 있는) 테이블(B-트리)을 수정하지 않습니다. 구문 핸들이 다른 구문 핸들과 같은 연결(트랜잭션 문맥, 데이터베이스 잠금 등)을 공유하더라도 마찬가지입니다. 그러한 작업 시도는 여전히 차단됩니다(즉, SQLITE_BUSY를 반환합니다).

이 문제는 하나의 구문으로 테이블을 탐색(iterate)하고 다른 구문으로 그 안의 레코드를 수정하려고 할 때 자주 발생합니다. 이 작업은 제대로 동작하지 않습니다(또는 최적화 수행의 개입에 따라 동작하지 않을 가능성을 수반합니다(아래 참고)). 수정 구문은 차단되는데 읽기 구문이 테이블에 열린 커서를 가지고 있기 때문입니다.

잠금 문제 피하기

해결책은 위에서 설명한대로 (1)을 따르는 것입니다. 이론적으로 (2)는 SQLite 3.x에서 제대로 동작하지 않습니다. 이 시나리오에서는 여러 개의 연결에 대하여 테이블 잠금과 더불어 데이터베이스 잠금이 역할을 하게 됩니다. 연결 2(수정 연결)는 연결 1(읽기 연결)이 테이터베이스를 읽는 동안 그것을 수정할 수 없습니다. 연결 2는 수정하는 SQL 구문을 실행하기 위하여 배타적인 잠금이 필요한데, 연결 1이 데이터베이스를 읽는 구문을 가지고 있는 한 이를 얻을 수 없습니다(연결 1은 이 때 공유하는 읽기 잠금을 가지고 있는데 이는 다른 연결이 배타적인 잠금을 얻을 수 없도록 합니다).

다른 선택 사항은 임시 테이블을 이용하는 것입니다. 해당 테이블의 결과를 포함한 임시 테이블을 생성하고 (읽기 구문의 테이블 잠금을 임시 테이블에 두면서) 그것을 탐색하십시오. 그러면 수정 구문은 문제 없이 실제 테이블을 바꿀 수 있습니다. 이 작업은 하나의 연결(트랜잭션 문맥)에서 나온 구문으로 수행할 수 있습니다. 이 시나리오는 ORDER BY가 내부적으로 임시 테이블을 생성할 수 있는 것처럼 가끔 보이지 않게 일어나기도 합니다. 그러나 최적화 수행이 모든 경우에 이렇게 할 것이라고 가정하는 것은 안전하지 않습니다. 오직 명시적으로 임시 테이블을 생성하는 것이 후자의 선택 사항을 수행하는 안전한 방법입니다.

Thread safety

The mozStorage service and sqlite are threadsafe. However, no other mozStorage or sqlite objects or operations are threadsafe.

  • The storage service must be created on the main thread. If you want to access the service from another thread, you should be sure that you call getService from the main thread ahead of time.
  • You can not access a connection or statement from multiple threads. These storage objects are not threadsafe, and the sqlite representations of them are not thread safe either. Even if you do locking and ensure that only one thread is doing something at once, there may be problems. This case hasn't been tested, and there may be some internal per-thread state in sqlite. It is strongly advised that you don't do this.
  • You can not access a single database from multiple connections from different threads. Normally, sqlite allows this. However, we do sqlite3_enable_shared_cache(1); (see sqlite shared-cache mode) which makes multiple connections share the same cache. This is important for performance. However, there is no lock for cache access, meaning it will break if you use if from more than one thread.

It's worth noting, however, that authors of JavaScript browser extensions are less impacted by these restrictions than it might first appear. If a database is created and used exclusively from within JavaScript, thread safety usually will not be an issue. SpiderMonkey (the JavaScript engine run within Firefox) executes JavaScript from a single persistent thread, except when the JavaScript runs in a different thread or is executed from a callback made on a different thread (e.g. via some networking or stream interfaces). Barring incorrect use of multi-threaded JavaScript, problems should occur only if a database already in use by a non-JavaScript, system-level thread is accessed through mozStorage.

See also

{{ wiki.languages( { "en": "en/Storage", "es": "es/Almacenamiento", "fr": "fr/Storage", "ja": "ja/Storage", "pl": "pl/Storage" } ) }}

리비전 소스

<p>
{{template.Fx_minversion_header(2)}}
</p><p><b>저장소(Storage)</b>는 <a class="external" href="http://www.sqlite.org/">SQLite</a> 데이터베이스 API입니다. 이는 신뢰할 수 있는 호출자에게 제공되는데 여기에는 확장과 Firefox 콤포넌트만 해당합니다. 데이터베이스 연결 인터페이스의 모든 메소드와 속성에 대한 전체 레퍼런스는 <a href="ko/MozIStorageConnection">mozIStorageConnection</a>를 참고하십시오.
</p><p>API는 현재 "확정되지 않은 상태(unfrozen)"로서 언제든지 바뀔 수 있습니다. Firefox 2와 Firefox 3 사이에 API가 바뀔 가능성이 높습니다.
</p><p><br>
</p>
<div class="note"><b>참고:</b> 저장소는 웹 페이지가 영속적인 데이터를 저장하는데 사용하는 <a href="ko/DOM/Storage">DOM:Storage</a> 기능이나 (확장이 사용하기 위한 <a href="ko/XPCOM">XPCOM</a> 저장소 유틸리티인) <a href="ko/Session_store_API">Session store API</a> 기능과는 다릅니다.</div>
<p><br>
</p>
<h2 name=".EC.8B.9C.EC.9E.91.ED.95.98.EA.B8.B0"> 시작하기 </h2>
<p>이 문서는 mozStorage API와 sqlite의 몇 가지 특성에 대해 다룹니다. SQL이나 "일반적인" sqlite에 대해서는 다루지 <i>않습니다</i>. 하지만, <a href="#See_also"> 참고 섹션</a>에서 유용한 링크를 찾을 수 있습니다. mozStorage API에 대한 도움을 얻으려면 news.mozilla.org 뉴스 서버의 mozilla.dev.apps.firefox 그룹으로 질문을 올릴 수 있습니다. 버그를 신고하려면 <a class="external" href="https://bugzilla.mozilla.org/enter_bug.cgi?product=Toolkit&amp;component=Storage">Bugzilla</a> (product "Toolkit", component "Storage")를 이용하십시오.
</p><p>이제 시작하겠습니다. mozStorage는 많은 다른 데이터베이스 시스템과 유사하게 설계되었습니다. 사용을 위한 전반적인 절차는 다음과 같습니다.
</p>
<ul><li> 선택한 데이터베이스로 접속을 엽니다.
</li><li> 접속에서 실행할 구문을 생성합니다.
</li><li> 필요한 경우에 매개 변수를 구문에 대입합니다.
</li><li> 구문을 실행합니다.
</li><li> 오류를 확인합니다.
</li><li> 구문을 초기화합니다.
</li></ul>
<h2 name=".EC.A0.91.EC.86.8D_.EC.97.B4.EA.B8.B0"> 접속 열기 </h2>
<p>C++ 사용자: 저장소 서비스의 첫 초기화는 주 스레드에서 해야 합니다. 다른 스레드에서 처음으로 저장소를 초기화하면 오류가 발생합니다. 그러므로, 스레드에서 서비스를 사용하려면 주 스레드에서 getService를 호출하여 서비스가 생성된 것을 확실하게 합니다.
</p><p>사용자 프로파일 디렉토리의 "asdf.sqlite"로 접속을 여는 C++ 예제:
</p>
<pre>nsCOMPtr&lt;nsIFile&gt; dbFile;
rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
                            getter_AddRefs(dbFile));
NS_ENSURE_SUCCESS(rv, rv);
rv = dbFile-&gt;Append(NS_LITERAL_STRING("asdf.sqlite"));
NS_ENSURE_SUCCESS(rv, rv);

mDBService = do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID, &amp;rv);
NS_ENSURE_SUCCESS(rv, rv);
rv = mDBService-&gt;OpenDatabase(dbFile, getter_AddRefs(mDBConn));
NS_ENSURE_SUCCESS(rv, rv);
</pre>
<p><code>MOZ_STORAGE_SERVICE_CONTRACTID</code> 는 {{template.Source("storage/build/mozStorageCID.h")}}에서 정의하고 있습니다. 그 값은 <code>"@mozilla.org/storage/service;1"</code>입니다.
</p><p>자바스크립트 예제:
</p>
<pre>var file = Components.classes["@mozilla.org/file/directory_service;1"]
                     .getService(Components.interfaces.nsIProperties)
                     .get("ProfD", Components.interfaces.nsIFile);
file.append("my_db_file_name.sqlite");

var storageService = Components.classes["@mozilla.org/storage/service;1"]
                        .getService(Components.interfaces.mozIStorageService);
var mDBConn = storageService.openDatabase(file);
</pre>
<dl><dd><div class="note">참고: OpenDatabase 함수는 바뀔 수 있습니다. 문제가 발생하지 않도록 개선되고 단순하게 될 가능성이 높습니다.</div>
</dd></dl>
<p>데이터베이스의 이름을 <b>s</b>qlite <b>d</b>ata<b>b</b>ase를 의미하는 ".sdb"로 끝나게 하고 싶을 수도 있지만 이는 <i>바람직하지 않습니다</i>. 윈도우는 이 확장자를 "애플리케이션 호환성 데이터베이스(Application Compatibility Database)"를 위한 알려진 확장자로 특별하게 간주하여 시스템 복구 기능의 일부로서 변경 사항을 자동으로 백업하게 됩니다. 이는 부하가 매우 큰 파일 작업을 야기합니다.
</p>
<h2 name=".EA.B5.AC.EB.AC.B8"> 구문 </h2>
<p>아래 단계에 따라 SQLite 데이터베이스에 구문을 생성하고 실행할 수 있습니다. 전체 레퍼런스는 <a href="ko/MozIStorageStatement">mozIStorageStatement</a>를 참고하십시오.
</p>
<h3 name=".EA.B5.AC.EB.AC.B8_.EC.83.9D.EC.84.B1"> 구문 생성 </h3>
<p>구문을 생성하는 데에는 두 가지 방법이 있습니다. 매개 변수가 없고 구문이 어떠한 데이터도 반환하지 않는 경우에는 <code>mozIStorageConnection.executeSimpleSQL</code>를 사용하십시오.
</p>
<pre>C++:
rv = mDBConn-&gt;ExecuteSimpleSQL(NS_LITERAL_CSTRING("CREATE TABLE foo (a INTEGER)"));

JS:
mDBConn.executeSimpleSQL("CREATE TABLE foo (a INTEGER)");
</pre>
<p>그렇지 않은 경우, <code>mozIStorageConnection.createStatement</code>을 사용하여 구문을 준비해야 합니다.
</p>
<pre>C++:
nsCOMPtr&lt;mozIStorageStatement&gt; statement;
rv = mDBConn-&gt;CreateStatement(NS_LITERAL_CSTRING("SELECT * FROM foo WHERE a = ?1"),
                              getter_AddRefs(statement));
NS_ENSURE_SUCCESS(rv, rv);

JS:
var statement = mDBConn.createStatement("SELECT * FROM foo WHERE a = ?1");
</pre>
<p>이 예제에서는 나중에 대입할 매개 변수에 대하여 "?1"라는 플레이스홀더를 사용하고 있습니다(다음 섹션을 참고하십시오).
</p><p>구문의 준비를 마치면 매개 변수를 대입하여 실행하고 초기화하는 것을 계속 반복할 수 있습니다. 구문을 여러 번 실행하는 경우, 미리 컴파일한 구문을 사용하면 SQL 질의를 매번 분석할 필요가 없으므로 현저한 성능의 향상을 얻을 수 있습니다.
</p><p>여러분이 sqlite에 대해 잘 알고 있다면, 데이터베이스의 스키마가 바뀌는 경우 준비된 구문이 무효가 된다는 것을 알고있을 지도 모릅니다. 다행히 mozIStorageStatement는 이 오류를 감지하여 필요하다면 구문을 다시 컴파일합니다. 그러므로 구문을 생성하고 나면 스키마를 변경할 때 걱정할 필요가 없습니다. 모든 구문은 투명하게 계속 작동하게 됩니다.
</p>
<h3 name=".EB.A7.A4.EA.B0.9C_.EB.B3.80.EC.88.98_.EB.8C.80.EC.9E.85"> 매개 변수 대입 </h3>
<p>일반적으로 실행 중에 매개 변수를 포함한 SQL 문자열을 생성하는 것보다 모든 매개 변수를 별도로 대입하는 것이 가장 좋은 방법입니다. 다른 무엇보다도 이는 SQL 주입 공격을 방지할 수 있는데, 대입한 매개 변수는 절대 SQL로 실행되지 않기 때문입니다.
</p><p>여러분은 플레이스홀더를 포함한 구문에 매개 변수를 대입합니다. 플레이스홀더는 색인으로 참조하는데, "?1"로 시작하고 그 다음 "?2"...와 같습니다. 플레이스홀더를 대입하려면 구문 함수 BindXXXParameter(0) BindXXXParameter(1)... 를 사용합니다.
</p>
<dl><dd><div class="note">주의: 플레이스홀더의 색인은 1부터 시작합니다. 대입 함수로 전달하는 정수는 0부터 시작합니다. 이는 "?1"가 매개 변수 0에 대응하고 "?2"가 매개 변수 1에 대응한다는 것을 의미합니다.</div>
</dd></dl>
<p>"?xx" 대신 ":example"와 같은 이름있는 매개 변수를 사용할 수도 있습니다.
</p><p>플레이스홀더는 하나의 SQL 구문에 여러 번 나타날 수 있으며 모든 인스턴스는 대입한 값으로 대체합니다. 대입하지 않은 매개 변수는 NULL로 해석합니다.
</p><p>아래 예제는 <code>bindUTF8StringParameter()</code>와 <code>bindInt32Parameter()</code>만 사용하고 있습니다. 모든 대입 함수 목록은 <a href="ko/MozIStorageStatement#Binding_functions">mozIStorageStatement</a>를 참고하시기 바랍니다.
</p><p>C++ 예제:
</p>
<pre>nsCOMPtr&lt;mozIStorageStatement&gt; statement;
rv = mDBConn-&gt;CreateStatement(NS_LITERAL_CSTRING("SELECT * FROM foo WHERE a = ?1 AND b &gt; ?2"),
                              getter_AddRefs(statement));
NS_ENSURE_SUCCESS(rv, rv);
rv = statement-&gt;BindUTF8StringParameter(0, "hello"); // "hello" will be substituted for "?1"
NS_ENSURE_SUCCESS(rv, rv);
rv = statement-&gt;BindInt32Parameter(1, 1234); // 1234 will be substituted for "?2"
NS_ENSURE_SUCCESS(rv, rv);
</pre>
<p>자바스크립트 예제:
</p>
<pre>var statement = mDBConn.createStatement("SELECT * FROM foo WHERE a = ?1 AND b &gt; ?2");
statement.bindUTF8StringParameter(0, "hello");
statement.bindInt32Parameter(1, 1234);
</pre>
<p>이름있는 매개 변수를 사용하는 경우에는 <code>getParameterIndex</code> 메소드를 사용하여 이름있는 매개 변수의 색인을 얻어야 합니다. 자바스크립트 예제는 다음과 같습니다.
</p>
<pre>var statement = mDBConn.createStatement("SELECT * FROM foo WHERE a = :myfirstparam AND b &gt; :mysecondparam");

var firstidx = statement.getParameterIndex(":myfirstparam");
statement.bindUTF8StringParameter(firstidx, "hello");

var secondidx = statement.getParameterIndex(":mysecondparam");
statement.bindInt32Parameter(secondidx, 1234);
</pre>
<p>같은 질의에 이름있는 매개 변수와 색인된 매개 변수를 혼합할 수도 있습니다.
</p>
<pre>var statement = mDBConn.createStatement("SELECT * FROM foo WHERE a = ?1 AND b &gt; :mysecondparam");

statement.bindUTF8StringParameter(0, "hello");
// you can also use
// var firstidx = statement.getParameterIndex("?1");
// statement.bindUTF8StringParameter(firstidx, "hello");

var secondidx = statement.getParameterIndex(":mysecondparam");
statement.bindInt32Parameter(secondidx, 1234);
</pre>
<p><code>IN ( value-list )</code> 표현식과 함께 <code>WHERE</code> 절을 사용하는 경우에 대입은 동작하지 않게 됩니다. 대신 문자열을 생성하시기 바랍니다. 사용자 입력을 처리하는 경우가 아니라면 보안 문제는 없습니다.
</p>
<pre>var ids = "3,21,72,89";
var sql = "DELETE FROM table WHERE id IN ( "+ ids +" )";
</pre>
<h3 name=".EA.B5.AC.EB.AC.B8_.EC.8B.A4.ED.96.89"> 구문 실행 </h3>
<p>구문을 실행하는 주 방법은 <code>mozIStorageStatement.executeStep</code>입니다. 이 함수는 구문이 생성하는 모든 결과 행을 나열할 수 있도록 해주고 더 이상 결과가 없을 때를 알려줍니다.
</p><p><code>executeStep</code>를 호출할 후에 <a href="ko/MozIStorageValueArray">mozIStorageValueArray</a>에서 <b>적절한 getter 함수를 사용하여</b> 결과 행의 값을 얻을 수 있습니다(mozIStorageStatement는 mozIStorageValueArray를 구현합니다). 아래의 예제는 <code>getInt32()</code>만 사용하고 있습니다.
</p><p>값의 형식은 지정한 열의 형식을 반환하는 <code>mozIStorageValueArray.getTypeOfIndex</code>로 구할 수 있습니다. 그러나, 주의하십시오. sqlite는 형식있는 데이터베이스가 아닙니다. 열에 선언한 형식과 무관하게 모든 셀에 아무 형식이나 입력할 수 있습니다. 다른 형식을 요청하면 sqlite는 최선을 다하여 그것을 변환하고 변환이 불가능하면 기본 값으로 처리합니다. 그러므로 형식 오류를 얻을 수 없으며 이상한 데이터 출력을 얻을 수도 있습니다.
</p><p>C++ 코드는 <code>AsInt32</code>, <code>AsDouble</code>과 같은 함수를 이용할 수도 있는데, 이는 더 편리한 C++ 반환 값으로 값을 반환합니다. 하지만, 색인이 잘못된 경우에도 오류가 발생하지 않으므로 주의하십시오. 다른 오류가 발생하는 것도 불가능한데, sqlite는 사리에 맞지 않는 경우에도 항상 형식을 변환하기 때문입니다.
</p><p>C++ 예제:
</p>
<pre>PRBool hasMoreData;
while (NS_SUCCEEDED(statement-&gt;ExecuteStep(&amp;hasMoreData)) &amp;&amp; hasMoreData) {
  PRInt32 value = statement-&gt;AsInt32(0);
  // use the value...
}
</pre>
<p>자바스크립트 예제:
</p>
<pre>while (statement.executeStep()) {
  var value = statement.getInt32(0); // use the correct function!
  // use the value...
}
</pre>
<p><code>mozIStorageStatement.execute()</code>는 구문에서 얻을 데이터가 없는 경우에 편리한 함수입니다. 이는 구문을 한 번 실행하고 초기화합니다. 이는 삽입 구문에 대해 유용한데 코드를 매우 간단하게 하기 때문입니다.
</p>
<pre>var statement = mDBConn.createStatement("INSERT INTO my_table VALUES (?1)");
statement.bindInt32Parameter(52);
statement.execute();
</pre>
<p>파일 <img alt="Image:TTRW2.zip" src="File:ko/Media_Gallery/TTRW2.zip">은 데이터베이스에 대하여 SQL SELECT를 실행하는 간단하지만 완전한 자바스크립트입니다.
</p>
<h3 name=".EA.B5.AC.EB.AC.B8_.EC.B4.88.EA.B8.B0.ED.99.94"> 구문 초기화 </h3>
<p>더 이상 사용하지 않는 구문을 초기화하는 것은 중요합니다. 초기화되지 않은 쓰기 구문은 테이블에 잠금을 유지하여 다른 구문이 테이블을 접근하는 것을 막게 됩니다. 초기화되지 않은 읽기 구문은 쓰기를 막게 됩니다.
</p><p>구문 개체가 해제되면 해당 데이터베이스 구문은 닫힙니다. C++을 사용 중인 경우, 모든 참조가 소멸된다는 것을 알고 있다면 따로 구문을 초기화할 필요가 없습니다. 또한 <code>mozIStorageStatement.execute()</code>를 사용하는 경우에도 따로 구문을 초기화할 필요가 없습니다. 이 함수는 여러분을 대신하여 구문을 초기화합니다. 나머지 경우에는 <code>mozIStorageStatement.reset()</code>를 호출하십시오.
</p><p>자바스크립트 호출자는 확실하게 구문을 초기화해야 합니다. 특히 예외에 대해서 주의하십시오. 예외가 발생하거나 데이터베이스에 접근하는 것이 불가능해진 경우에도 구문을 초기화하는 것을 확실하게 해야 합니다. 구문 초기화는 비교적 가벼운 작업이고 이미 초기화된 경우에도 아무런 문제가 발생하지 않기 때문에 불필요한 초기화에 대해서 걱정할 필요는 없습니다.
</p>
<pre>var statement = connection.createStatement(...);
try {
  // use the statement...
} finally {
  statement.reset();
}
</pre>
<p>C++ 호출자도 같은 일을 해야 합니다. {{template.Source("storage/public/mozStorageHelper.h")}}에는 mozStorageStatementScoper라고 불리우는 유효 영역이 있는 개체가 있는데, 이 개체는 둘러싼 영역을 빠져 나갈 때 주어진 구문이 초기화되는 것을 보장합니다. 가능하면 이 개체를 사용하는 것이 바람직합니다.
</p>
<pre>void someClass::someFunction()
{
  mozStorageStatementScoper scoper(mStatement)
  // use the statement
}
</pre>
<h2 name=".EC.B5.9C.EC.A2.85_insert_.EC.95.84.EC.9D.B4.EB.94.94"> 최종 insert 아이디 </h2>
<p>연결의 <code>lastInsertRowID</code> 속성을 이용하면 데이터베이스의 마지막 <code>INSERT</code> 작업에서 할당한 아이디(rowid)를 구할 수 있습니다.<br>
이는 여러분이 테이블에 <code>INTEGER PRIMARY KEY</code>나 <code>INTEGER PRIMARY KEY AUTOINCREMENT</code>로 지정된 열을 가지고 있을 때 유용한데, 이 경우 SQLite는 여러분이 값을 제공하지 않으면 삽입하는 각 행에 대하여 자동으로 값을 할당합니다.<a class="external" href="http://www.sqlite.org/capi3ref.html#sqlite3_last_insert_rowid"></a><a class="external" href="http://www.sqlite.org/faq.html#q1"> 반환 값은 자바스크립트에서는 <code>number</code> 형식이고 C++에서는 <code>long long</code>입니다.
</a></p><p><a class="external" href="http://www.sqlite.org/faq.html#q1"><code>lastInsertRowID</code>를 이용하는 자바스크립트 예제는 다음과 같습니다.
</a></p><a class="external" href="http://www.sqlite.org/faq.html#q1">
<pre>var sql = "INSERT INTO contacts_table (number_col, name_col) VALUES (?1, ?2)"
var statement = mDBConn.createStatement(sql);
    statement.bindUTF8StringParameter(0, number);
    statement.bindUTF8StringParameter(1, name);
    statement.execute();
    statement.reset();
    
var rowid = mDBConn.lastInsertRowID;
</pre>
<h2 name=".ED.8A.B8.EB.9E.9C.EC.9E.AD.EC.85.98"> 트랜잭션 </h2>
</a><p><a class="external" href="http://www.sqlite.org/faq.html#q1">mozIStorageConnection는 트랜잭션을 시작하고 끝내는 함수를 가지고 있습니다. 명시적으로 트랜잭션을 사용하지 않으면 각 구문에 대하여 암시적인 트랜잭션이 생성됩니다. 이는 성능과 밀접한 관계가 있습니다. 각 트랜잭션에 대해 부하가 걸리는데 특히 커밋에 대해서 그렇습니다. 그러므로 하나의 행에서 여러 구문을 실행할 때 하나의 트랜잭션으로 처리하면 커다란 성능 향상을 얻을 수 있습니다. 성능에 대한 자세한 정보는 </a><a href="ko/Storage/Performance">Storage:Performance</a>를 참고하십시오.
</p><p>다른 데이터베이스 시스템과의 주요한 차이점은 sqlite가 중첩 트랜잭션을 지원하지 않는다는 것입니다. 이는 하나의 트랜잭션을 열면 다른 트랜잭션을 열 수 없다는 뜻입니다. <code>mozIStorageConnection.transactionInProgress</code>를 확인하면 현재 진행 중인 트랜잭션이 있는지 알 수 있습니다.
</p><p>SQL 구문으로 "BEGIN TRANSACTION"과 "END TRANSACTION"을 직접 실행할 수도 있습니다(이는 함수를 호출할 때 연결에서 실행하는 것입니다). 하지만 <code>mozIStorageConnection.beginTransaction</code>와 관련 함수를 사용하는 것이 바람직한데, 트랜잭션의 상태를 연결에 저장하기 때문입니다. 그렇지 않으면 <code>transactionInProgress</code> 속성은 잘못된 값을 갖게 됩니다.
</p><p>sqlite는 다음과 같은 트랜잭션 형식을 가지고 있습니다.
</p>
<ul><li> mozIStorageConnection.TRANSACTION_DEFERRED: 기본 값. 필요할 때(보통 트랜잭션의 구문을 처음으로 실행할 때) 데이터베이스 잠금을 얻습니다.
</li></ul>
<ul><li> mozIStorageConnection.TRANSACTION_IMMEDIATE: 곧바로 데이터베이스에 대한 읽기 잠금을 얻습니다.
</li></ul>
<ul><li> mozIStorageConnection.TRANSACTION_EXCLUSIVE: 곧바로 데이터베이스에 대한 쓰기 잠금을 얻습니다.
</li></ul>
<p>이 트랜잭션의 형식을 <code>mozIStorageConnection.beginTransactionAs</code>로 전달하여 여러분에게 필요한 트랜잭션의 종류를 지정할 수 있습니다. 다른 트랜잭션이 이미 시작되었다면 이 작업은 성공하지 못한다는 것을 잊지 마십시오. 보통 기본 TRANSACTION_DEFERRED 형식으로 충분하며 다른 형식이 필요한 이유를 제대로 알지 못한다면 사용해서는 안됩니다. 더 자세한 정보는 <a class="external" href="http://www.sqlite.org/lang_transaction.html">BEGIN TRANSACTION</a>과 <a class="external" href="http://www.sqlite.org/lockingv3.html">locking</a>에 대한 sqlite 문서를 참고하십시오.
</p>
<pre>var ourTransaction = false;
if (!mDBConn.transactionInProgress) {
  ourTransaction = true;
  mDBConn.beginTransactionAs(mDBConn.TRANSACTION_DEFERRED);
}

// ... use the connection ...

if (ourTransaction)
  mDBConn.commitTransaction();
</pre>
<p>C++ 코드에서는 {{template.Source("storage/public/mozStorageHelper.h")}}에 정의된 mozStorageTransaction 도우미 클래스를 사용할 수 있습니다. 이 클래스는 유효 범위에 들어오면 지정한 연결에서 지정한 형식의 트랜잭션을 시작하고 유효 범위를 벗어나면 트랜잭션을 커밋하거나 롤백합니다. 트랜잭션이 이미 진행 중이라면 트랜잭션 도우미 클래스는 어떤 작업도 하지 않습니다.
</p><p>또한 명시적으로 커밋하는 함수도 가지고 있습니다. 전형적인 용법은 롤백을 기본으로 하는 클래스를 생성하고 나서 처리가 성공하면 명시적으로 트랜잭션을 커밋하는 것입니다.
</p>
<pre>nsresult someFunction()
{
  // deferred transaction (the default) with rollback on failure
  mozStorageTransaction transaction(mDBConn, PR_FALSE);

  // ... use the connection ...

  // everything succeeded, now explicitly commit
  return transaction.Commit();
}
</pre>
<h2 name=".EB.8D.B0.EC.9D.B4.ED.84.B0.EB.B2.A0.EC.9D.B4.EC.8A.A4.EB.A5.BC_.EC.86.90.EC.83.81.ED.95.98.EB.8A.94_.EA.B2.BD.EC.9A.B0"> 데이터베이스를 손상하는 경우 </h2>
<ul><li> 다음 문서에서 손상(corruption)에 대한 섹션을 참고하십시오. <a class="external" href="http://www.sqlite.org/lockingv3.html">File locking and concurrency in sqlite version 3</a>.
</li></ul>
<ul><li> <code>strcmp</code>로 비교하면 정확하게 같은 이름은 아니지만 실제 동일한 파일로 하나 이상의 연결을 엽니다. "my.db"와 "../dir/my.db" 또는 (대소문자 구별이 없는) 윈도우에서 "my.db"와 "My.db"가 여기에 포함됩니다. Sqlite는 많은 경우를 처리하려고 시도하지만 여러분은 그것에 의존하면 안됩니다.
</li></ul>
<ul><li> 심볼릭 링크나 하드 링크로 데이터베이스를 접근합니다.
</li></ul>
<ul><li> 하나 이상의 스레드에서 같은 데이터베이스로 연결을 엽니다(아래의 "스레드 안전성" 참고).
</li></ul>
<ul><li> 하나 이상의 스레드에서 연결이나 구문을 접근합니다(아래의 "스레드 안전성" 참고).
</li></ul>
<ul><li> 데이터베이스가 열려 있는 동안 외부 프로그램에서 데이터베이스를 엽니다. 우리의 캐시는 이 작업을 안전하게 처리할 수 있도록 하는 sqlite의 일반 파일 잠금을 방해합니다.
</li></ul>
<h2 name="SQLite_.EC.9E.A0.EA.B8.88"> SQLite 잠금 </h2>
<p>SQLite는 전체 데이터베이스를 잠급니다. 즉, 읽기 동작 중인 경우에 쓰기 시도는 SQLITE_BUSY를 반환하고, 쓰기 동작 중인 경우에 읽기 시도는 SQLITE_BUSY를 반환합니다. 구문은 첫 번째 step()부터 reset() 호출 때까지 동작 중인 것으로 간주합니다. execute()는 하나의 실행으로 step()과 reset()을 호출합니다. 흔한 문제는 step()하기를 마친 후에 reset() 구문을 빠뜨리는 것입니다.
</p><p>주어진 SQLite 연결은 여러 구문을 동시에 열 수 있지만, 잠금 모델은 이 구문들이 동시에 처리할 수 있는 작업(읽기 또는 쓰기)을 제한합니다. 사실 여러 구문이 동시에 읽는 것은 가능합니다. 그러나 여러 구문이 같은 테이블을 동시에 읽고 쓰는 것은 불가능합니다. 이는 같은 연결에서 동작하더라도 마찬가지입니다.
</p><p>SQLite는 연결 수준과 테이블 수준의 2층 잠금 모델을 가지고 있습니다. 많은 사람들이 연결(데이터베이스) 수준 잠금 모델에 대해서 잘 알고 있습니다. 이는 읽는 작업은 여럿이지만 쓰는 작업은 단 하나입니다. 테이블 수준(B-트리) 잠금은 가끔 헷갈리는 것입니다. (내부적으로 데이터베이스의 각 테이블은 자신의 B-트리를 가지고 있으므로 "테이블"과 "B-트리"는 기술적으로 동의어입니다).
</p>
<h3 name=".ED.85.8C.EC.9D.B4.EB.B8.94_.EC.88.98.EC.A4.80_.EC.9E.A0.EA.B8.88"> 테이블 수준 잠금 </h3>
<p>하나의 연결만 가지고 있고 그것이 쓰기 작업을 위하여 데이터베이스를 잠궜다면 원하는 작업을 처리하기 위해 여러 구문을 사용할 수 있다고 생각할 지도 모릅니다. 전적으로 그렇지는 않습니다. 여러분은 데이터베이스를 탐색 중인 구문 핸들(예를 들어, 열려 있는 SELECT 구문)이 관리하는 테이블(B-트리) 수준 잠금에 대해서 알아야 합니다.
</p><p>일반적인 규칙은 다음과 같습니다. 구문 핸들은 다른 구문 핸들이 읽고 있는(열려 있는 커서가 있는) 테이블(B-트리)을 수정하지 <b>않습니다</b>. 구문 핸들이 다른 구문 핸들과 같은 연결(트랜잭션 문맥, 데이터베이스 잠금 등)을 공유하더라도 마찬가지입니다. <b>그러한 작업 시도는 여전히 차단됩니다(즉, SQLITE_BUSY를 반환합니다)</b>.
</p><p>이 문제는 하나의 구문으로 테이블을 탐색(iterate)하고 다른 구문으로 그 안의 레코드를 수정하려고 할 때 자주 발생합니다. 이 작업은 제대로 동작하지 않습니다(또는 최적화 수행의 개입에 따라 동작하지 않을 가능성을 수반합니다(아래 참고)). 수정 구문은 차단되는데 읽기 구문이 테이블에 열린 커서를 가지고 있기 때문입니다.
</p>
<h3 name=".EC.9E.A0.EA.B8.88_.EB.AC.B8.EC.A0.9C_.ED.94.BC.ED.95.98.EA.B8.B0"> 잠금 문제 피하기 </h3>
<p>해결책은 위에서 설명한대로 (1)을 따르는 것입니다. 이론적으로 (2)는 SQLite 3.x에서 제대로 동작하지 않습니다. 이 시나리오에서는 여러 개의 연결에 대하여 테이블 잠금과 더불어 데이터베이스 잠금이 역할을 하게 됩니다. 연결 2(수정 연결)는 연결 1(읽기 연결)이 테이터베이스를 읽는 동안 그것을 수정할 수 없습니다. 연결 2는 수정하는 SQL 구문을 실행하기 위하여 배타적인 잠금이 필요한데, 연결 1이 데이터베이스를 읽는 구문을 가지고 있는 한 이를 얻을 수 없습니다(연결 1은 이 때 공유하는 읽기 잠금을 가지고 있는데 이는 다른 연결이 배타적인 잠금을 얻을 수 없도록 합니다).
</p><p>다른 선택 사항은 임시 테이블을 이용하는 것입니다. 해당 테이블의 결과를 포함한 임시 테이블을 생성하고 (읽기 구문의 테이블 잠금을 임시 테이블에 두면서) 그것을 탐색하십시오. 그러면 수정 구문은 문제 없이 실제 테이블을 바꿀 수 있습니다. 이 작업은 하나의 연결(트랜잭션 문맥)에서 나온 구문으로 수행할 수 있습니다. 이 시나리오는 ORDER BY가 내부적으로 임시 테이블을 생성할 수 있는 것처럼 가끔 보이지 않게 일어나기도 합니다. 그러나 최적화 수행이 모든 경우에 이렇게 할 것이라고 가정하는 것은 안전하지 않습니다. 오직 명시적으로 임시 테이블을 생성하는 것이 후자의 선택 사항을 수행하는 안전한 방법입니다.
</p>
<h2 name="Thread_safety"> Thread safety </h2>
<p>The mozStorage service and sqlite are threadsafe. However, no other mozStorage or sqlite objects or operations are threadsafe.
</p>
<ul><li> The storage service must be created on the main thread. If you want to access the service from another thread, you should be sure that you call getService from the main thread ahead of time.
</li></ul>
<ul><li> You can not access a connection or statement from multiple threads. These storage objects are not threadsafe, and the sqlite representations of them are not thread safe either. Even if you do locking and ensure that only one thread is doing something at once, there may be problems. This case hasn't been tested, and there may be some internal per-thread state in sqlite. It is strongly advised that you don't do this.
</li></ul>
<ul><li> You can not access a single database from multiple connections from different threads. Normally, sqlite allows this. However, we do <code>sqlite3_enable_shared_cache(1);</code> (see <a class="external" href="http://www.sqlite.org/sharedcache.html">sqlite shared-cache mode</a>) which makes multiple connections share the same cache. This is important for performance. However, there is no lock for cache access, meaning it will break if you use if from more than one thread.
</li></ul>
<p>It's worth noting, however, that authors of JavaScript browser extensions are less impacted by these restrictions than it might first appear. If a database is created and used exclusively from within JavaScript, thread safety usually will not be an issue.  SpiderMonkey (the JavaScript engine run within Firefox) executes JavaScript from a single persistent thread, except when the JavaScript runs in a different thread or is executed from a callback made on a different thread (e.g. via some networking or stream interfaces).  Barring incorrect use of multi-threaded JavaScript, problems should occur only if a database already in use by a non-JavaScript, system-level thread is accessed through mozStorage.
</p>
<h2 name="See_also"> See also </h2>
<ul><li><a href="ko/MozIStorageConnection">mozIStorageConnection</a> Database connection to a specific file or in-memory data storage
</li><li><a href="ko/MozIStorageStatement">mozIStorageStatement</a> Create and execute SQL statements on a SQLite database.
</li><li><a href="ko/MozIStorageValueArray">mozIStorageValueArray</a> Wraps an array of SQL values, such as a result row.
</li><li><a href="ko/MozIStorageFunction">mozIStorageFunction</a> Create a new SQLite function.
</li><li><a href="ko/MozIStorageAggregateFunction">mozIStorageAggregateFunction</a> Create a new SQLite aggregate function.
</li><li><a href="ko/MozIStorageProgressHandler">mozIStorageProgressHandler</a> Monitor progress during the execution of a statement.
</li><li><a href="ko/MozIStorageStatementWrapper">mozIStorageStatementWrapper</a> Storage statement wrapper
</li></ul>
<ul><li><a href="ko/Storage/Performance">Storage:Performance</a> How to get your database connection performing well.
</li><li><a class="external" href="https://addons.mozilla.org/en-US/firefox/addon/3072">Storage Inspector Extension</a> Makes it easy to view any sqlite database files in the current profile.
</li><li><a class="external" href="http://www.sqlite.org/lang.html">SQLite Syntax</a> Query language understood by SQLite
</li><li><a class="external" href="http://sqlitebrowser.sourceforge.net/">SQLite Database Browser</a> is a capable free tool available for many platforms. It can be handy for examining existing databases and testing SQL statements.
</li><li><a class="external" href="https://addons.mozilla.org/en-US/firefox/addon/5817">SQLite Manager Extension</a> helps manage sqlite database files on your computer.
</li></ul>
<div class="noinclude">
</div>
{{ wiki.languages( { "en": "en/Storage", "es": "es/Almacenamiento", "fr": "fr/Storage", "ja": "ja/Storage", "pl": "pl/Storage" } ) }}
Revert to this revision