下面通过实例介绍这四种隔离级别。
首先,准备工作, 我使用的数据库是Sybase, 我们在数据库里建一个测试表,并插入数据:
create table test_table (
col1 int,
col2 varchar(15)
)
go
alter table test_table lock datarows
go
create unique index ux_idx1 on test_table (col1)
go
insert test_table (col1, col2) values (1, "test")
insert test_table (col1, col2) values (2, "test")
insert test_table (col1, col2) values (3, "test")
insert test_table (col1, col2) values (4, "test")
insert test_table (col1, col2) values (5, "test")
go
col1 col2
----------- ---------------
1 test
2 test
3 test
4 test
5 test
(5 rows affected)
注意上面建表的时候,把表的锁改为了行模式
alter table test_table lock datarows
go
现在打开两个窗口, 分别叫做窗口A,和窗口B。
设置窗口A的事务隔离级别为 0 (读未提交)
SET TRANSACTION ISOLATION LEVEL 0
go
begin tran
update test_table
set col2 = "TEST UPDATE"
where col1 = 1
go
我们看到输出:
(1 row affected)
这时,在窗口A,执行如下SQL,查看同一条记录(col1=1)的值:
select * from test_table where col1 = 1
go
col1 col2
----------- ---------------
1 TEST UPDATE
(1 row affected)
下面介绍读已提交,这时候,把窗口A的事务隔离级别设置为1(读已提交)
SET TRANSACTION ISOLATION LEVEL 1
go
并在窗口A执行如下SQL
select * from test_table where col1 = 1
go
这时候,我们发现上面的SQL语句阻塞了。因为事务隔离级别是读已提交,窗口A中的事务和窗口B中的事务操作了同一条记录, 它在等待窗口B中的事务提交或者回滚。如果窗口B中的事务没有完成,它就一直等待下去。
这时候,我们提交窗口B中的事务,窗口B执行:
commit
go
col1 col2
----------- ---------------
1 TEST UPDATE
(1 row affected)
窗口A的事务,读到了窗口B提交后的数据。
总结:事务隔离界别设置为读已提交,只有提交后的数据才能被读取。如果当前事务读取的记录在另外一个事务中更新了,且还没有的提交,当前读取操作会被阻塞。
再来介绍可重复读,可重复读对应的就是不可重复读,先介绍什么是不可重复读。
现在窗口A中的事务隔离级别还是读已提交,先不用改它。我们在窗口A中执行如下SQL
begin tran
select * from test_table where col1 = 2
go
col1 col2
----------- ---------------
2 test
(1 row affected)
这时,在窗口B中,我们执行如下SQL,修改col2=2的值:
update test_table
set col2 = "TEST UPDATE"
where col1 = 2
go
(1 row affected)
表示更新成功。
这时候,回到窗口A,在来查询 col2=2的值,
select * from test_table where col1 = 2
go
col1 col2
----------- ---------------
2 TEST UPDATE
(1 row affected)
窗口A两次读取的完整SQL和输出如下:
begin tran
select * from test_table where col1 = 2
go
col1 col2
----------- ---------------
2 test
(1 row affected)
select * from test_table where col1 = 2
go
col1 col2
----------- ---------------
2 TEST UPDATE
(1 row affected)
我们提交窗口A中的事务
commit
go
在窗口A中设置事务隔离界别为可重复读:
SET TRANSACTION ISOLATION LEVEL 2
go
begin tran
select * from test_table where col1 = 3
go
col1 col2
----------- ---------------
3 test
(1 row affected)
update test_table
set col2 = "TEST UPDATE"
where col1 = 3
go
这时候发现上面的SQL语句阻塞了,上面的更新等待窗口A的事务完成。
在窗口A中,再次执行如下SQL
select * from test_table where col1 = 3
go
输出如下:
col1 col2
----------- ---------------
3 test
(1 row affected)
我们在窗口B,按Ctrl + C,把窗口B中SQL break掉。我们看看,在窗口B,能不能update 另外的一条记录 col1=4, 窗口B中执行:
update test_table
set col2 = "TEST UPDATE"
where col1 = 4
go
(1 row affected)
select * from test_table where col1 = 4
go
col1 col2
----------- ---------------
4 TEST UPDATE
这时候再回到窗口B,再次 update col1=4的记录,
update test_table
set col2 = "TEST UPDATE UPDATE"
where col1 = 4
go
这时发现,上面的update阻塞了。
回到窗口A,查询col1=4的记录:
select * from test_table where col1 = 4
go
col1 col2
----------- ---------------
4 TEST UPDATE
(1 row affected)
这时,我们提交A窗口的事务,在窗口A执行:
commit
go
这时,窗口B中被阻塞的更新操作立即得到了执行,窗口B中输出:
(1 row affected)
select * from test_table where col1 = 4
go
col1 col2
----------- ---------------
4 TEST UPDATE UPD
(1 row affected)
总结:如果A事务设置为可重复读, 当它读取了资源R,但还没有提交事务时,其他事务就不能更新资源R,对它没有读取的资源,其它事务是可以更新的。
上面的例子是以单条记录作为资源的, 如果使用select * 呢?
如果在窗口A中, 使用如下SQL
SET TRANSACTION ISOLATION LEVEL 2
go
begin tran
select * from test_table
go
输出:
col1 col2
----------- ---------------
1 TEST UPDATE
2 TEST UPDATE
3 test
4 TEST UPDATE UPD
5 test
窗口B执行:
update test_table
set col2 = "TEST UPDATE"
where col1 = 5
go
窗口B执行:
delete test_table where col1 = 1
go
上例说明,可重复读对 select * 已有数据是适用的。
那么对新插入的数据呢?看下面的例子:
我们新插入一条记录,窗口B执行:
insert test_table (col1, col2) values (6, "test")
go
(1 row affected)
回到窗口A, 执行
select * from test_table
go
col1 col2
----------- ---------------
1 TEST UPDATE
2 TEST UPDATE
3 test
4 TEST UPDATE UPD
5 test
6 test
(6 rows affected)
这时,窗口A中事务,读到了窗口B中新插入的数据。
窗口A中,完整的SQL和输出如下:
SET TRANSACTION ISOLATION LEVEL 2
go
begin tran
select * from test_table
go
col1 col2
----------- ---------------
1 TEST UPDATE
2 TEST UPDATE
3 test
4 TEST UPDATE UPD
5 test
(5 rows affected)
select * from test_table
go
col1 col2
----------- ---------------
1 TEST UPDATE
2 TEST UPDATE
3 test
4 TEST UPDATE UPD
5 test
6 test
(6 rows affected)
从上面的输出可以看出,虽然事务隔离级别设置可重复读,但是,同一个事务中,上面两次select * 操作,返回的结果是不同的。这个问题就是范读。
要解决泛读的问题,就需要提高事务的隔离界别到串行化读。
最后来看看串行化读,提交窗口A中的事务,并设置事务隔离级别到串行化读,窗口A中执行:
commit tran
go
SET TRANSACTION ISOLATION LEVEL 3
go
begin tran
select * from test_table
go
输出:
col1 col2
----------- ---------------
1 TEST UPDATE
2 TEST UPDATE
3 test
4 TEST UPDATE UPD
5 test
6 test
(6 rows affected)
insert test_table (col1, col2) values (7, "test")
go
窗口A中,执行select *:
select * from test_table
go
输入和上次的一样:
col1 col2
----------- ---------------
1 TEST UPDATE
2 TEST UPDATE
3 test
4 TEST UPDATE UPD
5 test
6 test
(6 rows affected)
窗口A中,执行
commit tran
go
(1 row affected)
查询一下:
select * from test_table
go
col1 col2
----------- ---------------
1 TEST UPDATE
2 TEST UPDATE
3 test
4 TEST UPDATE UPD
5 test
6 test
7 test
(7 rows affected)