Perl 完全指南 / 第 16 章:数据库编程
第 16 章:数据库编程
“数据是应用的核心”
Perl 通过 DBI(Database Interface)模块访问各种数据库。本章涵盖 DBI 基础、ORM 框架 DBIx::Class 以及连接池管理。
16.1 DBI 基础
连接数据库
use strict;
use warnings;
use DBI;
# 连接 SQLite
my $dbh = DBI->connect(
"dbi:SQLite:dbname=mydata.db", # DSN
"", # 用户名
"", # 密码
{
RaiseError => 1, # 错误时 die
AutoCommit => 1, # 自动提交
PrintError => 0, # 不打印错误(由 RaiseError 处理)
}
) or die "连接失败: $DBI::errstr\n";
常见 DSN 格式
| 数据库 | DSN 格式 |
|---|
| SQLite | dbi:SQLite:dbname=file.db |
| MySQL | dbi:mysql:database=db;host=localhost;port=3306 |
| PostgreSQL | dbi:Pg:database=db;host=localhost;port=5432 |
| Oracle | dbi:Oracle:SID=orcl;host=localhost;port=1521 |
16.2 执行 SQL
增删改查
# 创建表
$dbh->do(<<'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 注入)
$dbh->do(
"INSERT INTO users (name, email, age) VALUES (?, ?, ?)",
undef,
"张三", "zhangsan\@example.com", 30
);
# 更新
my $rows = $dbh->do(
"UPDATE users SET age = ? WHERE name = ?",
undef,
31, "张三"
);
print "更新了 $rows 行\n";
# 删除
$dbh->do("DELETE FROM users WHERE age < ?", undef, 18);
SELECT 查询
# 准备并执行
my $sth = $dbh->prepare("SELECT id, name, email, age FROM users WHERE age > ?");
$sth->execute(18);
# 逐行获取
while (my $row = $sth->fetchrow_hashref()) {
printf "ID: %d, 姓名: %s, 邮箱: %s\n",
$row->{id}, $row->{name}, $row->{email};
}
# 获取所有行
$sth->execute(18);
my $all = $sth->fetchall_arrayref({}); # 数组引用(哈希引用)
# 获取单个值
my $count = $dbh->selectrow_array(
"SELECT COUNT(*) FROM users WHERE age > ?", undef, 18
);
fetch 方法对比
| 方法 | 返回值 | 适用场景 |
|---|
fetchrow_array() | 列表 | 已知列数 |
fetchrow_arrayref() | 数组引用 | 性能敏感 |
fetchrow_hashref() | 哈希引用 | 需要列名 |
fetchall_arrayref() | 所有行 | 批量处理 |
selectrow_array() | 单行 | 查单个值 |
selectall_arrayref() | 所有行 | 快速查询 |
16.3 事务处理
# 手动事务
$dbh->{AutoCommit} = 0;
eval {
$dbh->do("UPDATE accounts SET balance = balance - 100 WHERE id = 1");
$dbh->do("UPDATE accounts SET balance = balance + 100 WHERE id = 2");
$dbh->commit();
};
if ($@) {
warn "事务失败: $@";
$dbh->rollback();
}
16.4 连接池 — DBIx::Connector
use DBIx::Connector;
my $conn = DBIx::Connector->new(
"dbi:mysql:database=mydb;host=localhost",
"user", "password",
{
RaiseError => 1,
AutoCommit => 1,
}
);
# 自动重连
$conn->run(sub {
my $dbh = $_; # 数据库句柄
my $rows = $dbh->do("UPDATE ...");
});
# 事务模式
$conn->txn(sub {
my $dbh = $_;
$dbh->do("INSERT ...");
$dbh->do("UPDATE ...");
# 自动 commit 或 rollback
});
16.5 DBIx::Class — ORM
定义 Schema
# lib/MyApp/Schema.pm
package MyApp::Schema;
use base 'DBIx::Class::Schema';
__PACKAGE__->load_namespaces();
1;
# lib/MyApp/Schema/Result/User.pm
package MyApp::Schema::Result::User;
use base 'DBIx::Class::Core';
__PACKAGE__->table('users');
__PACKAGE__->add_columns(
id => { data_type => 'integer', is_auto_increment => 1 },
name => { data_type => 'text', is_nullable => 0 },
email => { data_type => 'text', is_nullable => 0 },
age => { data_type => 'integer', default_value => 0 },
);
__PACKAGE__->set_primary_key('id');
__PACKAGE__->add_unique_constraint(['email']);
__PACKAGE__->has_many(posts => 'MyApp::Schema::Result::Post', 'user_id');
1;
使用 ORM
use MyApp::Schema;
my $schema = MyApp::Schema->connect("dbi:SQLite:dbname=mydata.db");
# 创建
my $user = $schema->resultset('User')->create({
name => "张三",
email => "zhangsan\@example.com",
age => 30,
});
# 查询
my $rs = $schema->resultset('User')->search(
{ age => { '>', 18 } },
{ order_by => { -asc => 'name' } }
);
while (my $row = $rs->next) {
printf "%s (%d)\n", $row->name, $row->age;
}
# 更新
$user->update({ age => 31 });
# 删除
$user->delete();
16.6 业务场景:数据迁移脚本
#!/usr/bin/env perl
use strict;
use warnings;
use DBI;
use Text::CSV;
my $csv = Text::CSV->new({ binary => 1 });
my $dbh = DBI->connect("dbi:SQLite:dbname=import.db", "", "", {
RaiseError => 1, AutoCommit => 0,
});
$dbh->do(<<'SQL');
CREATE TABLE IF NOT EXISTS products (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
price REAL NOT NULL,
category TEXT
)
SQL
my $sth = $dbh->prepare(
"INSERT INTO products (name, price, category) VALUES (?, ?, ?)"
);
open my $fh, '<:encoding(UTF-8)', 'products.csv' or die $!;
my $header = $csv->getline($fh); # 跳过表头
my ($imported, $errors) = (0, 0);
while (my $row = $csv->getline($fh)) {
eval {
$sth->execute($row->[0], $row->[1], $row->[2]);
$imported++;
};
if ($@) {
warn "导入失败: @$row - $@\n";
$errors++;
}
}
close $fh;
$dbh->commit();
$dbh->disconnect();
print "导入完成: $imported 成功, $errors 失败\n";
本章小结
| 要点 | 内容 |
|---|
| DBI | Perl 数据库标准接口 |
占位符 ? | 防止 SQL 注入 |
RaiseError | 推荐的错误处理方式 |
| DBIx::Connector | 连接池与自动重连 |
| DBIx::Class | Perl 的 ORM 框架 |
| 事务 | 手动 commit/rollback 或使用 DBIx::Connector |
练习
- 使用 DBI 创建一个 SQLite 数据库,建表并插入 10 条数据
- 实现参数化查询,防止 SQL 注入
- 编写事务脚本:模拟银行转账
- 使用 DBIx::Class 定义一对多关系
- 编写 CSV 导入脚本,带错误处理和统计
扩展阅读