作为一名 Python 开发者,我经常需要处理各种数据存储需求。从简单的脚本到中型项目,SQLite 一直是我的首选解决方案。它就像数据库界的瑞士军刀——小巧但功能齐全,无需额外配置就能直接使用。
SQLite 的最大优势在于它的"零配置"特性。与 MySQL 或 PostgreSQL 这些需要单独安装和配置的数据库系统不同,SQLite 直接以单个文件形式存储数据,Python 标准库中内置了 sqlite3 模块,这意味着你可以立即开始使用,无需任何额外安装。
在实际项目中,我发现 SQLite 特别适合以下场景:
提示:虽然 SQLite 功能强大,但它并不适合高并发的写入场景。如果你的应用需要处理大量并发写入(比如每秒超过 100 次写入),可能需要考虑客户端-服务器架构的数据库系统。
在 Python 中使用 SQLite 的第一步是建立连接。连接对象不仅负责与数据库文件的通信,还管理事务等重要功能。
python复制import sqlite3
# 建立数据库连接
# 如果文件不存在会自动创建
conn = sqlite3.connect('my_database.db')
# 创建游标对象
cursor = conn.cursor()
# 检查 SQLite 版本
cursor.execute('SELECT sqlite_version()')
version = cursor.fetchone()[0]
print(f"当前 SQLite 版本: {version}")
# 关闭连接
conn.close()
在实际开发中,我强烈建议使用上下文管理器(with 语句)来管理数据库连接,这样可以确保连接总是被正确关闭,即使在发生异常的情况下:
python复制with sqlite3.connect('my_database.db') as conn:
cursor = conn.cursor()
# 执行各种数据库操作
# 不需要显式调用 conn.close()
良好的表设计是数据库应用的基础。在 SQLite 中创建表时,需要考虑字段类型、约束和索引。
SQLite 支持以下几种主要数据类型:
下面是一个更完整的用户表示例,包含了各种约束和索引:
python复制create_users_table = """
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT NOT NULL UNIQUE,
password_hash TEXT NOT NULL,
email TEXT UNIQUE CHECK(email LIKE '%@%.%'),
age INTEGER CHECK(age >= 0),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
is_active BOOLEAN DEFAULT 1
);
"""
# 添加索引
create_index = "CREATE INDEX IF NOT EXISTS idx_users_username ON users (username);"
with sqlite3.connect('app.db') as conn:
conn.execute(create_users_table)
conn.execute(create_index)
conn.commit()
注意事项:SQLite 的 ALTER TABLE 功能有限,一旦表创建后,修改表结构会比较麻烦。因此,在设计阶段就要仔细考虑表结构,特别是主键和约束。
在实际项目中,我们经常需要插入大量数据。以下是几种不同的插入方法及其性能比较:
python复制cursor.execute("INSERT INTO products (name, price) VALUES (?, ?)", ('Product A', 19.99))
python复制products = [('Product B', 29.99), ('Product C', 39.99)]
cursor.executemany("INSERT INTO products (name, price) VALUES (?, ?)", products)
python复制def batch_insert(conn, data, batch_size=1000):
cursor = conn.cursor()
try:
for i in range(0, len(data), batch_size):
batch = data[i:i+batch_size]
cursor.executemany("INSERT INTO products (name, price) VALUES (?, ?)", batch)
conn.commit() # 每批提交一次
except Exception as e:
conn.rollback()
raise e
在我的性能测试中,对于 10,000 条记录:
SQLite 支持大多数标准 SQL 查询功能。以下是一些实用技巧:
python复制# 获取第2页,每页10条
page = 2
per_page = 10
cursor.execute("SELECT * FROM products ORDER BY id LIMIT ? OFFSET ?",
(per_page, (page-1)*per_page))
python复制search_term = '%apple%'
cursor.execute("SELECT * FROM products WHERE name LIKE ?", (search_term,))
python复制cursor.execute("""
SELECT category,
COUNT(*) as count,
AVG(price) as avg_price,
MAX(price) as max_price,
MIN(price) as min_price
FROM products
GROUP BY category
""")
SQLite 默认在自动提交模式下运行,但显式使用事务可以显著提高性能和数据一致性。
python复制try:
conn = sqlite3.connect('inventory.db')
cursor = conn.cursor()
# 开始事务
cursor.execute("BEGIN TRANSACTION")
# 执行多个操作
cursor.execute("UPDATE products SET stock = stock - ? WHERE id = ?", (quantity, product_id))
cursor.execute("INSERT INTO orders (product_id, quantity) VALUES (?, ?)", (product_id, quantity))
# 提交事务
conn.commit()
except Exception as e:
# 出错时回滚
conn.rollback()
print(f"操作失败: {e}")
finally:
conn.close()
python复制conn.execute("PRAGMA journal_mode = WAL") # 使用 Write-Ahead Logging 模式
conn.execute("PRAGMA synchronous = NORMAL") # 平衡安全性和性能
conn.execute("PRAGMA cache_size = -10000") # 设置缓存大小为 10MB
python复制# 为经常用于搜索和排序的列创建索引
cursor.execute("CREATE INDEX IF NOT EXISTS idx_products_category ON products (category)")
cursor.execute("CREATE INDEX IF NOT EXISTS idx_products_price ON products (price)")
python复制import sqlite3
from contextlib import contextmanager
@contextmanager
def get_db_connection():
conn = sqlite3.connect('app.db')
try:
yield conn
finally:
conn.close()
# 使用示例
with get_db_connection() as conn:
cursor = conn.cursor()
cursor.execute("SELECT * FROM users")
SQL 注入是最常见的安全漏洞之一。在 Python 中使用 SQLite 时,必须始终使用参数化查询:
python复制# 危险!绝对不要这样做
user_input = "admin' --"
cursor.execute(f"SELECT * FROM users WHERE username = '{user_input}'")
# 安全做法
cursor.execute("SELECT * FROM users WHERE username = ?", (user_input,))
对于表名或列名等不能使用参数的地方,应该使用白名单验证:
python复制def safe_query(table_name, column_name, value):
# 验证表名和列名是否合法
valid_tables = ['users', 'products']
valid_columns = ['id', 'name', 'email']
if table_name not in valid_tables or column_name not in valid_columns:
raise ValueError("Invalid table or column name")
cursor.execute(f"SELECT * FROM {table_name} WHERE {column_name} = ?", (value,))
虽然 SQLite 是单文件数据库,但仍需要定期备份:
python复制import shutil
import datetime
def backup_db(db_path, backup_dir):
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
backup_path = f"{backup_dir}/{os.path.basename(db_path)}.{timestamp}.bak"
shutil.copy2(db_path, backup_path)
python复制def backup_db(source_conn, target_path):
with sqlite3.connect(target_path) as target_conn:
source_conn.backup(target_conn)
python复制def export_to_sql(conn, output_file):
with open(output_file, 'w') as f:
for line in conn.iterdump():
f.write(f"{line}\n")
让我们开发一个完整的学生成绩管理系统,展示 SQLite 在实际项目中的应用。
python复制def initialize_database():
schema = """
CREATE TABLE IF NOT EXISTS students (
student_id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
gender TEXT CHECK(gender IN ('M', 'F')),
enrollment_date TEXT,
class TEXT
);
CREATE TABLE IF NOT EXISTS courses (
course_id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
credit INTEGER NOT NULL,
teacher TEXT
);
CREATE TABLE IF NOT EXISTS scores (
score_id INTEGER PRIMARY KEY AUTOINCREMENT,
student_id INTEGER NOT NULL,
course_id INTEGER NOT NULL,
score REAL CHECK(score >= 0 AND score <= 100),
exam_date TEXT,
FOREIGN KEY (student_id) REFERENCES students (student_id),
FOREIGN KEY (course_id) REFERENCES courses (course_id)
);
CREATE INDEX IF NOT EXISTS idx_scores_student ON scores (student_id);
CREATE INDEX IF NOT EXISTS idx_scores_course ON scores (course_id);
"""
with sqlite3.connect('school.db') as conn:
conn.executescript(schema)
python复制class GradeManager:
def __init__(self, db_path='school.db'):
self.conn = sqlite3.connect(db_path)
self.conn.row_factory = sqlite3.Row # 返回字典形式的结果
def add_student(self, name, gender, enrollment_date, class_name):
sql = """INSERT INTO students (name, gender, enrollment_date, class)
VALUES (?, ?, ?, ?)"""
self.conn.execute(sql, (name, gender, enrollment_date, class_name))
self.conn.commit()
return self.conn.execute("SELECT last_insert_rowid()").fetchone()[0]
def record_score(self, student_id, course_id, score, exam_date=None):
exam_date = exam_date or datetime.date.today().isoformat()
sql = """INSERT INTO scores (student_id, course_id, score, exam_date)
VALUES (?, ?, ?, ?)"""
self.conn.execute(sql, (student_id, course_id, score, exam_date))
self.conn.commit()
def get_student_grades(self, student_id):
sql = """SELECT c.name as course_name, s.score, s.exam_date, c.credit
FROM scores s
JOIN courses c ON s.course_id = c.course_id
WHERE s.student_id = ?"""
return self.conn.execute(sql, (student_id,)).fetchall()
def calculate_gpa(self, student_id):
grades = self.get_student_grades(student_id)
if not grades:
return 0.0
total_credits = sum(g['credit'] for g in grades)
weighted_sum = sum(g['score'] * g['credit'] for g in grades)
return weighted_sum / total_credits
def close(self):
self.conn.close()
python复制def demo_grade_system():
manager = GradeManager()
# 添加测试数据
math_id = manager.conn.execute(
"INSERT INTO courses (name, credit) VALUES ('Mathematics', 4) RETURNING course_id"
).fetchone()[0]
physics_id = manager.conn.execute(
"INSERT INTO courses (name, credit) VALUES ('Physics', 3) RETURNING course_id"
).fetchone()[0]
alice_id = manager.add_student("Alice", "F", "2023-09-01", "Class A")
bob_id = manager.add_student("Bob", "M", "2023-09-01", "Class B")
# 记录成绩
manager.record_score(alice_id, math_id, 95.5)
manager.record_score(alice_id, physics_id, 88.0)
manager.record_score(bob_id, math_id, 78.0)
manager.record_score(bob_id, physics_id, 92.5)
# 查询成绩
print("Alice的成绩单:")
for grade in manager.get_student_grades(alice_id):
print(f"{grade['course_name']}: {grade['score']} (学分: {grade['credit']})")
print(f"Alice的GPA: {manager.calculate_gpa(alice_id):.2f}")
manager.close()
SQLite 允许你使用 Python 函数扩展 SQL 功能。例如,创建一个计算统计模式的聚合函数:
python复制class Mode:
def __init__(self):
self.counts = {}
def step(self, value):
self.counts[value] = self.counts.get(value, 0) + 1
def finalize(self):
if not self.counts:
return None
return max(self.counts.items(), key=lambda x: x[1])[0]
conn = sqlite3.connect(':memory:')
conn.create_aggregate("mode", 1, Mode)
cursor = conn.cursor()
cursor.execute("CREATE TABLE test (value INTEGER)")
cursor.executemany("INSERT INTO test VALUES (?)", [(1,), (2,), (2,), (3,)])
cursor.execute("SELECT mode(value) FROM test")
print(cursor.fetchone()[0]) # 输出 2
现代 SQLite 版本(3.38.0+)内置了 JSON 支持:
python复制# 需要 SQLite 3.38.0 或更高版本
conn = sqlite3.connect(':memory:')
cursor = conn.cursor()
# 创建包含 JSON 列的表
cursor.execute("CREATE TABLE products (id INTEGER, details TEXT CHECK(json_valid(details)))")
# 插入 JSON 数据
product = {
"name": "Wireless Mouse",
"price": 29.99,
"specs": {"dpi": 1600, "buttons": 5, "wireless": True}
}
cursor.execute("INSERT INTO products VALUES (?, ?)",
(1, json.dumps(product)))
# 查询 JSON 字段
cursor.execute("SELECT json_extract(details, '$.name') FROM products")
print(cursor.fetchone()[0]) # 输出 "Wireless Mouse"
# 更新 JSON 字段
cursor.execute("""
UPDATE products
SET details = json_set(details, '$.price', 24.99)
WHERE id = 1
""")
SQLite 的 FTS5 扩展提供了全文搜索功能:
python复制# 创建虚拟表用于全文搜索
conn.execute("""
CREATE VIRTUAL TABLE IF NOT EXISTS docs USING fts5(
title,
content,
tokenize='porter unicode61'
)
""")
# 插入文档
docs = [
("Python Tutorial", "This tutorial covers Python basics and advanced topics."),
("SQLite Guide", "A comprehensive guide to SQLite database operations."),
("Web Development", "Building modern web applications with Python and JavaScript.")
]
conn.executemany("INSERT INTO docs VALUES (?, ?)", docs)
# 执行搜索
cursor = conn.cursor()
cursor.execute("SELECT * FROM docs WHERE docs MATCH 'python OR javascript'")
for row in cursor.fetchall():
print(row)
SQLite 使用文件级锁,当多个进程/线程同时访问时可能会遇到锁定问题。解决方案:
python复制conn.execute("PRAGMA journal_mode=WAL")
python复制# 设置 30 秒超时
conn = sqlite3.connect('app.db', timeout=30)
使用 EXPLAIN QUERY PLAN 分析 SQL 查询:
python复制cursor.execute("EXPLAIN QUERY PLAN SELECT * FROM users WHERE username = ?", ('test',))
for row in cursor.fetchall():
print(row)
输出示例:
code复制(0, 0, 0, 'SEARCH TABLE users USING INDEX idx_users_username (username=?)')
对于应用升级需要修改数据库结构的情况,可以使用以下模式:
python复制def migrate_database(conn):
user_version = conn.execute("PRAGMA user_version").fetchone()[0]
if user_version < 1:
# 初始版本升级
conn.executescript("""
CREATE TABLE IF NOT EXISTS users (...);
PRAGMA user_version = 1;
""")
if user_version < 2:
# 版本2升级
conn.executescript("""
ALTER TABLE users ADD COLUMN last_login TEXT;
PRAGMA user_version = 2;
""")
# 更多版本升级...
虽然直接使用 sqlite3 模块很灵活,但对于大型项目,ORM 可能更合适:
python复制from sqlalchemy import create_engine, Column, Integer, String
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
engine = create_engine('sqlite:///mydatabase.db')
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
name = Column(String)
email = Column(String)
Base.metadata.create_all(engine)
python复制from peewee import *
db = SqliteDatabase('mydatabase.db')
class User(Model):
name = CharField()
email = CharField(unique=True)
class Meta:
database = db
db.connect()
db.create_tables([User])
python复制import dataset
db = dataset.connect('sqlite:///mydatabase.db')
table = db['users']
table.insert({'name': 'John', 'email': 'john@example.com'})
在多年的 Python 开发中,我总结了以下 SQLite 使用心得:
文件位置选择:将数据库文件放在用户可写目录(如 appdata 或用户主目录),而不是程序目录,避免权限问题。
备份策略:除了定期备份数据库文件,还可以实现自动导出到 SQL 或 CSV 的功能。
性能监控:对于长期运行的应用,记录查询执行时间,识别性能瓶颈。
内存数据库:对于临时数据处理,可以使用 :memory: 数据库:
python复制conn = sqlite3.connect(':memory:')
多线程注意事项:每个线程应该创建自己的连接,不要在线程间共享连接对象。
数据类型处理:SQLite 使用动态类型系统,Python 类型到 SQLite 类型的映射需要特别注意:
日期时间处理:SQLite 没有专门的日期类型,通常存储为 TEXT(ISO8601)、REAL(Julian day)或 INTEGER(Unix 时间戳)
python复制# 日期处理示例
from datetime import datetime
# 存储为 ISO 字符串
now = datetime.now().isoformat()
cursor.execute("INSERT INTO events (name, event_time) VALUES (?, ?)",
("Meeting", now))
# 查询并转换回 datetime
cursor.execute("SELECT event_time FROM events WHERE name = ?", ("Meeting",))
event_time = datetime.fromisoformat(cursor.fetchone()[0])
python复制def sql_trace_callback(sql):
print(f"执行SQL: {sql}")
conn = sqlite3.connect('app.db')
conn.set_trace_callback(sql_trace_callback)
数据库大小限制:虽然 SQLite 理论上支持最大 140TB 的数据库,但实际上受文件系统限制。对于超过几十GB的数据库,应考虑其他解决方案。
跨平台注意事项:SQLite 数据库文件在不同操作系统间是兼容的,但要注意:
在我的实际项目中,SQLite 已经成功应用于:
对于 Python 开发者来说,掌握 SQLite 意味着你拥有了一个随时可用的强大数据存储工具,能够在各种场景下快速实现数据持久化需求。