MySQL事务学习-->隔离级别
6 事务的隔离级别
设置的目的
在数据库操作中,为了有效保证并发读取数据的正确性,提出的事务隔离级别。
数据库是要被广大客户所共享访问的,那么在数据库操作过程中很可能出现以下几种不确定情况。
更新丢失
两个事务都同时更新一行数据,但是第二个事务却中途失败退出,导致对数据的两个修改都失效了。这是因为系统没有执行任何的锁操作,因此并发事务并没有被隔离开来。
脏读
一个事务开始读取了某行数据,但是另外一个事务已经更新了此数据但没有能够及时提交。这是相当危险的,因为很可能所有的操作都被回滚。
不可重复读(Non-repeatable Reads)
一个事务对同一行数据重复读取两次,但是却得到了不同的结果。它包括以下情况:
(1) 事务T1读取某一数据后,事务T2对其做了修改,当事务T1再次读该数据时得到与前一次不同的值。
(2) 幻读(Phantom Reads):事务在操作过程中进行两次查询,第二次查询的结果包含了第一次查询中未出现的数据或者缺少了第一次查询中出现的数据(这里并不要求两次查询的SQL语句相同)。这是因为在两次查询过程中有另外一个事务插入数据造成的。
解决方案
为了避免上面出现的几种情况,在标准SQL规范中,定义了4个事务隔离级别,不同的隔离级别对事务的处理不同。
未授权读取
也称为读未提交(Read Uncommitted):允许脏读取,但不允许更新丢失。如果一个事务已经开始写数据,则另外一个事务则不允许同时进行写操作,但允许其他事务读此行数据。该隔离级别可以通过“排他写锁”实现。
授权读取
也称为读提交(Read Committed):允许不可重复读取,但不允许脏读取。这可以通过“瞬间共享读锁”和“排他写锁”实现。读取数据的事务允许其他事务继续访问该行数据,但是未提交的写事务将会禁止其他事务访问该行。
可重复读取
可重复读取(Repeatable Read):禁止不可重复读取和脏读取,但是有时可能出现幻影数据。这可以通过“共享读锁”和“排他写锁”实现。读取数据的事务将会禁止写事务(但允许读事务),写事务则禁止任何其他事务。
序列化
序列化(Serializable):提供严格的事务隔离。它要求事务序列化执行,事务只能一个接着一个地执行,但不能并发执行。如果仅仅通过“行级锁”是无法实现事务序列化的,必须通过其他机制保证新插入的数据不会被刚执行查询操作的事务访问到。
隔离级别越高,越能保证数据的完整性和一致性,但是对并发性能的影响也越大。对于多数应用程序,可以优先考虑把数据库系统的隔离级别设为Read Committed。它能够避免脏读取,而且具有较好的并发性能。尽管它会导致不可重复读、虚读和第二类丢失更新这些并发问题,在可能出现这类问题的个别场合,可以由应用程序采用悲观锁或乐观锁来控制。
在MySQL中设置事务隔离级别有2种方法:
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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
|
1 在my.cnf中设置,在mysqld选项中如下设置 [mysqld] transaction - isolation = READ - COMMITTED 2 在mysql窗口用 set 命令重置 [sql] mysql> set global tx_isolation= 'REPEATABLE-READ' ; Query OK, 0 rows affected (0.01 sec) mysql> 查询当前的会话事务级别,可以使用: [sql] mysql> select @@tx_isolation; + ----------------+ | @@tx_isolation | + ----------------+ | READ - COMMITTED | + ----------------+ 1 row in set (0.00 sec) mysql> 查询全局的事务隔离级别,可以使用 [sql] mysql> select @@ global .tx_isolation; + -----------------------+ | @@ global .tx_isolation | + -----------------------+ | READ - COMMITTED | + -----------------------+ 1 row in set (0.00 sec) mysql> 在 Serializable 模式下。 [sql] mysql> system cat /usr/ local /mysql56m2/my.cnf |grep transaction - isolation transaction - isolation = READ - COMMITTED mysql> 复制二进制与隔离级别的关系 在 SERIALIZABLE 模式下,Innodb存储引擎会对每个 select 语句自动加Lock in sharedmode,给每一个读操作加共享锁。因此在这个隔离级别下,读占用锁了,一致性的非锁定读不再予以支持。因为Innodb存储引擎在 repeatable read 模式下就已经达到了3度的隔离,所以一般不在本地事务中使用 serializable 隔离级别, serializable 的事务隔离级别主要用于innodb存储引擎的分布式事务。 在 Read committed 的隔离模式下,除了唯一性约束检查以及外键约束检查需要Gap lock,innodb存储引擎不会使用gap lock的锁算法。不过使用 read committed 隔离级别需要注意一些问题,mysql5.1中, Read committed 的事务隔离级别默认只能在replication的二进制为row格式下,如果二进制默认在statement模式下,则会报如下错误: [sql] mysql> select @@version; + -------------+ | @@version | + -------------+ | 5.5.25a-log | + -------------+ 1 row in set (0.00 sec) mysql> mysql> select @@binlog_format; + -----------------+ | @@binlog_format | + -----------------+ | STATEMENT | + -----------------+ 1 row in set (0.00 sec) mysql> select @@tx_isolation; + -----------------+ | @@tx_isolation | + -----------------+ | REPEATABLE - READ | + -----------------+ 1 row in set (0.00 sec) mysql> set tx_isolation= 'READ-COMMITTED' ; Query OK, 0 rows affected (0.00 sec) mysql> use test; Database changed mysql> create table a (b int , primary key (b)) engine=innodb; ERROR 1050 (42S01): Table 'a' already exists mysql> select @@tx_isolation; + ----------------+ | @@tx_isolation | + ----------------+ | READ - COMMITTED | + ----------------+ 1 row in set (0.00 sec) mysql> begin -> ; Query OK, 0 rows affected (0.00 sec) mysql> insert into a select 100000; ERROR 1665 (HY000): Cannotexecute statement: impossible to write to binary log since BINLOG_FORMAT =STATEMENT and at least one table uses a storage engine limited to row-basedlogging. InnoDB is limited to row-logging when transaction isolation level isREAD COMMITTED or READ UNCOMMITTED . ERROR 1665 (HY000): Cannotexecute statement: impossible to write to binary log since BINLOG_FORMAT =STATEMENT and at least one table uses a storage engine limited to row-basedlogging. InnoDB is limited to row-logging when transaction isolation level isREAD COMMITTED or READ UNCOMMITTED . [Note]:在mysql5.1以及mysql5.6模式下实验过都是如此。也许可以知道通过将innodb_locks_unsafe_for_binlog设置为1,来可以使binlog日志在statement下使用readcommitted的事务隔离级别: [sql] mysql> select @@innodb_locks_unsafe_for_binlog; + ----------------------------------+ | @@innodb_locks_unsafe_for_binlog | + ----------------------------------+ | 0 | + ----------------------------------+ 1 row in set (0.00 sec) mysql> set global innodb_locks_unsafe_for_binlog=1; ERROR 1238 (HY000): Variable 'innodb_locks_unsafe_for_binlog' is a readonly variable mysql> 此参数是只读模式,需要修改my.cnf重新启动才行。 在my.cnf里面的[mysqld]添加 [mysqld] innodb_locks_unsafe_for_binlog = 1 然后重启,然后去 check 模仿一个事务操作,如下所示: [sql] mysql> select @@innodb_locks_unsafe_for_binlog; + ----------------------------------+ | @@innodb_locks_unsafe_for_binlog | + ----------------------------------+ | 1 | + ----------------------------------+ 1 row in set (0.00 sec) mysql> mysql> use test; Reading table information for completion of table and column names You can turn off this feature to get a quicker startup with -A Database changed mysql> select @@tx_isolation; + ----------------+ | @@tx_isolation | + ----------------+ | READ - COMMITTED | + ----------------+ 1 row in set (0.00 sec) mysql> begin ; Query OK, 0 rows affected (0.00 sec) mysql> insert into t select 15; Query OK, 1 row affected (0.00 sec) Records: 1 Duplicates: 0 Warnings: 0 mysql> commit ; Query OK, 0 rows affected (0.00 sec) mysql> select * from t; + --------+ | id | + --------+ | 1 | | 12 | | 15 | | 11111 | | 111110 | + --------+ 5 rows in set (0.00 sec) |