SQLite는 별도의 서버 프로세스 없이 작동하며 장치의 단일 파일에 전체 데이터베이스를 저장하는 임베디드 관계형 데이터베이스입니다. 제로 구성, 작은 라이브러리 크기, 완전한 SQL 지원 덕분에 SQLite는 모바일 애플리케이션의 로컬 데이터 저장 표준이 되었습니다. SQLite 컨소시엄(2025)에 따르면, 이 DBMS는 iOS 및 Android의 모든 스마트폰을 포함하여 40억 개 이상의 장치에서 사용됩니다.
핵심 사항
SQLite는 전용 서버 없이 관계형 DBMS를 구현하는 C 언어 라이브러리입니다. 애플리케이션에 직접 임베드되어 장치의 파일 시스템에 있는 일반 파일에서 데이터를 읽고 씁니다. 라이브러리 크기는 약 600KB로, SQLite는 가장 가벼운 완전한 기능의 SQL 데이터베이스입니다.
SQLite는 JOIN, 하위 쿼리, 트리거, 뷰, 인덱스 및 윈도우 함수를 포함한 SQL:1999 표준의 대부분을 지원합니다. 제한 사항은 ALTER TABLE(제한된 지원) 및 완전한 RIGHT/FULL OUTER JOIN에 관한 것입니다. 그럼에도 모바일 애플리케이션의 경우 SQLite의 기능은 로컬 저장소의 99% 경우에 충분합니다.
Stack Overflow 개발자 설문조사(2025)에 따르면, SQLite는 임베디드 솔루션에서 가장 인기 있는 데이터베이스이며 MySQL 및 PostgreSQL 다음으로 모든 DBMS 중 인기도 3위입니다. 모바일 개발에서 SQLite는 모든 애플리케이션에서 직접 또는 래퍼를 통해 사용됩니다.
제로 구성 — SQLite는 설치, 권한 설정, 사용자 생성 또는 서비스 시작이 필요하지 않습니다. 라이브러리를 프로젝트에 연결하고 단일 함수를 호출하여 데이터베이스가 생성됩니다. 이는 서버 설치, 포트 구성 및 사용자 설정이 필요한 클라이언트-서버 DBMS에 비해 배포를 근본적으로 단순화합니다.
SQLite 데이터베이스 파일은 복사, 분석, 네트워크 전송 또는 백업에서 복원이 가능한 일반적인 크로스 플랫폼 파일입니다. 파일 형식은 API 수준에서 안정적입니다. 2004년에 생성된 SQLite 3 파일은 현재 버전의 라이브러리로 열리며 장기적인 데이터 호환성을 보장합니다.
SQLite의 아키텍처는 Tokenizer, Parser, Code Generator, VM, B-Tree, Pager, OS Interface 및 Utilities의 8개 가상 머신으로 구성됩니다. SQL 쿼리는 Tokenizer(토큰 분할), Parser(AST 구축), Code Generator(바이트코드 변환)를 거쳐 B-Tree 및 Pager를 통해 데이터 페이지를 읽는 가상 머신에서 실행됩니다.
SQLite는 테이블과 인덱스 저장에 B-Tree를 사용합니다. 각 테이블은 별도의 B-Tree로 저장되며 리프 노드에 데이터 행이 포함됩니다. 인덱스도 B-Tree로 저장되지만 리프에 키가 있습니다. Pager는 파일에서 메모리로 페이지(기본 4096바이트) 로딩을 관리하며 저널 또는 WAL을 통해 ACID 트랜잭션을 제공합니다.
WAL(Write-Ahead Logging)은 모바일 애플리케이션에 권장되는 모드입니다. 변경 사항은 먼저 별도의 WAL 파일에 기록된 다음 주기적으로 주 데이터베이스로 전송됩니다. WAL은 데이터베이스에서 동시에 읽기(오래된 데이터)와 쓰기(WAL 통해)를 허용하여 멀티스레드 애플리케이션의 성능을 향상시킵니다. 표준 저널(롤백 저널)은 쓰기 중 읽기를 차단합니다.
| 매개변수 | 롤백 저널 | WAL(Write-Ahead Logging) |
|---|---|---|
| 쓰기 중 읽기 | 차단됨 | 허용됨(오래된 데이터 읽기) |
| 쓰기 성능 | 중간 | 높음(WAL에 순차 쓰기) |
| 디스크 소비 | 적음(롤백 저널만) | 많음(WAL + 주 데이터베이스) |
| 충돌 복구 | 마지막 체크포인트로 롤백 | WAL에서 복구(데이터 손실 없음) |
| 권장 | 단일 스레드 시나리오용 | 일반 모바일 애플리케이션용 |
모드 전환은 단일 SQL 쿼리로 수행됩니다: PRAGMA journal_mode=WAL. 백그라운드 동기화와 동시에 데이터를 읽는 UI 스레드가 있는 모바일 애플리케이션의 경우 WAL은 더 나은 성능과 인터페이스 잠금 방지를 제공합니다.
SQLite는 로컬 데이터 저장소의 유일한 옵션은 아니지만 가장 다재다능합니다. Realm은 메모리 내 객체에 대한 직접 액세스의 높은 속도를 제공하지만 자체 NoSQL 형식을 사용하고 라이브러리 크기가 더 큽니다. iOS의 Core Data는 SQLite 위의 ORM 계층으로 객체 그래프 관리와 작업 실행 취소를 추가합니다.
대부분의 애플리케이션에서 SQLite는 예측 가능한 성능, 벤더 종속성 없음, 시간이 검증된 안정성 덕분에 최적의 선택입니다. Realm과 Core Data는 복잡한 객체 그래프, 반응형 쿼리 또는 장치 간 동기화가 필요한 프로젝트에서 정당화됩니다.
| 특성 | SQLite | Realm | Core Data |
|---|---|---|---|
| 데이터베이스 유형 | 관계형(SQL) | NoSQL(객체 기반) | ORM(SQLite 위) |
| 라이브러리 크기 | ~600KB | ~4MB | Apple SDK에 내장 |
| 성능 | 중간 | 높음(메모리 내 객체) | 중간(ORM 오버헤드) |
| 플랫폼 | iOS, Android, 웹, 데스크탑 | iOS, Android, Node.js | iOS, macOS |
| 벤더 종속성 | 없음(개방형 표준) | 중간(독점 형식) | 높음(Apple만) |
SQLite, Realm, Core Data 중 선택은 플랫폼, 객체 모델 요구 사항 및 동기화 전략에 따라 다릅니다. 크로스 플랫폼 프로젝트(KMP, Flutter)의 경우 SQLite는 데이터 모델 변경 없이 모든 대상 플랫폼에서 작동하는 유일한 범용 선택입니다.
Room은 Android Jetpack의 라이브러리로 SQLite 위에 ORM 계층을 제공합니다. Room은 어노테이션된 DAO 인터페이스에서 자동으로 SQL 쿼리를 생성하고 컴파일 타임에 쿼리 정확성을 검증하며 스키마 변경 시 데이터베이스 마이그레이션을 지원합니다. Room은 Android에서 SQLite를 작업하는 권장 방법입니다.
SQLiteOpenHelper는 ORM 없이 직접 SQLite를 관리하기 위한 저수준 API입니다. 클래스는 데이터베이스 생성, 열기 및 업그레이드를 관리합니다. SQLiteOpenHelper는 간단한 SQL 쿼리가 있는 프로젝트 또는 Room 추상화 없이 SQL 로직에 대한 완전한 제어가 필요할 때 적합합니다.
Room에서 엔티티는 @Entity로, DAO는 @Dao로 어노테이션됩니다. Room은 어노테이션된 메서드를 SQL 쿼리로 변환합니다. @Insert는 INSERT를 생성하고 @Query는 지정된 SQL로 SELECT를 생성합니다. 마이그레이션은 이전 및 새 스키마 버전을 지정하여 Migration 클래스를 통해 추가됩니다. Room은 컴파일 타임에 SQL을 검증하여 프로덕션에서 구문 오류를 제거합니다.
@Entity
data class User(
@PrimaryKey val id: Long,
val name: String,
@ColumnInfo(name = "created_at")
val createdAt: Long
)
@Dao
interface UserDao {
@Query("SELECT * FROM user ORDER BY name ASC")
suspend fun getAllUsers(): List<User>
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertUser(user: User)
@Query("DELETE FROM user WHERE id = :id")
suspend fun deleteUser(id: Long)
}
Room은 자동으로 UserDao_Impl 구현을 생성하며, 내부 RoomDatabase를 통해 런타임 SQLite 쿼리를 포함합니다. 코루틴(suspend) 덕분에 DAO 메서드는 UI를 차단하지 않고 백그라운드 스레드에서 비동기적으로 실행됩니다. @Query의 Flow 반환 유형은 테이블이 변경될 때 자동으로 결과를 업데이트합니다.
FMDB는 SQLite C API에 대한 Objective-C 래퍼로, 역사적으로 iOS에서 처음으로 널리 사용된 라이브러리입니다. 쿼리 실행 및 결과 검색을 위해 FMDatabase 및 FMResultSet 객체를 제공합니다. FMDB는 간단하고 미니멀하지만 Swift 특정 구조(옵셔널, Codable, async/await)를 지원하지 않습니다.
GRDB는 SQLite 작업을 위한 현대적인 Swift 라이브러리입니다. 타입 안전 API, Codable 지원, Combine Publishers, async/await, 마이그레이션 및 실시간 변경 관찰을 제공합니다. GRDB는 Swift Concurrency와의 완전한 통합 및 더 나은 코드 가독성으로 인해 새로운 Swift 프로젝트에 선호됩니다.
GRDB는 FetchableRecord 및 TableRecord 프로토콜을 준수하는 Record 클래스를 통해 테이블을 정의합니다. 쿼리는 원시 SQL 대신 타입 안전 구문으로 Swift로 작성됩니다. GRDB는 스키마 버전 관리 및 앱 버전 간 마이그레이션을 위한 DatabaseMigrator도 지원합니다.
struct User: Codable, FetchableRecord, TableRecord {
var id: Int64
var name: String
var createdAt: Date
}
let dbPool = try DatabasePool(path: dbPath)
var migrator = DatabaseMigrator()
migrator.registerMigration("v1") { db in
try db.create(table: "user") { t in
t.autoIncrementedPrimaryKey("id")
t.column("name", .text).notNull()
t.column("createdAt", .datetime).notNull()
}
}
let users = try await dbPool.read { db in
try User.order(Column("name")).fetchAll(db)
}
DatabasePool은 동시 읽기를 위해 SQLite의 WAL 모드를 사용합니다. 여러 리더가 동시에 데이터베이스에 액세스할 수 있는 반면 단일 작성자가 WAL을 통해 데이터를 업데이트합니다. GRDB는 연결 및 트랜잭션을 자동으로 관리하여 수동 동기화 없이 모든 스레드에서 스레드 안전한 데이터베이스 액세스를 제공합니다.
인덱스는 SQLite 쿼리를 가속화하는 가장 효과적인 방법입니다. WHERE, JOIN 및 ORDER BY 절에 사용되는 열에 인덱스가 생성됩니다. 100000개 레코드가 있는 테이블의 경우 인덱스된 열 검색은 초가 아닌 밀리초가 소요됩니다. 그러나 인덱스는 INSERT 및 UPDATE를 느리게 하므로 그 수는 쓰기 빈도와 균형을 이루어야 합니다.
단일 트랜잭션 내의 배치 삽입은 대량 데이터 로딩을 근본적으로 가속화합니다. 1000개 레코드를 하나씩 삽입하면 약 1초의 오버헤드가 발생합니다. 동일한 1000개 레코드를 단일 트랜잭션으로 삽입하면 약 5~10밀리초가 소요됩니다. 차이는 각 개별 INSERT가 동기 디스크 쓰기로 새 트랜잭션을 생성하기 때문입니다.
PRAGMA는 라이브러리 동작을 구성하는 SQLite 명령입니다. 주요 최적화 프라그마: PRAGMA synchronous=NORMAL(fsync 빈도 감소), PRAGMA cache_size=-8000(8MB 캐시 할당), PRAGMA temp_store=MEMORY(메모리의 임시 테이블). 대용량 데이터를 다루는 모바일 애플리케이션의 경우 이러한 프라그마의 조합으로 쿼리가 2~3배 빨라집니다.
또 다른 중요한 최적화는 SQL 쿼리의 사전 컴파일(준비된 문)입니다. 쿼리가 반복적으로 실행되는 경우(예: 10000행 삽입), SQL을 한 번 컴파일하고 문을 재사용하면 CPU 부하가 30~50% 감소합니다. Room과 GRDB는 준비된 문을 자동으로 캐시하지만 SQLite C API를 직접 사용할 때는 컴파일을 수동으로 수행해야 합니다.
class UserRepository(private val db: RoomDatabase) {
suspend fun insertBatch(users: List<User>) {
db.withTransaction {
users.chunked(500).forEach { batch ->
batch.forEach { user ->
insertUser(user)
}
}
}
}
}
withTransaction을 사용한 배치 삽입은 모든 INSERT 작업이 단일 트랜잭션 내에서 실행되도록 보장합니다. 청크(chunked)로 하위 배치로 분할하면 단일 트랜잭션이 너무 커져 다른 스레드를 장시간 차단하는 것을 방지합니다. 백그라운드 동기화의 경우 500개 레코드의 하위 배치 크기가 속도와 UI 응답성의 최적 균형을 제공합니다.
자주 묻는 질문
네, SQLite는 WAL 모드에서 다중 스레드 액세스를 지원합니다. 여러 스레드가 동시에 데이터를 읽을 수 있지만 쓰기는 하나만 가능합니다. Room과 GRDB는 동기화를 자동으로 관리합니다. 롤백 저널 모드(기본값)에서는 쓰기 작업 중에 데이터베이스가 완전히 잠깁니다.
SQLite의 제한은 281TB(이론적 최대치)입니다. 실제로 데이터베이스 크기는 장치의 사용 가능한 메모리에 의해 제한됩니다. 모바일 애플리케이션의 경우 편안한 크기는 최대 1~2GB입니다. 2GB를 초과하는 데이터베이스는 백업, App Store 업데이트를 느리게 하고 RAM 소비를 증가시킵니다.
SQLite는 기본적으로 데이터를 암호화하지 않습니다. 파일에 액세스할 수 있는 모든 프로세스가 데이터를 읽을 수 있습니다. 암호화를 위해 SQLCipher(AES-256 확장), EncryptedDatabase(Android)를 사용한 Room, 또는 iOS의 Encrypted Core Data를 사용하세요. 암호화는 데이터 읽기 및 쓰기 작업에 5~15%의 오버헤드를 추가합니다.
SQLite는 서버 프로세스가 필요 없는 임베디드 라이브러리입니다. MySQL은 별도의 서버, 사용자, 액세스 권한 및 네트워크 프로토콜이 있는 클라이언트-서버 DBMS입니다. SQLite는 단일 파일에 데이터베이스를 저장하고 MySQL은 서버가 관리하는 여러 파일에 저장합니다. SQLite는 더 간단하고 가벼우며 MySQL은 더 강력하고 확장 가능합니다.
SQLite 마이그레이션을 위해 ALTER TABLE(열 추가)을 사용하거나 데이터 전송 및 이전 테이블 삭제와 함께 새 테이블을 만드세요. Room은 Migration 클래스를 통해 이 프로세스를 자동화합니다. startVersion, endVersion 및 스키마 변경을 위한 SQL 쿼리를 지정하세요. GRDB와 FMDB도 유사한 DatabaseMigrator를 제공합니다.
요약
턴키 방식의 모바일 애플리케이션을 개발해 드립니다
IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.