事务隔离
在进行数据库编程时,我们经常要处理事务,一些事务隔离问题,如果隔一段时间不用,即使是老鸟也容易忘记,今天把他们写下来,方便大家,也方便自己需要的时候查阅。基本上所有问题都是在2个或多个事务同时处理同一数据源时发生的,这里缩小到2个事务来分析问题,下面以T1代表事务1,T2代表事务2,D1代表数据源1,D2代表数据源2。
lost update:
1. T1 更新(update)了D1, 但是还没有提交(commit)。
2. T2 更新D2,在准备提交是发生问题,回滚,这导致T1的更新也被回滚。
3. T1提交,但是更新已被回滚。
结果:T1的更新丢失。
dirty read
1. T1更新D1,但是还未提交。
2. T2读取D1,注意D1这时已经被T1更新。
3. T1在提交时发生问题,回滚,导致T2目前在使用无效(脏的)D1数据。
4. T1提交。
结果: D1的脏数据被写入数据库。
unrepeatable read
1. T1读取D1, 但是还未提交。
2. T2更新D1并成功提交。
3. T1再次读取D1。
结果: 两次读取的D1含有不同的数据。
second lost updates problem
1. T1读取D1。
2. T2读取D1。
3. T1更新并成功提交D1。
4. T2更新并成功提交D1。
结果:T1的更新丢失了。
phantom read
1. T1执行某一查询获得D1,为提交。
2. T2向数据库表格中插入D2。(注意这里T2并未处理D1)
3. T1再次执行同一查询,结果获得D1和D2。
结果: 第一次查询看不到第二次查询获得的D2, 两次查询获得数据量不同。
注意,T2插入和删除时,这个问题都会出现。
事务隔离级别
我们可以在程序中设定不同的事务隔离级别来避免上述部分或全部事务隔离的问题。
read uncommitted
该级别允许程序读取未提交的数据,但是保证在T1更新D1,但是还未提交之时,任何事务都不能更新D1。这个级别可以防止“lost update”,但是“dirty read”会发生。原理上可以这样理解:使用共享读取锁和半共享的写入锁。多事务可以在任何时候无限制读取数据,但是当T1更新D1时,此时T1获得的写入锁禁止其他事务更新D1,但是不禁止其他事务读取D1,因此该写入锁只为半共享。
--- 共享读锁,半共享写锁
read committed
该级别使用共享的读取锁和排他的写入锁来实现高一级的事务隔离 - 防止“dirty read”,但是允许“unreapable read”发生。原理: dangT1读取D1时,其他事务仍然可以读写D1,但是当T1更新D1(未提交)时,T1获得D1的写锁,他是一个排他的锁,在T1完成整个提交之前,任何其他事务都不能读写D1。
--- 共享读锁,排他写锁
repeatable read
这个级别又高了一级,“unreapable read”也被排除了,但是“phantom read”仍然会发生。 这个级别和read committed的处理方式类似,不过这里使用“半共享”的读取锁:进行读取任务的事务共享一个锁,可以同时获取D1的数据,但是T1读取D1时,任何其他的事务都无法修改D1。可以看到,该读锁只和其他读事务共享数据而禁止其他写事务接触数据,因此称为半共享。写锁和read committed一样,是完全排他的。
--- 半共享读锁,排他享写锁
Serializable
最高级别的事务隔离,模拟实现线性的事务执行,就是说,事务好像排队一个一个来执行的一样,“phantom read”的问题也可以被排除了。单单使用完全排他的读suo写锁是无法实现这样绝对的隔离级别的,通常还需要使用一些辅助的算法。需要注意的是,这个级别的事务隔离性能是最差的。
--- 排他读锁,排他写锁,辅助算法模拟线性事务处理
在进行数据库编程时,我们经常要处理事务,一些事务隔离问题,如果隔一段时间不用,即使是老鸟也容易忘记,今天把他们写下来,方便大家,也方便自己需要的时候查阅。基本上所有问题都是在2个或多个事务同时处理同一数据源时发生的,这里缩小到2个事务来分析问题,下面以T1代表事务1,T2代表事务2,D1代表数据源1,D2代表数据源2。
lost update:
1. T1 更新(update)了D1, 但是还没有提交(commit)。
2. T2 更新D2,在准备提交是发生问题,回滚,这导致T1的更新也被回滚。
3. T1提交,但是更新已被回滚。
结果:T1的更新丢失。
dirty read
1. T1更新D1,但是还未提交。
2. T2读取D1,注意D1这时已经被T1更新。
3. T1在提交时发生问题,回滚,导致T2目前在使用无效(脏的)D1数据。
4. T1提交。
结果: D1的脏数据被写入数据库。
unrepeatable read
1. T1读取D1, 但是还未提交。
2. T2更新D1并成功提交。
3. T1再次读取D1。
结果: 两次读取的D1含有不同的数据。
second lost updates problem
1. T1读取D1。
2. T2读取D1。
3. T1更新并成功提交D1。
4. T2更新并成功提交D1。
结果:T1的更新丢失了。
phantom read
1. T1执行某一查询获得D1,为提交。
2. T2向数据库表格中插入D2。(注意这里T2并未处理D1)
3. T1再次执行同一查询,结果获得D1和D2。
结果: 第一次查询看不到第二次查询获得的D2, 两次查询获得数据量不同。
注意,T2插入和删除时,这个问题都会出现。
事务隔离级别
我们可以在程序中设定不同的事务隔离级别来避免上述部分或全部事务隔离的问题。
read uncommitted
该级别允许程序读取未提交的数据,但是保证在T1更新D1,但是还未提交之时,任何事务都不能更新D1。这个级别可以防止“lost update”,但是“dirty read”会发生。原理上可以这样理解:使用共享读取锁和半共享的写入锁。多事务可以在任何时候无限制读取数据,但是当T1更新D1时,此时T1获得的写入锁禁止其他事务更新D1,但是不禁止其他事务读取D1,因此该写入锁只为半共享。
--- 共享读锁,半共享写锁
read committed
该级别使用共享的读取锁和排他的写入锁来实现高一级的事务隔离 - 防止“dirty read”,但是允许“unreapable read”发生。原理: dangT1读取D1时,其他事务仍然可以读写D1,但是当T1更新D1(未提交)时,T1获得D1的写锁,他是一个排他的锁,在T1完成整个提交之前,任何其他事务都不能读写D1。
--- 共享读锁,排他写锁
repeatable read
这个级别又高了一级,“unreapable read”也被排除了,但是“phantom read”仍然会发生。 这个级别和read committed的处理方式类似,不过这里使用“半共享”的读取锁:进行读取任务的事务共享一个锁,可以同时获取D1的数据,但是T1读取D1时,任何其他的事务都无法修改D1。可以看到,该读锁只和其他读事务共享数据而禁止其他写事务接触数据,因此称为半共享。写锁和read committed一样,是完全排他的。
--- 半共享读锁,排他享写锁
Serializable
最高级别的事务隔离,模拟实现线性的事务执行,就是说,事务好像排队一个一个来执行的一样,“phantom read”的问题也可以被排除了。单单使用完全排他的读suo写锁是无法实现这样绝对的隔离级别的,通常还需要使用一些辅助的算法。需要注意的是,这个级别的事务隔离性能是最差的。
--- 排他读锁,排他写锁,辅助算法模拟线性事务处理