데이터베이스 성능을 위한 응용 프로그램 디자인

실행 후 SQLStatement 객체의 text 속성을 변경하지 마십시오. 대신, 각 SQL 문에 대해 하나의 SQLStatement 인스턴스를 사용하고 명령문 매개 변수를 사용하여 다른 값을 제공하십시오.

SQL 문이 실행되기 전에 런타임은 SQL 문을 준비(컴파일)하여 명령문을 수행하기 전에 내부적으로 수행되는 단계를 확인합니다. 이전에 실행되지 않은 SQLStatement 인스턴스에서 SQLStatement.execute() 를 호출하면 명령문이 실행되기 전에 자동으로 준비됩니다. execute() 메서드를 이후에 호출하는 경우 SQLStatement.text 속성이 변경되지 않았으면 명령문이 여전히 준비된 상태입니다. 따라서 명령문이 더 빨리 실행됩니다.

문 실행마다 값이 변경되는 경우 명령문을 다시 사용하는 이점을 극대화하도록 명령문 매개 변수를 사용하여 명령문을 사용자 정의합니다. 문 매개 변수는 SQLStatement.parameters 연관 배열 속성을 사용하여 지정됩니다. SQLStatement 인스턴스의 text 속성을 변경하는 경우와 달리, 명령문 매개 변수의 값을 변경하면 런타임에서 명령문을 다시 준비할 필요가 없습니다.

SQLStatement 인스턴스를 다시 사용하는 경우 응용 프로그램은 SQLStatement 인스턴스가 준비되면 이 인스턴스에 대한 참조를 저장해야 합니다. 이 인스턴스에 대한 참조를 유지하려면 함수 범위 변수가 아니라 클래스 범위 변수로 변수를 선언합니다. SQLStatement를 클래스 범위 변수로 만들기 위한 한 가지 좋은 방법은 한 클래스에 SQL 문을 래핑하는 응용 프로그램을 구성하는 것입니다. 함께 실행되는 명령문 그룹도 한 클래스에 래핑할 수 있습니다. 이 기술은 명령 디자인 패턴을 사용하는 것으로 알려져 있습니다. 인스턴스를 클래스의 멤버 변수로 정의하면 래퍼 클래스의 인스턴스가 응용 프로그램에 있는 한 이러한 SQLStatement 인스턴스가 유지됩니다. 최소한 함수 밖에 SQLStatement 인스턴스가 포함된 변수를 정의하기만 해도 해당 인스턴스가 메모리에 유지됩니다. 예를 들어, SQLStatement 인스턴스를 ActionScript 클래스에서 멤버 변수로 선언하거나 JavaScript 파일에서 비함수 변수로 선언합니다. 그런 다음 쿼리를 정말로 실행하려고 할 때 명령문의 매개 변수 값을 설정하고 execute() 메서드를 호출할 수 있습니다.

데이터 비교 및 정렬을 위한 실행 속도를 향상시키려면 데이터베이스 인덱스를 사용하십시오..

한 열에 대한 인덱스를 만들면 데이터베이스에 해당 열 데이터에 대한 복사본이 저장됩니다. 이 복사본은 숫자 또는 알파벳순으로 정렬됩니다. 따라서 데이터베이스에서 항등 연산자를 사용할 때와 같이 신속하게 값을 일치시키고 ORDER BY 절을 사용하여 결과 데이터를 정렬할 수 있습니다.

데이터베이스 인덱스는 지속적으로 최신 상태로 유지되므로 해당 테이블에서 데이터 변경 작업(INSERT 또는 UPDATE)을 발생시켜 속도가 약간 느려집니다. 그러나 데이터 검색 속도가 크게 향상됩니다. 이러한 성능상의 장단점으로 인해 단순히 모든 테이블의 모든 열을 인덱싱하는 것은 피해야 합니다. 대신에 자신만의 인덱스 정의 전략을 사용하십시오. 인덱싱 전략을 계획하려면 다음 지침을 참조하십시오.

  • 연결 테이블, WHERE 절 또는 ORDER BY 절에 사용되는 열은 인덱스를 설정하십시오.

  • 열이 자주 함께 사용될 경우 단일 인덱스를 사용하여 하나로 지정하십시오.

  • 검색하는 텍스트 데이터가 포함된 열이 알파벳순으로 정렬된 경우 인덱스에 대해 COLLATE NOCASE 상관 관계를 지정하십시오.

응용 프로그램 유휴 시간 동안 SQL 문을 미리 컴파일하는 것이 좋습니다..

SQL 문을 처음 실행하면 데이터베이스 엔진에서 SQL 텍스트를 준비(컴파일)하기 때문에 속도가 더 느립니다. 명령문의 준비 및 실행에는 많은 리소스가 필요할 수 있으므로 한 가지 전략은 초기 데이터를 미리 로드한 다음 다른 명령문을 백그라운드에서 실행하는 것입니다.

  1. 응용 프로그램에서 처음 필요한 데이터를 로드합니다.

  2. 응용 프로그램의 초기 시작 작업이 완료되었을 때나 응용 프로그램의 다른 "유휴" 시간에 다른 명령문을 실행합니다.

예를 들어, 응용 프로그램이 초기 화면을 표시하기 위해 데이터베이스에 전혀 액세스할 필요가 없다고 가정해 보십시오. 이 경우에는 데이터베이스 연결을 열기 전에 화면이 표시될 때까지 기다리십시오. 마지막으로 SQLStatement 인스턴스를 만들고 실행 가능한 인스턴스를 모두 실행합니다.

또는 응용 프로그램이 시작될 때 특정 쿼리의 결과와 같은 데이터를 즉시 표시한다고 가정해 보십시오. 이 경우에는 해당 쿼리에 대한 SQLStatement 인스턴스를 실행합니다. 초기 데이터가 로드되고 표시된 후 다른 데이터베이스 작업에 대한 SQLStatement 인스턴스를 만들고 가능하면 나중에 필요한 다른 명령문을 실행합니다.

실제로는 SQLStatement 인스턴스를 재사용할 경우 이 명령문을 준비하는데 걸리는 추가 시간은 단지 한 번만 필요합니다. 이러한 추가 시간은 전체 성능에 큰 영향을 주지 않을 것입니다.

여러 SQL 데이터 변경 작업은 하나의 트랜잭션으로 그룹화하십시오..

데이터 추가나 변경이 포함된 많은 SQL 문( INSERT 또는 UPDATE 문)을 실행하는 경우를 가정해 보십시오. 명시적 트랜잭션에서 모든 명령문을 실행하여 성능을 크게 향상시킬 수 있습니다. 트랜잭션을 명시적으로 시작하지 않으면 각 명령문이 자동으로 만들어진 자체 트랜잭션에서 실행됩니다. 각 트랜잭션(각 명령문)의 실행이 완료된 후 런타임은 결과 데이터를 디스크의 데이터베이스 파일에 씁니다.

반면에 트랜잭션을 명시적으로 만들고 해당 트랜잭션의 컨텍스트에서 명령문을 실행하는 경우 런타임은 메모리에서 모든 변경을 수행하고 트랜잭션이 커밋될 때 한 번에 모든 변경 사항을 데이터베이스 파일에 씁니다. 데이터를 디스크에 쓰는 것은 대개 작업에서 가장 시간이 오래 걸리는 부분입니다. 따라서 SQL 문마다 한 번이 아니라 전체적으로 한 번만 디스크에 쓰면 성능이 크게 향상될 수 있습니다.

SELECT 쿼리 결과는 SQLStatement 클래스의 execute() 메서드( prefetch 매개 변수 사용) 및 next() 메서드를 사용하여 여러 부분으로 처리하십시오..

큰 결과 집합을 검색하는 SQL 문을 실행한다고 가정해 보십시오. 그런 다음 응용 프로그램이 각 데이터 행을 루프로 처리합니다. 예를 들어, 데이터의 형식을 지정하고 이로부터 객체를 만듭니다. 이러한 데이터 처리에는 시간이 많이 소요되어 화면이 멈추거나 응답하지 않는 등의 렌더링 문제가 발생할 수 있습니다. 비동기 작업 에서 설명한 것처럼 한 가지 해결 방법은 작업을 여러 청크로 분할하는 것입니다. SQL 데이터베이스 API를 사용하면 데이터 처리를 쉽게 분할할 수 있습니다.

SQLStatement 클래스의 execute() 메서드에는 선택적인 prefetch 매개 변수(첫 번째 매개 변수)가 포함됩니다. 값을 제공하면 실행이 완료될 때 데이터베이스가 반환하는 최대 결과 행 수를 지정합니다.

dbStatement.addEventListener(SQLEvent.RESULT, resultHandler); 
dbStatement.execute(100); // 100 rows maximum returned in the first set

결과 데이터의 첫 번째 집합이 반환되면 next() 메서드를 호출하여 명령문 실행을 계속하고 다른 결과 행 집합을 검색할 수 있습니다. execute() 메서드와 비슷하게 next() 메서드는 prefetch 매개 변수를 수락하여 반환할 최대 행 수를 지정합니다.

// This method is called when the execute() or next() method completes 
function resultHandler(event:SQLEvent):void 
{ 
    var result:SQLResult = dbStatement.getResult(); 
    if (result != null) 
    { 
        var numRows:int = result.data.length; 
        for (var i:int = 0; i < numRows; i++) 
        { 
            // Process the result data 
        } 
         
        if (!result.complete) 
        { 
            dbStatement.next(100); 
        } 
    } 
}

모든 데이터가 로드될 때까지 next() 메서드를 계속 호출할 수 있습니다. 이전 목록에서와 같이 데이터가 모두 로드되었는지를 확인할 수 있습니다. execute() 또는 next() 메서드가 완료될 때마다 생성되는 SQLResult 객체의 complete 속성을 확인합니다.

참고: prefetch 매개 변수와 next() 메서드를 사용하여 결과 데이터 처리를 분할합니다. 쿼리의 결과를 결과 집합의 부분으로 제한하려는 경우에는 이 매개 변수와 메서드를 사용하지 마십시오. 명령문의 결과 집합에서 일부 행만 검색하려는 경우에는 SELECT 문의 LIMIT 절을 사용합니다. 결과 집합이 큰 경우에도 prefetch 매개 변수 및 next() 메서드를 사용하여 결과 처리를 분할할 수 있습니다.
단일 데이터베이스에서 여러 비동기 SQLConnection 객체를 사용하여 여러 명령문을 동시에 실행한다고 가정해 보십시오..

openAsync() 메서드를 사용하여 SQLConnection 객체가 데이터베이스에 연결되면 기본 런타임 실행 스레드가 아닌 백그라운드에서 실행됩니다. 또한 각 SQLConnection은 고유한 백그라운드 스레드로 실행됩니다. 여러 SQLConnection 객체를 사용하면 여러 SQL 문을 동시에 효과적으로 실행할 수 있습니다.

하지만 이 방법도 잠재적인 단점을 갖고 있습니다. 가장 중요한 문제는 각각의 추가 SQLStatement 객체에 추가 메모리가 필요하다는 것입니다. 또한 동시 실행 작업으로 인해 프로세스의 부담이 증가하고 CPU 또는 CPU 코어가 하나 뿐인 시스템의 경우 특히 문제가 될 수 있습니다. 이러한 단점이 있기 때문에 이 방법은 휴대 장치의 경우 권장되는 방법이 아닙니다.

또 다른 문제는 SQLStatement 객체가 단일 SQLConnection 객체에 연결될 경우 SQLStatement 객체를 재사용하여 얻게 되는 잠재적인 이점이 사라질 수 있다는 것입니다. 따라서 연관된 SQLConnection 객체가 이미 사용 중이면 SQLStatement 객체를 재사용할 수 없습니다.

단일 데이터베이스에 연결된 여러 SQLConnection 객체를 사용하도록 선택한 경우에는 각 객체가 고유한 트랜잭션에서 자신의 명령문을 실행한다는 것을 염두에 둬야 합니다. 데이터 추가, 수정, 삭제 등 데이터를 변경하는 모든 코드에서 이러한 별개의 트랜잭션을 처리해야 합니다.

Paul Robertson이 만든 오픈 소스 코드 라이브러리를 이용하면 잠재적인 단점을 최소화하면서 여러 SQLConnection 객체 사용에 따른 이점을 효과적으로 활용할 수 있습니다. 이 라이브러리에서는 SQLConnection 객체 풀을 사용하고 연관된 SQLStatement 객체를 관리합니다. 이 방식으로 SQLStatement 객체가 재사용되도록 보장하고 여러 SQLConnection 객체를 사용하여 여러 명령문을 동시에 실행할 수 있도록 보장합니다. 자세한 내용을 확인하고 라이브러리를 다운로드하려면 http://probertson.com/projects/air-sqlite/ 를 방문하십시오.