---
title: SQLite 也能扛生产：索引、事务与分页的正确姿势
url: https://vabora.dev/posts/sqlite-production-practices
author: Vabora
published: 2026-09-10 09:05:53
updated: 2026-09-27 10:33:42
category: 技术分享
tags: PHP 8, 数据库
canonical: https://vabora.dev/posts/sqlite-production-practices
license: 转载与引用请注明出处并保留原文链接
---

# SQLite 也能扛生产：索引、事务与分页的正确姿势

> 个人博客、内部工具、边缘站点，SQLite 往往比 MySQL 更合适。前提是你要知道它的边界。 什么时候选 SQLite 单实例部署、读多写少 数据量在百万行以内 希望零运维、备份就是一个文件 必须开的三个开关 WAL 让读写不互相阻塞，是性能的关键。 索引只建在这三类字段上…
个人博客、内部工具、边缘站点，SQLite 往往比 MySQL 更合适。前提是你要知道它的边界。

## 什么时候选 SQLite

- 单实例部署、读多写少
- 数据量在百万行以内
- 希望零运维、备份就是一个文件

## 必须开的三个开关

```php
$connection->exec('PRAGMA journal_mode = WAL');
$connection->exec('PRAGMA synchronous = NORMAL');
$connection->exec('PRAGMA foreign_keys = ON');
```

WAL 让读写不互相阻塞，是性能的关键。

## 索引只建在这三类字段上

1. 查询条件列：`status`、`published_at`
2. 唯一约束列：`slug`
3. 关联列：`post_id`、`tag_id`

## 分页别用 OFFSET 深翻

```php
// 差：OFFSET 越大越慢
SELECT * FROM posts ORDER BY id DESC LIMIT 10 OFFSET 10000;

// 好：游标分页
SELECT * FROM posts WHERE id < :cursor ORDER BY id DESC LIMIT 10;
```

## 事务包裹写入

```php
Database::transaction(function (Connection $connection): void {
    $post_id = Database::table('posts')->insertGetId($row);
    Database::table('post_tag')->insertBatch($links);
});
```

## 小结

SQLite 的问题通常不是性能，而是**并发写入**与**连接管理**。用 WAL + 短事务，大多数场景足够。
