ansi/iso sql92标准定义了一些数据库操作的隔离级别:一个更新数据库的事务a在未commit的情况下,另一个事务b正在读取事务a更新的
ansi/iso sql92标准定义了一些数据库操作的隔离级别:
1 未提交读(read uncommitted)
2 提交读(read committed)
3 重复读(repeatable read)
4 序列化(serializable)
锁机制:
共享锁:其他事务可以读,但不能修改。
排他锁:其他事务不能读取。
锁粒度:一般分为:行锁、表锁、库锁
解释:
1 未提交读(read uncommitted)
一个更新数据库的事务a在未commit的情况下,另一个事务b正在读取事务a更新的记录,会产生脏读现象,这是因为a事务在开启 db transaction后,做一些dml操作时,记录会保存在内存中,这时b事务读取了a事务提交在内存中的数据,,产生了脏读。
2 提交读(read committed)
数据的修改只有在commit之后,才回被读取。和1 相反。
3 重复读(repeatable read)
当数据库隔离级别设置成 repeatable read后,事务a中的select 的过程中事务b可以修改a读取部分的数据,当a第2次执行同样的sql时,返回和上次相同的数据 ,消除不可重复读。
注:个人认为只是应为a事务采用这种隔离级别后,读取的是数据库在事务开始时间点的映象,在这个时间点后的所有操作都不会对a事务中的查询产生影响,依据是本文后续的实验,如果有疑问,请指出。
4 序列化(serializable)
当数据库隔离级别设置成serializeable后,事务a中的select 会以共享锁锁定相关的数据(在select 返回的数据结果集),这些数据不可以被修改(可以被读取),若事务b对这些数据做update操作,会处于等待状态,消除幻读。
注:事务b可以update 事务a中为锁定的数据,后面的实验可以证明。
实验:(mysql command line client 测试前记得用 set autocommit=off; 将自动提交关闭)
查看数据库默认隔离级别 mysql> select @@global.tx_isolation;
查看当前会话隔离级别 mysql> select @@tx_isolation;
修改数据库默认隔离级别 mysql> set global transaction isolation level read committed;
修改当前会话隔离级别 mysql> set session transaction isolation level read committed;
1 read uncommitted 测试
开启两个mysql command line client a b,将a设置为 read uncommitted ,b 为默认的 repeatable read ;
set session transaction isolation level read uncommitted;
通过b向数据库表中插入一条记录,但是不提交事务
insert into test.user (user_id,name,age) values(4,'fangpin6',25);
在a中执行 select * from test.user; 会看到这条新插入的记录,说明a用read uncommitted的隔离级别产生了脏读的问题。
2 read committed 测试
场景同测试1,将a的隔离级别设定为 read committed(mysql> set session transaction isolation level read committed),同样用 select * from test.user; 没有显示b插入的记录。在b中提交数据(mysql> commit;)后,a中显示了b插入的数据。这就说明了a用read committed 不会产生脏读现象。
3 repeatable read 测试
这部分测试使用了java客户端连接mysql,具体代码如下:
public static void getresult() throws exception {
thread t = new thread(new mysqltest().new threadtest());
t.start();
connection mysqlcon = getconn();//获取数据库连接
mysqlcon.settransactionisolation(connection.transaction_repeatable_read);//设置隔离级别
mysqlcon.setautocommit(false);
string sql = select * from test.user where user_id in( 1 ,3,2,8) ;
printresult(mysqlcon, sql);//打印输出结果
t.sleep(20000); //睡眠20秒(在此过程中 更新数据 update test.user set where user_id = 1 )(1)
system.out.println( thread sleep finashed );
string sql2 = select * from test.user where user_id in(1,3, 2,8) ;
printresult(mysqlcon, sql2);
string sql3 = select * from test.job;
printresult(mysqlcon, sql3);
mysqlcon.commit();
}
首先我们将事务隔离级别设置成transaction_repeatable_read 就是对应数据库中的repeatable read,然后开始查询user_id为 1,2,3,8的user
表中的数据,在线程挂起的时候(1)处,通过mysql客户端(可以认为是事务b)去更新user表中user_id为1,2的数据,同时更新表job的数据。线程继续执行后,打印出user表为更新前的数据,job表为更新前的数据,事务b的操作没有影响的事务a。
由上述结论推断出:repeatable read 隔离级别是在事务a开始的时间点,读取数据库的映象。
4 serializable 测试
和3用相同的测试代码,将隔离级别改为mysqlcon.settransactionisolation(connection.transaction_serializable);//设置隔离级别
transaction_serializable 对应数据库的serializable 。
通过事务b在(1)处执行更新表job数据、更新表user where user_id in (4,5)、更新表user where user_id in (1,2),在b事务执行过程中,前两个sql执行正常,更新 user_id in (1,2)的操作处于等待状态,在事务a结束后,事务b也能正常结束。同时事务a输出的结果包含了b的修改结果。
由上述实验推断出:serializable 隔离级别是在事务a开始后,对事务a中以扫描到的数据做共享锁,事务b如果要修改这部分加锁的数据,就需要等待a结束。如果在a还没有扫描到(后续会扫描到)某些数据时,事务b已经对这些数据做了修改,那么a将扫描到最新数据(b修改后的数据)