在使用数据库时,因为存在不同的用户操作同一条数据,数据库可能出现如下问题:
- 脏读:表示一个事务正在访问数据,并且对数据进行了修改,而这种修改还么提交到数据库,这个时候,另一个事务也访问了这个数据,然后获取到了该事务未提交的数据。
- 不可重复读:是指在一个事务内,多次读取同一数据。在这个事务还没结束时,另一个事务也访问并修改了改数据(已经提交事务),第一个事务再次读取数据发现两次读取的数据不一样。
- 幻读:一个事务计划对表中数据进行修改,同时第二个事务向表中插入了一条数据(提交事务),符合第一个事务中的条件,之后第一个事务对表进行修改,那么对于操作第一个事务的用户而言,会发现表中还有其他数据(第二个事务),如同幻觉。
事务导致的问题
脏读
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
|
mysql1> set SESSION transaction ISOLATION LEVEL READ UNCOMMITTED;
mysql1> set autocommit = 0; mysql1> start transaction; mysql1> SELECT * FROM item order by id desc;
+ | id | serial_num | sku_code | num | status | + | 1 | 000001 | K00006 | 5 | 1 | +
mysql2> set autocommit = 0;
mysql2> start transaction; mysql2> update item set status = 2 where i = 1;
mysql1> select * from item; + | id | serial_num | sku_code | num | status | + | 1 | 000001 | K00006 | 5 | 2 | +
mysql2> rollback;
|
如上操作,事务1读取到了事务2未提交的数据。在有些情况下脏读比较危险,如:A转账给B,A转账后,还有后续逻辑,都在一个事务里面,这时B去查账,查询到了余额的增加,如果B中某个耗时逻辑发送了错误导致事务回滚,这样实际是没有成功,对于B而言之前是已经看见成功了。
在使用自增ID时,此时自增ID为1,如果一个事务提交了insert,生成的自增加ID为2,另外一个事务也提交了insert,生成的自增ID为3,因为第一个事务并未提交,第二个事务却生成的自增ID为3。这勉强可以视为脏的读,只不过这种情况是为了在并发时提升数据库性能做的操作。
不可重复读
不可重复读和脏读的区别在于,在事务内,一个是读取到了另一个事务未提交的数据;一个是读取了另一个已经提交的数据,前后读取的数据不一致。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| mysql1> set SESSION transaction ISOLATION LEVEL READ COMMITTED; mysql1> commit; mysql1> start transaction; mysql1> SELECT * FROM item order by id desc; + | id | serial_num | sku_code | num | status | + | 1 | 000001 | K00006 | 5 | 1 | +
mysql2> set SESSION transaction ISOLATION LEVEL READ COMMITTED; mysql2> commit; mysql2> start transaction; mysql2> update item set status = 2 where id=1;
mysql1> SELECT * FROM item order by id desc; + | id | serial_num | sku_code | num | status | + | 1 | 000001 | K00006 | 5 | 1 | +
mysql2> commit;
mysql1> SELECT * FROM item order by id desc; + | id | serial_num | sku_code | num | status | + | 1 | 000001 | K00006 | 5 | 2 | + mysql1> commit;
|
如上操作,事务一在自己事务内读取到的数据前后不一致。
幻读
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| mysql1> set SESSION transaction ISOLATION LEVEL REPEATABLE READ ; mysql1> commit; mysql1> start transaction; mysql1> SELECT * FROM item order by id desc; + | id | serial_num | sku_code | num | status | + | 1 | 000001 | K00006 | 5 | 2 | +
mysql2> set SESSION transaction ISOLATION LEVEL REPEATABLE READ ; mysql2> commit; mysql2> start transaction; mysql2> insert into item(serial_num, sku_code, num, status) VALUES ('000002', 'K00006', 5, 1); mysql2> commit;
mysql1> SELECT * FROM item order by id desc; + | id | serial_num | sku_code | num | status | + | 1 | 000001 | K00006 | 5 | 2 | +
mysql1> update item set status=4 where sku_code = 'K00006'; mysql1> SELECT * FROM item order by id desc; + | id | serial_num | sku_code | num | status | + | 2 | 000002 | K00006 | 5 | 4 | | 1 | 000001 | K00006 | 5 | 4 | +
mysql1> commit;
|
在原本事务一中只有一条数据,在事务二插入提交后,事务一中对数据进行更新,此时事务二中提交的数据被修改查询出来。
事务隔离级别
在上述例子中,每个例子都是使用的不同的事务隔离级别。
设置事务隔离级别命令
SET [SESSION | GLOBAL] TRANSACTION ISOLATION LEVEL {READ UNCOMMITTED | READ COMMITTED | REPEATABLE READ | SERIALIZABLE}
不同的事务隔离级别用于解决不同的问题:
| — |
脏读 |
不可重复读 |
幻读 |
| READ UNCOMMITTED |
可能 |
可能 |
可能 |
| READ COMMITTED |
不可能 |
可能 |
可能 |
| REPEATABLE READ(mysql InnoDB 默认) |
不可能 |
不可能 |
可能 |
| SERIALIZABLE |
不可能 |
不可能 |
不可能 |
对于SERIALIZABLE并没有举例,在使用SERIALIZABLE实际上是自己锁住了数据,其他数据需要修改只能等待锁释放。
参考:MySQL 四种事务隔离级的说明