SQLite是一款轻量级的嵌入式数据库,整个数据库就是一个单独的文件,不需要单独安装数据库服务进程,非常适合小型项目、脚本工具、原型开发或者单元测试场景。在Ruby中操作SQLite,最常用的方式就是通过sqlite3这个gem。本文将完整讲解sqlite3 gem的安装、基本的增删改查、事务处理以及一些常见的进阶用法和踩坑经验。

一、安装sqlite3 gem与环境准备
在安装gem之前,需要确保系统里已经有SQLite的本地库。大多数Linux发行版和macOS都自带SQLite,可以直接在终端执行sqlite3 --version验证。如果系统缺少SQLite开发头文件(常见于Ubuntu等系统),gem安装时会编译原生扩展,这时需要先安装对应的开发包:
# Ubuntu / Debian sudo apt-get install libsqlite3-dev # CentOS / RHEL sudo yum install sqlite-devel # macOS(一般自带,若缺失可用Homebrew) brew install sqlite
然后执行gem安装命令:
gem install sqlite3
如果是在Rails项目中使用,通常直接在Gemfile中添加gem 'sqlite3', '~> 1.7',再执行bundle install即可。需要注意的是,sqlite3 gem从2.x版本开始要求Ruby 3.0以上,如果你的Ruby版本较旧,可以锁定1.x版本,这是实践中非常常见的一个兼容性问题。
安装完成后可以用下面的代码验证:
require 'sqlite3'
db = SQLite3::Database.new ':memory:'
puts db.execute('SELECT SQLITE_VERSION()').first.first
db.close这段代码在内存数据库中查询SQLite版本号,如果正常输出版本字符串,说明gem已经可以正常工作。
二、数据库的创建与基本增删改查
sqlite3 gem的核心类是SQLite3::Database。当打开一个不存在的数据库文件路径时,SQLite会自动创建这个文件,这一点和MySQL等需要手动建库的数据库不太一样。下面演示一个完整的基本操作流程:
require 'sqlite3'
# 打开(或创建)数据库文件
db = SQLite3::Database.new 'test.db'
# 建表
db.execute <<~SQL
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
email TEXT UNIQUE,
age INTEGER DEFAULT 0
);
SQL
# 插入数据(使用参数绑定,避免SQL注入)
db.execute 'INSERT INTO users (name, email, age) VALUES (?, ?, ?)',
['张三', 'zhangsan@ipipp.com', 28]
# 也可以使用命名参数
db.execute 'INSERT INTO users (name, email, age) VALUES (:name, :email, :age)',
{ name: '李四', email: 'lisi@ipipp.com', age: 34 }
# 查询,execute返回数组的数组
db.execute('SELECT * FROM users').each do |row|
puts row.inspect
end
db.close这里要特别强调参数绑定的写法。永远不要用字符串拼接的方式构造SQL,例如"INSERT INTO users VALUES ('#{name}')",这种写法存在严重的SQL注入风险。使用?占位符配合参数数组,SQLite驱动会自动处理转义,既安全又省事。
查询方面,execute返回的结果是二维数组,每个元素对应一行。如果想让结果按哈希形式返回,字段名作为键,可以设置results_as_hash属性:
db.results_as_hash = true
db.execute('SELECT * FROM users').each do |row|
puts "#{row['name']} 的邮箱是 #{row['email']}"
end除了execute,gem还提供了几个便捷方法:get_first_value用于取单个值(比如统计数量),execute2会额外返回字段名数组,prepare则用于预编译语句。当需要循环插入大量数据时,预编译语句的性能优势会非常明显,因为SQL解析只发生一次。
三、事务处理与批量写入优化
SQLite默认每执行一条写语句就自动提交一次事务,而每次提交都会触发磁盘IO。这意味着如果用循环插入一万条数据,就会产生一万次磁盘写入,速度会慢到难以接受。正确的做法是把批量写操作包在一个显式事务里:
require 'sqlite3'
db = SQLite3::Database.new 'batch.db'
db.execute 'CREATE TABLE IF NOT EXISTS logs (id INTEGER PRIMARY KEY, msg TEXT)'
# 写法一:使用transaction块
data = Array.new(10000) { |i| "日志条目 #{i}" }
db.transaction do
data.each do |msg|
db.execute 'INSERT INTO logs (msg) VALUES (?)', [msg]
end
end
# 块正常结束自动提交,抛出异常则自动回滚事务块的行为是:块内代码全部执行成功则自动提交,一旦抛出异常则自动回滚,保证数据的原子性。也可以手动控制事务,使用db.transaction、db.commit和db.rollback三个方法的组合。
进一步提升性能的办法是结合预编译语句,插入一万条数据的耗时可以从数秒压缩到几十毫秒级别:
stmt = db.prepare 'INSERT INTO logs (msg) VALUES (?)'
db.transaction do
data.each do |msg|
stmt.execute msg
end
end
stmt.close
db.close另一个容易被忽略的点是资源释放。数据库连接和预编译语句用完之后都应该调用close,虽然Ruby的垃圾回收器最终会回收它们,但显式关闭是一个好习惯,尤其在长期运行的程序里可以避免文件句柄堆积。也可以使用SQLite3::Database.open配合块的形式,块结束时自动关闭连接:
SQLite3::Database.open 'test.db' do |db| puts db.get_first_value 'SELECT COUNT(*) FROM users' end # 块结束后连接自动关闭
四、常见报错与注意事项
实际使用中,几个高频报错值得提前了解。第一是SQLite3::BusyException,SQLite整库级别只有一把写锁,当多个进程或多个连接同时写入时就会出现这个错误。解决办法包括重试写入、使用db.busy_timeout = 5000设置等待时间(单位毫秒),或者在打开数据库时传入results_as_hash之外加上WAL模式:
db = SQLite3::Database.new 'app.db' db.busy_timeout = 5000 db.execute 'PRAGMA journal_mode = WAL'
WAL模式允许读写并发,能显著缓解多线程场景下的锁冲突问题,这是SQLite调优中最常用的手段之一。
第二是SQLite3::SQLException: no such table,通常是数据库文件路径不对导致的。SQLite在路径不存在时会静默创建一个空库,程序不会报错,直到查询时才发现表不存在。因此建议在代码里固定使用绝对路径或者基于__dir__的相对路径,避免工作目录变化引起的问题。
第三点是多线程使用:同一个SQLite3::Database连接默认不能在多个线程中共享,否则可能抛出SQLite3::CantOpenException或产生段错误。如果确实需要多线程访问,可以在打开连接时设置Database.new(path, results_as_hash: true, readonly: false)并通过每线程独立连接的方式,或者借助连接池管理。理解这些边界条件后,sqlite3 gem在中小规模项目里会是非常趁手的工具。
Ruby sqlite3 gemSQLite数据库Ruby数据库操作修改时间:2026-09-06 10:16:39