DEV Community

Cover image for SQLite Limitations and Internal Architecture
Athreya aka Maneshwar
Athreya aka Maneshwar

Posted on

SQLite Limitations and Internal Architecture

Hello, I'm Maneshwar. I'm working on FreeDevTools online currently building **one place for all dev tools, cheat codes, and TLDRs* — a free, open-source hub where developers can quickly find and use tools without any hassle of searching all over the internet.*

In the previous post, I explored how SQLite ensures correctness through strict concurrency control and a robust journaling-based recovery mechanism.

That discussion showed how SQLite favors safety and simplicity over aggressive parallelism.

Today’s learning completes the picture by looking at where SQLite intentionally draws the line its limitations and how its internal architecture is designed to support those trade-offs cleanly and predictably.

SQLite Limitations

Despite its strengths, SQLite is not designed to be a universal replacement for enterprise database systems.

Its primary design goal has always been simplicity, and the development team consistently prioritizes this goal, even when it leads to restricted functionality or less efficient implementations.

Understanding these limitations is critical to using SQLite correctly.

Limited SQL Feature Set

SQLite does not fully implement all ANSI SQL-92 features.

While it supports a large and practical subset of SQL, some advanced constructs available in enterprise databases are intentionally omitted.

These omissions are documented and deliberate, ensuring SQLite remains:

  • Small in code size
  • Easy to reason about
  • Simple to embed

No General Transaction Nesting

SQLite supports only flat transactions.

While it does implement statement-level subtransactions internally, it does not support true nested transactions where subtransactions are fully independent execution environments.

This limits complex transactional workflows but greatly simplifies recovery and locking logic.

Low Transaction Concurrency

SQLite uses database-level locking, not row-level or table-level locks.

As a result:

  • Many read transactions may run concurrently
  • Only one write transaction may exist at any time
  • Readers block writers
  • Writers block everyone

This makes SQLite unsuitable for workloads with heavy concurrent writes or long-running transactions.

Application-Level Constraints

Because of its concurrency model, SQLite performs best when:

  • Transactions are small
  • Database work completes quickly
  • Locks are held only for milliseconds

Applications that require:

  • High write throughput
  • Fine-grained locking
  • Long-running transactions

are better served by client–server DBMSs such as PostgreSQL or MySQL.

SQLite is not meant to replace enterprise databases, it is meant to eliminate complexity where it is unnecessary.

NFS and Network File System Issues

SQLite relies on native OS file locking. Many NFS implementations have known bugs in their locking behavior.

If file locking does not work correctly:

  • Multiple writers may modify the database concurrently
  • Database corruption can occur

Additionally, NFS latency can severely degrade performance.

In networked environments, a client–server database model is usually a better choice.

No Stored Procedures

SQLite does not support stored procedures.

Unlike enterprise DBMSs, logic cannot be encapsulated and stored inside the database itself.

All procedural logic must live in the application layer. This is a conscious design decision that keeps SQLite small and portable.

SQLite Architecture Overview

SQLite’s internal design mirrors its philosophy: modular, layered, and minimal.

The architecture consists of seven major modules, divided into two logical halves:

  • Frontend (SQL compilation)
  • Backend (SQL execution)

Applications interact with SQLite only through a single, unified interface layer.

image

Frontend: SQL Compilation Pipeline

The frontend is responsible for transforming SQL text into executable bytecode.

It consists of three modules:

Tokenizer

The tokenizer splits incoming SQL text into tokens keywords, identifiers, operators, and literals.

This is the first stage of understanding an SQL statement.

Parser and Optimizer

The parser:

  • Analyzes token structure
  • Builds a parse tree
  • Validates SQL syntax and semantics

The optimizer then restructures the parse tree to produce an equivalent but more efficient execution plan.

Code Generator

The code generator traverses the optimized parse tree and produces an internal bytecode program.

This bytecode is what SQLite’s backend actually executes.

Together, these steps are implemented through the sqlite3_prepare API call.

Backend: Execution Engine

The backend executes bytecode programs and performs all physical database operations.

It consists of four tightly integrated modules:

Virtual Machine (VM)

The VM executes bytecode instructions produced by the frontend.

  • It Drives query execution
  • Manipulates tables and indexes
  • Coordinates access to storage

From the VM’s perspective, a database is a collection of tuple sets.

Tree Module

The tree module organizes data using:

  • B+ trees for tables
  • B-trees for indexes

It supports efficient searching, insertion, deletion, and updates, and allows the VM to create and destroy database objects.

Pager Module

The pager is one of SQLite’s most critical components.

  • It implements page-based access over byte-oriented files
  • Maintains an in-memory page cache
  • Handles file locking
  • Manages journaling and transactions

In effect, the pager acts as SQLite’s data manager, lock manager, log manager, and transaction manager.

Operating System Interface

This is a thin abstraction layer over the host OS.

It provides:

  • File I/O
  • Mutexes
  • Timing
  • Random number generation
  • Thread primitives

Thanks to this layer, SQLite remains highly portable across operating systems.

The SQLite Interface Layer

Applications do not interact directly with the frontend or backend.

Instead, all access flows through the SQLite interface layer, which:

  • Accepts API calls
  • Routes them to the appropriate internal modules
  • Enforces correct usage patterns

This clean separation is a major reason SQLite remains stable and easy to embed.

Closing Thoughts

SQLite’s limitations are not weaknesses exactly they are deliberate design boundaries.

  • By restricting concurrency
  • Avoiding stored procedures
  • Using database-level locks
  • Favoring simplicity over feature depth

SQLite achieves remarkable reliability, portability, and ease of use.

Understanding both its constraints and architecture makes it clear why SQLite excels in embedded systems, tools, and applications where correctness and simplicity matter more than scale.

My experiments and hands-on executions related to SQLite will live here: lovestaco/sqlite

References:

SQLite Database System: Design and Implementation. N.p.: Sibsankar Haldar, (n.d.).

FreeDevTools

👉 Check out: FreeDevTools

Any feedback or contributors are welcome!

It’s online, open-source, and ready for anyone to use.

⭐ Star it on GitHub: freedevtools

Top comments (0)