事物隔离
SQL标准定义了四种隔离级别,如下:
在各个级别上被禁止出现的现象是:
脏读
一个事务读取了另一个并行未提交事务写入的数据。
不可重复读
一个事务重新读取之前读取过的数据,发现该数据已经被另一个事务(在初始读之后提交)修改。
幻读
一个事务重新执行一个返回符合一个搜索条件的行集合的查询, 发现满足条件的行集合因为另一个最近提交的事务而发生了改变。
序列化异常
成功提交一组事务的结果与这些事务所有可能的串行执行结果都不一致。
用例
以pg为例,进行测试,先查看默认的隔离级别
psql (12.0 (Debian 12.0-2.pgdg100+1)) Type "help" for help. postgres=# show transaction_isolation; transaction_isolation ----------------------- read committed (1 row)
读未提交
表示可以读到其他会话未提交的数据(postgresql不支持)。
读已提交
表示可以读到其他会话已提交的数据。
1.创建一张表为test,插入一条记录
postgres=# create table test(id int,name char(10)); CREATE TABLE postgres=# insert into test values(1,'tom'); INSERT 0 1
2.在会话1中打开事务进行查询
postgres=# begin; BEGIN postgres=# select * from test; id | name ----+------------ 1 | tom (1 row)
3.在会话2中打开事务进行更新
postgres=# begin; BEGIN postgres=# update test set name='jack' where id=1; UPDATE 1 postgres=#
4.此时在会话2中还没有关闭事务,在会话1中进行查询
postgres=# select * from test; id | name ----+------------ 1 | tom (1 row)
5.发现会话1中的记录并没有进行改变。当提交会话2中的事务,在会话1中进行查询值已经改变
postgres=# update test set name='jack' where id=1; UPDATE 1 postgres=# commit; COMMIT
再次查询
postgres=# select * from test; id | name ----+------------ 1 | jack (1 row)
可重复读
表示在一个事务中,执行同一条SQL,读到的是同样的数据(即使被读的数据可能已经被其他会话修改并提交)。
先修改隔离级别
postgres=# set default_transaction_isolation='repeatable read'; SET postgres=# show transaction_isolation; transaction_isolation ----------------------- repeatable read (1 row)
在会话1中打开可重复读事务,进行查询
postgres=# select * from test; id | name ----+------------ 1 | jack (1 row)
在会话2中进行更新操作
postgres=# update test set name='pg' where id=1; UPDATE 1
在会话1中进行查询,发现会话1中的记录没有因为会话2的提交而变化
postgres=# select * from test; id | name ----+------------ 1 | jack (1 row)
在会话1中进行提交,再查询,发现会话1中的记录变化了
postgres=# commit; COMMIT postgres=# select * from test; id | name ----+------------ 1 | pg (1 row)
串行化
表示并行事务模拟串行执行,违反串行执行规则的事务,将回滚。
1.在会话 1中打开事务串行
postgres=# begin transaction isolation level serializable; BEGIN
2.在会话2中打开事务串行
postgres=# begin transaction isolation level serializable; BEGIN
3.在会话1中进行插入操作
postgres=# insert into test select * from test; INSERT 0 1 postgres=# select * from test; id | name ----+------------ 1 | pg 1 | pg (2 rows)
4.在会话2中进行插入操作
postgres=# insert into test select * from test; INSERT 0 1 postgres=# select * from test; id | name ----+------------ 1 | pg 1 | pg (2 rows)
5.提交会话1中的事务,能够正常提交,进行查询能够查到插入的记录
postgres=# commit; COMMIT postgres=# select * from test; id | name ----+------------ 1 | pg 1 | pg (2 rows)
6.当提交会话2的事务时会报错
postgres=# commit; ERROR: could not serialize access due to read/write dependencies among transactions DETAIL: Reason code: Canceled on identification as a pivot, during commit attempt. HINT: The transaction might succeed if retried.