<commit_before>## Rails 中的事务处理

> 本文主要介绍一下 Rails 中事务的使用方法。

### 隔离性（isolation）：
`脏读`：事务A修改了一个数据，但未提交，事务 B 读到了事务 A 未提交的更新结果，如果事务 A 提交失败，事务 B 读到的就是脏数据。
`不可重复读`：在同一个事务中，对于同一份数据读取到的结果不一致。比如，事务 B 在事务A提交前读到的结果，和提交后读到的结果可能不同。不可重复读出现的原因就是事务并发修改记录，要避免这种情况，最简单的方法就是对要修改的记录加锁，这回导致锁竞争加剧，影响性能。另一种方法是通过 MVCC 可以在无锁的情况下，避免不可重复读。
`幻读`：在同一个事务中，同一个查询多次返回的结果不一致。事务 A 新增了一条记录，事务 B 在事务 A 提交前后各执行了一次查询操作，发现后一次比前一次多了一条记录。幻读是由于并发事务增加记录导致的，这个不能像不可重复读通过记录加锁解决，因为对于新增的记录根本无法加锁。需要将事务串行化，才能避免幻读。

>作者：郭无心
>链接：https://www.zhihu.com/question/31346392/answer/59815366
>来源：知乎
>著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。

为了避免以上问题，Rails 的 `ActiveRecord::ConnectionAdapters::DatabaseStatements#transaction` 方法允许传入三个参数`requires_new: nil, isolation: nil, joinable: true`，其中 isolation 是来设置事务的隔离性的。isolation 的等级有如下四种：
- :read_uncommitted: 未提交读，读脏数据
- :read_committed:
- :repeatable_read: 避免不可重复读
- :serializable: 避免幻读

> read_committed: 读操作之前首先申请并获得共享锁，允许其他读操作读取该锁定的数据，但是写操作必须等待锁释放，一般读操作读取完就会立刻释放共享锁。**注意**：但是由于READ COMMITTED读操作一完成就立即释放共享锁，读操作不会在一个事务过程中保持共享锁，也就是说在一个事务的的两个查询过程之间有另一个回话对数据资源进行了更改，会导致一个事务的两次查询得到的结果不一致,这种现象称之为不可重复读。
```ruby
ActiveRecord::Schema.define do
  create_table :orders, force: true do |t|
    t.integer :stock, default: 0
  end
end<commit_msg>update doc<commit_after>## Rails 中的事务处理

> 本文主要介绍一下 Rails 中事务的使用方法。

### 隔离性（isolation）：
- `脏读`：事务A修改了一个数据，但未提交，事务 B 读到了事务 A 未提交的更新结果，如果事务 A 提交失败，事务 B 读到的就是脏数据。
- `不可重复读`：在同一个事务中，对于同一份数据读取到的结果不一致。比如，事务 B 在事务A提交前读到的结果，和提交后读到的结果可能不同。不可重复读出现的原因就是事务并发修改记录，要避免这种情况，最简单的方法就是对要修改的记录加锁，这回导致锁竞争加剧，影响性能。另一种方法是通过 MVCC 可以在无锁的情况下，避免不可重复读。
- `幻读`：在同一个事务中，同一个查询多次返回的结果不一致。事务 A 新增了一条记录，事务 B 在事务 A 提交前后各执行了一次查询操作，发现后一次比前一次多了一条记录。幻读是由于并发事务增加记录导致的，这个不能像不可重复读通过记录加锁解决，因为对于新增的记录根本无法加锁。需要将事务串行化，才能避免幻读。

>作者：郭无心
>链接：https://www.zhihu.com/question/31346392/answer/59815366
>来源：知乎
>著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。

为了避免以上问题，Rails 的 `ActiveRecord::ConnectionAdapters::DatabaseStatements#transaction` 方法允许传入三个参数`requires_new: nil, isolation: nil, joinable: true`，其中 isolation 是来设置事务的隔离性的。isolation 的等级有如下四种：
- :read_uncommitted: 未提交读，读脏数据
- :read_committed:
- :repeatable_read: 避免不可重复读
- :serializable: 避免幻读

> read_committed: 读操作之前首先申请并获得共享锁，允许其他读操作读取该锁定的数据，但是写操作必须等待锁释放，一般读操作读取完就会立刻释放共享锁。**注意**：但是由于READ COMMITTED读操作一完成就立即释放共享锁，读操作不会在一个事务过程中保持共享锁，也就是说在一个事务的的两个查询过程之间有另一个回话对数据资源进行了更改，会导致一个事务的两次查询得到的结果不一致,这种现象称之为不可重复读。

```ruby
ActiveRecord::Schema.define do
  create_table :orders, force: true do |t|
    t.integer :stock, default: 0
  end
end
{'fraction_non_alphanumeric': 0.10778688524590165, 'fraction_numerical': 0.013934426229508197, 'mean_word_length': 10.460093896713616, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 15, 'https://': 2, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7188145', 'n_tokens_mistral': 1806, 'n_tokens_neox': 2083, 'n_words': 138}