SQLite 入门指南:从零到精通轻量级数据库
数据库是现代软件应用中不可或缺的核心组件,用于存储、管理和检索数据。在众多数据库系统中,SQLite 以其独特的优势占据了一席之地,尤其在嵌入式系统、移动应用、桌面软件以及需要轻量级、零配置数据存储的场景中广受欢迎。
如果你是数据库领域的初学者,或者正在寻找一个简单易用、无需独立服务器的数据库解决方案,那么 SQLite 绝对是一个极好的起点。本指南将带你从零开始,逐步深入理解 SQLite 的概念、使用方法以及如何在实际应用中与其交互。
目录
-
SQLite 是什么?为何选择它?
- 什么是 SQLite?核心特性介绍
- SQLite 的优势:为何如此受欢迎?
- SQLite 的局限性
- 常见的应用场景
-
快速入门:安装与第一个数据库
- 安装 SQLite:不同操作系统的获取方式
- 使用命令行工具
sqlite3
- 创建或打开一个数据库文件
- 基本命令行操作 (
.help
,.quit
,.tables
,.schema
)
-
数据库基础概念
- 数据库文件 (
.db
) - 表 (Table)、列 (Column) 和行 (Row)
- 数据类型 (Datatypes)
- 主键 (Primary Key) 与约束 (Constraints)
- SQL:与数据库交互的语言
- 数据库文件 (
-
SQL 实战:数据操作核心 (CRUD)
- 创建表 (
CREATE TABLE
) - 插入数据 (
INSERT INTO
) - 查询数据 (
SELECT
)- 基本查询 (
SELECT *
,SELECT column1, ...
) - 过滤 (
WHERE
子句) - 排序 (
ORDER BY
) - 限制结果数量 (
LIMIT
,OFFSET
) - 聚合函数 (
COUNT
,SUM
,AVG
,MIN
,MAX
) - 分组 (
GROUP BY
)
- 基本查询 (
- 更新数据 (
UPDATE
) - 删除数据 (
DELETE
) - 删除表 (
DROP TABLE
)
- 创建表 (
-
稍微进阶:索引与事务
- 索引 (Indexes):提升查询性能
- 什么是索引?
- 创建索引 (
CREATE INDEX
) - 何时使用索引?
- 事务 (Transactions):确保数据一致性
- 什么是事务?ACID 特性
- 事务命令 (
BEGIN
,COMMIT
,ROLLBACK
) - 一个简单的事务示例
- 索引 (Indexes):提升查询性能
-
与编程语言交互 (以 Python 为例)
- 为何需要编程接口?
- Python 的
sqlite3
模块 - 连接数据库、创建游标
- 执行 SQL 语句
- 获取查询结果
- 提交与回滚事务
- 关闭连接
- 参数化查询:防止 SQL 注入 (重要!)
- 一个完整的 Python 示例
-
SQLite 工具推荐
- 命令行工具
sqlite3
- 图形用户界面 (GUI) 工具 (例如:DB Browser for SQLite)
- 命令行工具
-
使用技巧与注意事项
- 备份数据库
VACUUM
命令- 理解 WAL 模式 (Write-Ahead Logging)
- 性能优化初步
- SQL 注入防范 (再次强调)
-
总结与展望
1. SQLite 是什么?为何选择它?
什么是 SQLite?核心特性介绍
SQLite 是一个轻量级、无服务器、自包含、高可靠性、全功能的 SQL 数据库引擎。与传统的数据库系统(如 MySQL、PostgreSQL、SQL Server)需要一个独立的服务器进程来管理数据不同,SQLite 将整个数据库存储在一个单一的文件中。它是一个 C 语言库,可以直接嵌入到应用程序中,应用程序通过调用函数来访问和操作数据库。
核心特性概览:
- 无服务器 (Serverless): 没有独立的服务器进程。应用程序直接读写数据库文件。这简化了部署和管理。
- 自包含 (Self-Contained): 只需要一个 C 库文件(或少量文件),无需外部依赖。
- 零配置 (Zero-Configuration): 无需安装、配置或启动服务器。数据库文件即数据库。
- 事务性 (Transactional): 支持完整的 ACID 事务,确保数据的一致性、完整性和持久性。
- 高可靠性 (Highly Reliable): 经过严格测试,在多种异常情况下(如崩溃、断电)也能保持数据完整。
- 跨平台 (Cross-Platform): 可以在几乎所有操作系统上运行。
- 公共领域 (Public Domain): 完全免费使用,无需许可费用,可用于任何目的。
- 单文件数据库: 整个数据库(包括定义、表、索引和数据)存储在一个普通磁盘文件中。
SQLite 的优势:为何如此受欢迎?
- 易于使用和部署: 无需安装服务器,直接复制数据库文件即可使用,极大地简化了应用程序的部署和分发。
- 零管理: 大部分情况下,你不需要数据库管理员。数据库没有用户、权限、配置等复杂概念(与传统服务器数据库相比)。
- 轻量级: 库文件体积小,资源消耗低,非常适合资源受限的环境。
- 可靠性高: 设计时就考虑了鲁棒性,能在各种异常情况(如程序崩溃、操作系统崩溃、断电)下保护数据不被破坏。
- 速度快: 对于许多常见的读操作,SQLite 的速度可以与文件系统读写相媲美。对于中小型数据集或读多写少的场景,性能表现优秀。
- SQL 支持: 支持大部分标准的 SQL92 语法,学习成本低,易于从其他数据库迁移。
- 文件格式: 数据库文件是跨平台的,可以直接复制到不同系统上使用。
SQLite 的局限性
虽然 SQLite 优点众多,但它并非万能的,在某些场景下可能不如传统的客户端-服务器数据库:
- 并发写入性能: 虽然支持并发读,但在同一时间点进行大量并发写入时,性能不如客户端-服务器数据库。通常情况下,整个数据库文件会被锁定进行写入操作。不过,通过 WAL 模式可以改善这一情况。
- 网络访问: SQLite 数据库不能直接通过网络访问,只能由访问数据库文件的同一台机器上的应用程序使用。如果需要网络访问,你需要在应用程序中构建一个服务层。
- 用户与权限管理: SQLite 没有内置的用户账户和权限管理系统,数据的访问控制依赖于文件系统的权限。
常见的应用场景
正是因为其独特的优势和局限性,SQLite 在以下场景中大放异彩:
- 嵌入式设备: 机顶盒、消费电子、物联网设备等。
- 移动应用: iOS (Core Data)、Android (SQLiteDatabase) 都内置了 SQLite。
- 桌面应用程序: 作为本地数据存储,例如浏览器 (Chrome, Firefox)、邮件客户端 (Thunderbird)、本地音乐播放器等。
- 文件格式: 可以用作复杂文档的文件格式(例如:Firefox Places 历史记录,Adobe Lightroom)。
- 网站数据存储: 对于流量不大、读操作远多于写操作的网站,SQLite 可以作为一种简单高效的后端数据库。
- 开发和测试: 作为开发过程中的临时数据库或测试数据库,部署和重置都非常方便。
- 数据分析: 用于处理小型到中型的数据集。
2. 快速入门:安装与第一个数据库
SQLite 的安装通常非常简单,甚至在很多系统上已经预装。
安装 SQLite:不同操作系统的获取方式
- Windows: 前往 SQLite 官方网站 (https://www.sqlite.org/download.html) 下载预编译的二进制文件 (
sqlite-tools-win64-x64-*.zip
或sqlite-tools-win32-x86-*.zip
)。解压后,将包含sqlite3.exe
的目录添加到系统的 PATH 环境变量中,或者直接进入该目录使用。 - macOS: macOS 系统通常内置了 SQLite。打开终端即可直接使用
sqlite3
命令。 - Linux: 大多数 Linux 发行版也预装了 SQLite 或者可以通过包管理器轻松安装。例如,在 Debian/Ubuntu 上:
sudo apt-get install sqlite3
;在 Fedora/CentOS/RHEL 上:sudo yum install sqlite
或sudo dnf install sqlite
。
使用命令行工具 sqlite3
sqlite3
是 SQLite 官方提供的命令行工具,是学习和管理 SQLite 数据库最直接的方式。
打开终端或命令提示符,输入 sqlite3
并按回车。
bash
$ sqlite3
SQLite version 3.36.0 2021-06-18 14:11:39
Enter ".help" for usage hints.
Connected to a transient in-memory database.
Use ".open FILENAME" to reopen on a persistent database.
sqlite>
这表示你已经进入了 SQLite 命令行界面。此时你连接的是一个临时的内存数据库。
创建或打开一个数据库文件
要创建一个持久化的数据库文件,或者打开一个已有的数据库文件,可以在启动 sqlite3
时指定文件名:
bash
$ sqlite3 my_first_database.db
SQLite version 3.36.0 2021-06-18 14:11:39
Enter ".help" for usage hints.
sqlite>
如果在当前目录下不存在 my_first_database.db
文件,SQLite 会自动创建一个。如果文件已经存在,SQLite 会打开它。现在,你在这个界面中执行的所有数据库操作(如创建表、插入数据)都将保存在这个文件中。
你也可以在进入 sqlite3
界面后再打开一个文件,使用 .open
命令:
sql
sqlite> .open my_another_database.db
基本命令行操作
sqlite3
命令行工具支持一些点开头的特殊命令,用于控制工具的行为,而不是执行 SQL 语句。
.help
:显示所有可用的点命令列表及其用途。.quit
或.exit
:退出 SQLite 命令行界面。.databases
:显示当前连接的数据库及其文件路径。.tables
:显示当前数据库中的所有表名。.schema [table_name]
:显示指定表的创建语句(即表结构),如果不指定表名,则显示所有表的结构。.mode MODE
:设置输出模式,例如.mode column
使输出对齐,.mode list
(默认) 用逗号分隔,.mode html
输出 HTML 表格等。.headers on
/.headers off
:开启/关闭在结果中显示列头。.show
:显示当前的设置(模式、头信息等)。
示例:
sql
sqlite> .help
... (一大串帮助信息) ...
sqlite> .databases
main: /path/to/my_first_database.db
sqlite> .tables
-- 此时应该没有表
sqlite> .quit
3. 数据库基础概念
在开始编写 SQL 语句之前,理解一些基本的数据库概念是必要的。
- 数据库文件 (.db): 在 SQLite 中,整个数据库的所有内容都存储在一个独立的磁盘文件中,通常以
.db
或.sqlite
作为扩展名。 - 表 (Table): 数据库中的基本组织单位,用来存储具有相同结构的数据集合。想象它像电子表格一样,由行和列组成。
- 列 (Column): 定义了表中存储的数据的类型和名称。每一列代表数据的一个属性。例如,在一个
users
表中,可能有id
,name
,age
等列。 - 行 (Row): 表中的一条记录,代表一个独立的数据项。每一行包含了表中所有列的数据。
- 数据类型 (Datatypes): 定义了列中可以存储的数据种类。SQLite 的数据类型系统比较灵活,称为“动态类型系统”或“类型亲和性”。尽管你可以声明列为特定的类型(如 INT, TEXT, VARCHAR),但 SQLite 实际上使用以下五种基本存储类来存储数据:
NULL
: 值是 NULL。INTEGER
: 带符号的整数,根据数值大小存储为 1、2、3、4、6 或 8 字节。REAL
: 浮点数值,存储为 8 字节 IEEE 浮点数。TEXT
: 文本字符串,使用数据库编码 (UTF-8, UTF-16LE 或 UTF-16BE) 存储。BLOB
: 二进制大对象 (Binary Large Object),数据完全按照输入存储(例如,图片、音频文件)。- 当你声明列类型时,SQLite 会根据声明的类型字符串,为其分配一个“类型亲和性”,指导它如何存储该列的数据。例如
INT
,INTEGER
,TINYINT
等会具有INTEGER
亲和性;CHAR
,VARCHAR
,TEXT
等具有TEXT
亲和性;REAL
,DOUBLE
,FLOAT
具有REAL
亲和性;BLOB
没有亲和性;其他类型如DATETIME
,BOOLEAN
等会根据其名称被赋予NUMERIC
或TEXT
亲和性。
- 主键 (Primary Key): 一列或一组列,其值能唯一标识表中的每一行。主键列的值不能重复,且通常是非空的 (
NOT NULL
)。在 SQLite 中,如果一个表的INTEGER PRIMARY KEY
列没有提供值,或者提供 NULL,SQLite 会自动生成一个唯一的、递增的整数作为该行的 ID。这通常称为 ROWID。 - 约束 (Constraints): 定义了表中数据必须满足的规则,用于维护数据的一致性和完整性。常见的约束有:
NOT NULL
: 该列的值不能为空。UNIQUE
: 该列的所有值都必须是唯一的。DEFAULT value
: 如果插入新行时该列没有提供值,则使用指定的默认值。CHECK expression
: 确保该列的值满足指定的布尔表达式。FOREIGN KEY
: 定义了与其他表之间关系的约束(虽然本入门指南不深入探讨,但知道它的存在很重要)。
- SQL (Structured Query Language): 结构化查询语言,是与关系型数据库交互的标准语言,用于定义、操作和控制数据。我们将主要使用 SQL 语句来创建表、插入、查询、更新和删除数据。
4. SQL 实战:数据操作核心 (CRUD)
CRUD 是指数据库操作中最核心的四种功能:Create (创建)、Read (读取)、Update (更新) 和 Delete (删除)。我们将学习如何使用 SQL 语句在 SQLite 中执行这些操作。
创建表 (CREATE TABLE
)
在存储数据之前,首先需要定义数据的结构,即创建表。
语法:
sql
CREATE TABLE table_name (
column1_name datatype [constraints],
column2_name datatype [constraints],
...
[PRIMARY KEY (column_name) | PRIMARY KEY (column1, column2, ...)]
[FOREIGN KEY (column_name) REFERENCES other_table(other_column)]
);
示例:创建一个 users
表来存储用户信息
“`sql
— 连接到数据库文件(如果还没连接)
— $ sqlite3 my_first_database.db
— 在 sqlite> 提示符下输入以下语句:
CREATE TABLE users (
id INTEGER PRIMARY KEY, — INTEGER PRIMARY KEY 通常自动递增且唯一
name TEXT NOT NULL UNIQUE, — 姓名是文本类型,不能为空,且必须唯一
age INTEGER DEFAULT 0, — 年龄是整数类型,默认为0
email TEXT — 电子邮件是文本类型,可以为空
);
— 验证表是否创建成功
.tables
— 应该看到: users
— 查看表结构
.schema users
— 应该看到创建表的 SQL 语句
“`
插入数据 (INSERT INTO
)
向表中添加新的数据行。
语法 1:指定列名和对应的值
sql
INSERT INTO table_name (column1, column2, ...)
VALUES (value1, value2, ...);
语法 2:为所有列提供值 (值的顺序必须与创建表时列的顺序一致)
sql
INSERT INTO table_name
VALUES (value1, value2, ...);
示例:向 users
表插入数据
“`sql
— 插入 Alice 的信息,指定列
INSERT INTO users (name, age, email) VALUES (‘Alice’, 30, ‘[email protected]’);
— 插入 Bob 的信息,为所有列提供值 (id 会自动生成)
INSERT INTO users VALUES (NULL, ‘Bob’, 25, ‘[email protected]’); — 使用 NULL 让 id 自动生成
— 插入 Charlie 的信息,只提供必须的列
INSERT INTO users (name) VALUES (‘Charlie’); — age 将使用默认值 0,email 为 NULL
— 插入多行数据 (部分数据库支持,SQLite 支持)
INSERT INTO users (name, age) VALUES
(‘David’, 22),
(‘Eve’, 35);
``
INTEGER PRIMARY KEY
注意:对于,插入
NULL` 或省略该列都会触发自动生成行为。
查询数据 (SELECT
)
从表中检索数据。这是最常用也是功能最强大的 SQL 命令。
语法:
sql
SELECT column1, column2, ... -- 或 * 表示所有列
FROM table_name
[WHERE condition] -- 过滤条件
[GROUP BY column1, ...] -- 分组
[HAVING condition] -- 分组后的过滤条件
[ORDER BY column1 [ASC|DESC], ...] -- 排序
[LIMIT count [OFFSET offset]]; -- 限制结果数量
方括号 []
表示可选部分。
示例:查询 users
表中的数据
“`sql
— 查询所有列和所有行
SELECT * FROM users;
— 查询特定列 (姓名和年龄)
SELECT name, age FROM users;
— 查询年龄大于 25 岁的用户 (使用 WHERE 过滤)
SELECT * FROM users WHERE age > 25;
— 查询姓名是 ‘Alice’ 的用户
SELECT * FROM users WHERE name = ‘Alice’;
— 查询年龄在 20 到 30 岁之间的用户 (使用 AND 和 BETWEEN)
SELECT * FROM users WHERE age >= 20 AND age <= 30;
SELECT * FROM users WHERE age BETWEEN 20 AND 30;
— 查询姓名以 ‘A’ 开头的用户 (使用 LIKE 和通配符 %)
SELECT * FROM users WHERE name LIKE ‘A%’; — % 匹配任意长度字符
— 查询姓名倒数第二个字母是 ‘i’ 的用户 (使用 LIKE 和通配符 )
SELECT * FROM users WHERE name LIKE ‘%i‘; — _ 匹配单个字符
— 查询没有电子邮件的用户 (使用 IS NULL)
SELECT * FROM users WHERE email IS NULL;
— 查询有电子邮件的用户 (使用 IS NOT NULL)
SELECT * FROM users WHERE email IS NOT NULL;
— 查询年龄大于 25 岁,并按年龄从低到高排序
SELECT * FROM users WHERE age > 25 ORDER BY age ASC; — ASC 可省略,默认升序
— 查询所有用户,按年龄从高到低排序
SELECT * FROM users ORDER BY age DESC;
— 查询所有用户,先按年龄升序,年龄相同时按姓名降序
SELECT * FROM users ORDER BY age ASC, name DESC;
— 查询年龄最大的 3 个用户
SELECT * FROM users ORDER BY age DESC LIMIT 3;
— 查询年龄最大的用户 (另一种方式,结合 ORDER BY 和 LIMIT)
SELECT * FROM users ORDER BY age DESC LIMIT 1;
— 查询总共有多少用户 (使用聚合函数 COUNT)
SELECT COUNT(*) FROM users;
— 查询所有用户的平均年龄 (使用聚合函数 AVG)
SELECT AVG(age) FROM users;
— 查询年龄最小和最大的用户的年龄 (使用聚合函数 MIN 和 MAX)
SELECT MIN(age), MAX(age) FROM users;
— 查询不同年龄组的人数 (使用 GROUP BY)
SELECT age, COUNT(*) FROM users GROUP BY age;
— 查询人数多于 1 人的年龄组 (使用 GROUP BY 和 HAVING)
SELECT age, COUNT() FROM users GROUP BY age HAVING COUNT() > 1;
“`
更新数据 (UPDATE
)
修改表中已有数据行的值。
语法:
sql
UPDATE table_name
SET column1 = value1, column2 = value2, ...
[WHERE condition]; -- 指定要更新哪些行
示例:更新 users
表中的数据
“`sql
— 将 Alice 的年龄改为 31
UPDATE users SET age = 31 WHERE name = ‘Alice’;
— 将所有年龄小于 10 岁的用户的年龄设置为 10
UPDATE users SET age = 10 WHERE age < 10;
— 将所有用户的年龄都增加 1
UPDATE users SET age = age + 1;
— 注意:如果没有 WHERE 子句,UPDATE 将会影响表中的所有行!
“`
删除数据 (DELETE
)
从表中删除数据行。
语法:
sql
DELETE FROM table_name
[WHERE condition]; -- 指定要删除哪些行
示例:删除 users
表中的数据
“`sql
— 删除年龄小于 20 岁的用户
DELETE FROM users WHERE age < 20;
— 删除姓名为 ‘Bob’ 的用户
DELETE FROM users WHERE name = ‘Bob’;
— 注意:如果没有 WHERE 子句,DELETE 将会删除表中的所有行!
DELETE FROM users; — 删除 users 表中的所有数据,但表结构还在
“`
删除表 (DROP TABLE
)
彻底删除数据库中的一个表及其所有数据。
语法:
sql
DROP TABLE table_name;
示例:删除 users
表
“`sql
— 删除 users 表
DROP TABLE users;
— 再次查看表列表,users 应该不见了
.tables
“`
重要提示: DROP TABLE
是一个不可逆的操作,会永久删除表及其数据,请谨慎使用。
5. 稍微进阶:索引与事务
了解索引和事务对于构建更高效、更可靠的数据库应用至关重要。
索引 (Indexes):提升查询性能
想象一本书的目录或索引。当你需要查找特定主题时,你会先查阅索引,找到对应的页码,然后直接翻到那一页,而不是从头到尾翻遍全书。数据库索引的作用类似。
什么是索引?
数据库索引是一种数据结构(如 B-树),它可以加快数据库表中数据的检索速度。索引是基于表中的一列或多列创建的。
创建索引 (CREATE INDEX
)
语法:
sql
CREATE INDEX index_name ON table_name (column1 [ASC|DESC], column2 [ASC|DESC], ...);
示例:
“`sql
— 为 users 表的 age 列创建索引,以便快速查询特定年龄范围的用户
CREATE INDEX idx_users_age ON users (age);
— 为 users 表的 name 列创建唯一索引,确保 name 列的值唯一 (虽然我们在 CREATE TABLE 时已经用了 UNIQUE,这里只是示例)
CREATE UNIQUE INDEX idx_users_name ON users (name); — UNIQUE 关键字确保索引值唯一
“`
何时使用索引?
- 经常用于 WHERE 子句中进行过滤的列。
- 经常用于 JOIN 操作的列。
- 经常用于 ORDER BY 子句中进行排序的列。
- 主键列会自动创建索引。
何时不使用索引?
- 表很小: 对于只有几十或几百行的表,全表扫描可能比使用索引更快或一样快。
- 经常进行大量写入 (INSERT, UPDATE, DELETE) 的表: 每次数据修改时,索引也需要更新,这会带来额外的开销。索引越多,写入越慢。
- 包含大量重复值的列: 比如一个只有 “男” 和 “女” 两个值的性别列,索引的区分度太低,效果不佳。
- 不会用于查询条件的列。
索引是以空间换时间:它们会占用额外的磁盘空间,但能显著提高查询速度。在 SQLite 中,索引存储在同一个 .db
文件中。
事务 (Transactions):确保数据一致性
事务是一组数据库操作,被视为单个逻辑工作单元。事务中的所有操作要么全部成功提交,要么全部失败回滚,回到事务开始前的状态。
什么是事务?ACID 特性
事务具有 ACID 特性,这是数据库系统可靠性的基石:
- 原子性 (Atomicity): 事务是一个不可分割的最小工作单元,其中的操作要么全做,要么全不做。
- 一致性 (Consistency): 事务应使数据库从一个一致性状态转换到另一个一致性状态。例如,转账事务执行前后,总金额不变。
- 隔离性 (Isolation): 并发执行的事务之间互不干扰,每个事务感觉自己是系统中唯一在运行的事务。
- 持久性 (Durability): 事务一旦提交,其所做的修改就是永久的,即使系统发生故障也不会丢失。
事务命令 (BEGIN
, COMMIT
, ROLLBACK
)
在 SQLite 中,默认情况下,每条 SQL 语句都是一个独立的事务并立即提交(这被称为自动提交模式)。对于需要多个步骤协作完成的操作,你需要显式地开始和结束事务。
BEGIN TRANSACTION;
或BEGIN;
: 开始一个新事务。COMMIT;
: 提交当前事务,使所有修改永久生效。ROLLBACK;
: 回滚当前事务,撤销所有未提交的修改。
一个简单的事务示例 (模拟转账)
假设有一个 accounts
表,包含 name
(TEXT), balance
(REAL) 两列。我们要从 Alice 的账户转 100 元给 Bob。这个操作包括两个步骤:Alice 余额减少,Bob 余额增加。这两个步骤必须同时成功或同时失败。
“`sql
— 模拟数据
CREATE TABLE accounts (name TEXT PRIMARY KEY, balance REAL);
INSERT INTO accounts VALUES (‘Alice’, 1000.0);
INSERT INTO accounts VALUES (‘Bob’, 500.0);
— 查看初始余额
SELECT * FROM accounts;
— 输出:
— Alice|1000.0
— Bob|500.0
— 开始一个事务
BEGIN TRANSACTION;
— 尝试从 Alice 账户扣款
UPDATE accounts SET balance = balance – 100 WHERE name = ‘Alice’;
— 模拟一个可能发生的错误,例如 Bob 账户不存在,或者其他操作失败
— 如果这里发生错误,后面的 COMMIT 就不会执行,我们可以手动 ROLLBACK
— 尝试给 Bob 账户加款
UPDATE accounts SET balance = balance + 100 WHERE name = ‘Bob’;
— 如果一切顺利,提交事务
COMMIT;
— 如果在中间步骤发生错误(例如,假设 Bob 的 UPDATE 语句写错了),
— 我们会执行 ROLLBACK 代替 COMMIT:
— ROLLBACK;
— 查看最终余额 (假设事务成功提交)
SELECT * FROM accounts;
— 输出:
— Alice|900.0
— Bob|600.0
— 如果我们 ROLLBACK 了,余额会回到初始状态
— Alice|1000.0
— Bob|500.0
“`
使用事务可以确保即使在操作过程中发生中断(如程序崩溃),数据库也能保持一致状态。
6. 与编程语言交互 (以 Python 为例)
在实际应用中,我们很少直接在命令行中操作数据库,而是通过编程语言来控制。几乎所有主流编程语言都有 SQLite 的驱动或库。这里以 Python 为例,因为它内置了 sqlite3
模块,无需额外安装。
为何需要编程接口?
- 动态构建 SQL: 根据程序逻辑生成不同的查询。
- 处理数据: 将数据库查询结果集成到程序的数据结构中进行进一步处理。
- 用户交互: 根据用户输入执行数据库操作并将结果展示给用户。
- 自动化任务: 定时或事件触发的数据库维护和数据处理。
Python 的 sqlite3
模块
Python 标准库提供了 sqlite3
模块,用于与 SQLite 数据库交互。
基本流程:
- 导入
sqlite3
模块。 - 使用
sqlite3.connect()
函数连接到数据库文件。如果文件不存在,会创建一个新的。 - 获取一个游标对象 (
connection.cursor()
)。游标用于执行 SQL 命令和获取结果。 - 使用游标对象的
execute()
方法执行 SQL 语句。 - 如果执行的是
SELECT
查询,使用游标对象的fetchone()
,fetchall()
, 或fetchmany()
方法获取结果。 - 如果执行的是修改操作 (
INSERT
,UPDATE
,DELETE
),需要调用连接对象的commit()
方法来保存修改。如果不需要保存,可以调用rollback()
回滚。 - 使用
try...except...finally
或with
语句处理异常并确保连接和游标被正确关闭。 - 最后,调用连接对象的
close()
方法关闭连接。
一个完整的 Python 示例
“`python
import sqlite3
import os
定义数据库文件路径
db_file = ‘my_app_database.db’
如果数据库文件已存在,先删除以便重新开始示例
if os.path.exists(db_file):
os.remove(db_file)
print(f”Deleted existing database file: {db_file}”)
conn = None # 初始化连接对象
try:
# 1. 连接数据库 (如果文件不存在则创建)
# 使用 with 语句可以确保连接和游标自动关闭,推荐使用
with sqlite3.connect(db_file) as conn:
# 2. 获取游标对象
cursor = conn.cursor()
# 3. 创建表 (如果不存在)
cursor.execute('''
CREATE TABLE IF NOT EXISTS products (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL UNIQUE,
price REAL DEFAULT 0.0
)
''')
print("Table 'products' created or already exists.")
# 4. 插入数据
# 插入单条数据
cursor.execute("INSERT INTO products (name, price) VALUES (?, ?)", ('Laptop', 1200.00)) # 使用占位符 ? 防止 SQL 注入
# 插入多条数据
products_list = [
('Keyboard', 75.00),
('Mouse', 25.00),
('Monitor', 300.00)
]
cursor.executemany("INSERT INTO products (name, price) VALUES (?, ?)", products_list) # executemany 用于插入多行
print("Data inserted into 'products' table.")
# 5. 查询数据
print("\n--- All products ---")
cursor.execute("SELECT * FROM products")
rows = cursor.fetchall() # 获取所有结果行
for row in rows:
print(row)
print("\n--- Products cheaper than $100 ---")
cursor.execute("SELECT name, price FROM products WHERE price < ?", (100.00,)) # WHERE 条件带参数
rows_cheap = cursor.fetchall()
for row in rows_cheap:
print(f"Name: {row[0]}, Price: {row[1]}")
print("\n--- Count of products ---")
cursor.execute("SELECT COUNT(*) FROM products")
count = cursor.fetchone()[0] # fetchone() 返回一个元组,取第一个元素
print(f"Total products: {count}")
# 6. 更新数据
cursor.execute("UPDATE products SET price = ? WHERE name = ?", (1300.00, 'Laptop'))
print("\nUpdated Laptop price.")
# 7. 删除数据
cursor.execute("DELETE FROM products WHERE name = ?", ('Mouse',))
print("Deleted Mouse.")
# 8. 再次查询确认修改
print("\n--- Products after update and delete ---")
cursor.execute("SELECT * FROM products")
rows_final = cursor.fetchall()
for row in rows_final:
print(row)
# with 语句块结束时,如果一切正常,会自动 COMMIT;如果发生异常,会自动 ROLLBACK。
# 如果不使用 with 语句,你需要手动 conn.commit() 或 conn.rollback()
except sqlite3.Error as e:
print(f”Database error: {e}”)
if conn:
conn.rollback() # 发生错误时回滚
except Exception as e:
print(f”An error occurred: {e}”)
finally:
# with 语句会确保连接关闭,但如果不用 with,这里需要手动关闭
# if conn:
# conn.close()
print(“\nDatabase connection managed.”)
“`
参数化查询 (防止 SQL 注入):
在上面的 Python 示例中,注意我们在 INSERT
和 SELECT
语句中使用了 ?
占位符,并将实际的值作为 execute()
方法的第二个参数(一个元组或列表)传递。这是防止 SQL 注入攻击的关键方法! 永远不要直接将用户输入或其他变量通过字符串格式化(如 f-string)拼接到 SQL 语句中。使用参数化查询,数据库驱动会负责正确地处理特殊字符和数据类型。
7. SQLite 工具推荐
除了命令行工具 sqlite3
,还有许多图形用户界面 (GUI) 工具可以帮助你更方便地管理 SQLite 数据库。
sqlite3
(命令行): 轻量级,安装方便,适合快速查看和执行简单命令,或者在自动化脚本中使用。- DB Browser for SQLite: 免费、开源、跨平台,功能强大且易于使用。提供了直观的界面来创建和修改表、索引、执行 SQL 查询、浏览和编辑数据、导入导出数据等。强烈推荐初学者使用。
- SQLiteStudio: 另一个免费、开源、跨平台的 SQLite GUI 工具,功能也很全面。
- 各种 IDE/编辑器插件: 许多集成开发环境 (IDE) 和代码编辑器(如 VS Code, PyCharm)都有支持 SQLite 的插件,可以直接在开发环境中管理数据库。
选择一个适合你的工具可以大大提高开发效率。
8. 使用技巧与注意事项
- 备份数据库: 备份 SQLite 数据库非常简单,只需要复制
.db
文件即可。在程序运行时进行备份可能需要注意文件锁定问题,可以考虑在程序关闭时备份,或者使用.backup
命令(通过编程接口或命令行)。 VACUUM
命令: 当你删除数据后,数据库文件的大小可能不会立即减小,被删除的空间会被标记为可用,但仍然占用磁盘。执行VACUUM;
命令可以重组数据库文件,回收未使用的空间,减小文件体积。这可能会比较慢,不应频繁执行。- 理解 WAL 模式: 默认情况下,SQLite 使用回滚日志模式。写入时,整个数据库可能会被锁定。启用 Write-Ahead Logging (WAL) 模式 (
PRAGMA journal_mode=WAL;
) 可以允许多个读取者与一个写入者并发工作,提高并发性能。但 WAL 模式会产生额外的-wal
和-shm
文件。 - 性能优化初步: 对于慢查询,首先考虑是否能添加合适的索引。分析查询语句是否复杂,考虑简化。
- SQL 注入防范: 再次强调,通过编程语言与 SQLite 交互时,务必使用参数化查询,不要直接拼接字符串。
- 错误处理: 在编程中,总是要捕获数据库操作可能抛出的异常,并进行适当的处理(例如回滚事务,记录错误信息)。
9. 总结与展望
通过本指南的学习,你应该对 SQLite 有了一个全面的初步认识。我们了解了它的核心特性、优势、局限性,学习了如何安装和使用命令行工具,掌握了数据库的基本概念以及如何使用 SQL 进行核心的 CRUD 操作。此外,我们还初步接触了索引和事务的概念,并通过 Python 示例了解了如何在代码中与 SQLite 交互,以及重要的安全实践——参数化查询。
SQLite 是一个强大而灵活的工具,尤其适合作为应用的本地数据存储。它的简单性使得数据库不再是一个高门槛的技术栈,即便是初学者也能快速上手。
这只是一个开始。你可以继续深入学习:
- 更复杂的 SQL 查询,例如 JOIN (多表连接)、子查询、窗口函数等。
- 更高级的事务管理和锁定机制。
- 如何根据具体应用场景进行性能调优。
- 与其他编程语言(如 Java, C#, Node.js 等)的 SQLite 接口。
- SQLite 的内部原理,例如 WAL 模式的细节、索引的实现方式等。
实践是最好的老师。现在就开始尝试使用 SQLite 来存储和管理你的数据吧!无论是开发一个小工具,制作一个桌面应用,还是为你的移动应用添加本地存储,SQLite 都是一个值得信赖的伙伴。
希望这篇详细的指南能帮助你顺利迈出 SQLite 学习的第一步!