ZetCode

Ruby 中的 SQLite 事务

最后修改于 2020 年 7 月 6 日

在本章中,我们将使用事务。首先,我们提供一些基本定义。然后,我们展示 Ruby 脚本,这些脚本演示了如何在 Ruby sqlite3 模块中使用事务。我们还将讨论自动提交模式,这对于理解事务至关重要。

定义

事务是对一个或多个数据库中数据的原子数据库操作单元。事务中所有 SQL 语句的效果要么全部提交到数据库,要么全部回滚。在自动提交模式下,更改立即生效。要使用事务,我们使用 transaction 方法启动一个事务。事务以 commitrollback 方法结束。

数据库连接默认处于自动提交模式。请注意,默认模式取决于驱动程序。在 SQLite Python 驱动程序中,默认关闭自动提交。

在 SQLite 中,除 SELECT 之外的任何命令都将启动一个隐式事务。此外,在一个事务中,像 CREATE TABLE ..., VACUUM, PRAGMA 这样的命令将在执行之前提交之前的更改。手动事务使用 BEGIN TRANSACTION 语句启动,并使用 COMMITROLLBACK 语句结束。

SQLite 支持三个非标准事务级别:DEFERREDIMMEDIATEEXCLUSIVE

示例

现在我们将有一些使用事务和自动提交模式的脚本。

#!/usr/bin/ruby

require 'sqlite3'

begin
    
    db = SQLite3::Database.open "test.db"
    
    db.execute "DROP TABLE IF EXISTS Friends"
    db.execute "CREATE TABLE Friends(Id INTEGER PRIMARY KEY, Name TEXT)"
    db.execute "INSERT INTO Friends(Name) VALUES ('Tom')"
    db.execute "INSERT INTO Friends(Name) VALUES ('Rebecca')"
    db.execute "INSERT INTO Friends(Name) VALUES ('Jim')"
    db.execute "INSERT INTO Friends(Name) VALUES ('Robert')"
    db.execute "INSERT INTO Friends(Name) VALUES ('Julian')"
    
rescue SQLite3::Exception => e 
    
    puts "Exception occurred"
    puts e
    
ensure
    db.close if db
end

我们创建一个 Friends 表并用数据填充它。我们没有显式地启动事务,也没有调用提交或回滚方法。然而,数据被写入到表中。这是因为默认工作模式是自动提交。在这种模式下,每个 SQL 语句都立即生效。

db.execute "DROP TABLE IF EXISTS Friends"
db.execute "CREATE TABLE Friends(Id INTEGER PRIMARY KEY, Name TEXT)"

如果 Friends 表已经存在,我们会删除它。然后我们用 CREATE TABLE 语句创建表。

db.execute "INSERT INTO Friends(Name) VALUES ('Tom')"
db.execute "INSERT INTO Friends(Name) VALUES ('Rebecca')"
...

我们插入数据。

$ ./autocommit.rb
$ sqlite3 test.db 
SQLite version 3.7.7 2011-06-23 19:49:22
Enter ".help" for instructions
Enter SQL statements terminated with a ";"
sqlite> SELECT * FROM Friends;
1|Tom
2|Rebecca
3|Jim
4|Robert
5|Julian

我们执行脚本并使用 sqlite3 命令行工具检查表。 Friends 表已成功创建。

在第二个示例中,我们将使用 transaction 方法启动一个事务。

#!/usr/bin/ruby

require 'sqlite3'

begin
    
    db = SQLite3::Database.open "test.db"

    db.transaction
    db.execute "DROP TABLE IF EXISTS Friends"
    db.execute "CREATE TABLE Friends(Id INTEGER PRIMARY KEY, Name TEXT)"
    db.execute "INSERT INTO Friends(Name) VALUES ('Tom')"
    db.execute "INSERT INTO Friends(Name) VALUES ('Rebecca')"
    db.execute "INSERT INTO Friends(Name) VALUES ('Jim')"
    db.execute "INSERT INTO Friends(Name) VALUES ('Robert')"
    db.execute "INSERT INTO Friends(Name) VALUES ('Julian')"
    db.execute "INSERT INTO Friends(Name) VALUES ('Michael')"
    db.commit
    
rescue SQLite3::Exception => e 
    
    puts "Exception occurred"
    puts e
    db.rollback
    
ensure
    db.close if db
end

我们重新创建 Friends 表。在调用 transaction 方法之后,每个语句都在一个事务中,直到我们调用 commit 方法。我们要么保存所有更改,要么什么都不保存。这是事务背后的基本思想。

db.transaction

transaction 方法开始一个新事务。该方法接受一个可选的模式参数,我们可以在其中指定事务级别。默认级别为 DEFERRED

db.commit

更改被写入到数据库。如果我们注释掉该行,则不会保存更改。

db.rollback

如果发生错误,我们会回滚更改。

sqlite> SELECT * FROM Friends;
1|Tom
2|Rebecca
3|Jim
4|Robert
5|Julian
6|Michael

我们使用 sqlite3 命令行工具验证更改是否被写入。

当事务中发生错误时,事务会被回滚,并且不会将任何更改提交到数据库。

#!/usr/bin/ruby

require 'sqlite3'

begin
    
    db = SQLite3::Database.open "test.db"

    db.transaction
    db.execute "UPDATE Friends SET Name='Thomas' WHERE Id=1"
    db.execute "UPDATE Friend SET Name='Bob' WHERE Id=4"
    db.commit
    
rescue SQLite3::Exception => e 
    
    puts "Exception occurred"
    puts e
    db.rollback
    
ensure
    db.close if db
end

在代码示例中,我们想更改两个名称。有两个语句构成一个事务。第二个 SQL 语句中存在错误。因此,事务被回滚。

db.execute "UPDATE Friend SET Name='Bob' WHERE Id=4"

表的名称不正确。数据库中没有 Friend 表。

$ ./rollingback.rb
Exception occurred
no such table: Friend

运行该示例将显示此错误消息。事务被回滚。

sqlite> SELECT * FROM Friends;
1|Tom
2|Rebecca
3|Jim
4|Robert
5|Julian

Friends 表未更改,即使第一个 UPDATE 语句是正确的。

我们将再次尝试更改两行,这次是在自动提交模式下。

#!/usr/bin/ruby

require 'sqlite3'

begin
    
    db = SQLite3::Database.new "test.db"
    
    db.execute "UPDATE Friends SET Name='Thomas' WHERE Id=1"
    db.execute "UPDATE Friend SET Name='Bob' WHERE Id=4"
    
rescue SQLite3::Exception => e 
    
    puts "Exception occurred"
    puts e
    
ensure
    db.close if db
end

我们尝试在 Friends 表中更新两个名称,将 Tom 更改为 Thomas,将 Robert 更改为 Bob。

db.execute "UPDATE Friends SET Name='Thomas' WHERE Id=1"
db.execute "UPDATE Friend SET Name='Bob' WHERE Id=4"

第二个 UPDATE 语句不正确。

$ ./autocommit2.rb
Exception occurred
no such table: Friend

我们收到与前一个示例相同的错误消息。

sqlite> SELECT * FROM Friends;
1|Thomas
2|Rebecca
3|Jim
4|Robert
5|Julian

但是这次,第一个 UPDATE 语句被保存。第二个语句没有被保存。

在 SQLite Ruby 教程的这一部分中,我们使用了事务。