数据库事务是指作为单个逻辑工作单元执行的一系列操作,要么完全执行,要么完全地不执行。
事务必须具备ACID四个特性:
原子性是指事务包含的所有操作要么全部成功,要么全部失败回滚。
一致性是指事务必须使数据库从一个一致的状态变到另外一个一致的状态,也就是执行事务之前和之后的状态都必须处于一致的状态。
隔离性是指当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离。
持久性是指一个事务一旦被提交了,那么对于数据库中的数据改变就是永久性的,即便是在数据库系统遭遇到故障的情况下也不会丢失提交事务的操作。
提交事务:
>set autocommit = 0 禁止自动提交
>start transaction;
>update accout set money=money+100 where name="Jason";
>commit;
回滚事务:
>set autocommit = 0 禁止自动提交
>start transaction;
>update account set money=money-100 where name="justin";
>rollback;
1、事务的隔离级别
mysql修改事务的隔离级别
>set [global | session] transaction isolation level 隔离级别名称;
>set tx_isolation=’隔离级别名称;’
隔离级别:Serializable | Repeatable read | Read committed |Read uncommitted
设置默认级别是指当前session的下一个事务
设置session级别是指当前session以后的所有事务
设置global级别是指对之后的所有session,不包括当前session
四种隔离级别和可能产生的问题
不可重复读:Read committed读已提交事务的数据,可能是两次查询过程中间插入了一个事务更新的原有的数据,导致出现同一事务的不同实例先后读取的内容不同。在 Repeatable read通过数据库事务版本号方式解决。
事务1:
mysql> begin;
Query OK, 0 rows affected
mysql> select * from student where stu_id = 2;
+----+------+--------+
| id | name | stu_id |
+----+------+--------+
| 2 | 生物 | 2 |
+----+------+--------+
1 row in set
事务2:
mysql> begin;
Query OK, 0 rows affected
mysql> update student set name = '地理' where stu_id = 2;
Query OK, 1 row affected
Rows matched: 1 Changed: 1 Warnings: 0
mysql> commit;
Query OK, 0 rows affected
接下来事务1再次查询:
mysql> select * from student where stu_id = 2;
+----+------+--------+
| id | name | stu_id |
+----+------+--------+
| 2 | 地理 | 2 |
+----+------+--------+
1 row in set
上述过程可见,带事务1的一个事务中,两次请求得到了不同的结果,就导致了不可重复读的现象。
幻读:在一个事务的两次查询中数据笔数不一致,例如有一个事务查询了几列(Row)数据,而另一个事务却在此时插入了新的几列数据,先前的事务在接下来的查询中,就会发现有几列数据是它先前所没有的。
数据库默认隔离级别是 Repeatable read。
2、锁与事务
数据库采用两段锁协议(Two-Phase Locking――2PL)
在对任何数据进行读、写操作之前,首先要申请并获得对该数据的封锁,即扩展阶段。
在释放一个封锁之后,事务不再申请和获得其它任何封锁,即收缩阶段。
若所有事务均遵守两段锁协议,则这些事务的所有交叉调度都是可串行化的。
mysql的读操作,通常是不会加锁的(和隔离机制有关),也就是说通常的读操作是不加锁的,而是通过mvcc去解决的,对于通常的写请求,insert、update、delete通常会加行锁、间隙锁或表锁(这和索引是有关系的),这些锁通常是排他的,会阻塞其他的事务写事务。具体的情况需要结合隔离机制。
(1)、锁的类型:
共享锁(S Lock)和排他锁(X Lock)
意向共享锁(IS Lock):事务想要获取一张表中的某几行共享锁。
意向排他锁(IX Lock):事务想要获取一张表中的某几行的排它锁。
表锁:
对一整张表加锁,并发能力低下(即使有分读锁、写锁),一般在DDL处理时使用。
行锁:
只锁住特定行的数据,并发能力强,MySQL一般都是用行锁来处理并发事务。
在更新记录的时候会对此记录加行锁,在事务没有commit之前不会释放锁,所以事务2的更新会阻塞等待事务1的排它锁,当事务1Commit后,行锁释放事务2获得行锁,更新成功。
Gap锁(间隙锁):
是MySQL使用索引对行锁两边的区间进行加锁,避免其他事务在这两个区间insert的一种锁。
如图所示:数据库中存在值5,30。那么数据库会将数据段切分以下几个区间:
(negative infinity, 5],(5,30],(30,positive infinity)
select * from table where number=30 for update;
当对值为30这一行加行锁的时候,会同时对(5,30]和(30,positive infinity)加GAP锁。这样其他事务如果想在这两个区间进行insert操作的时候,需要等待本次事务完成。如果对不存在的数据进行更新,比如更新20(不存在)对应数据行,那么数据库也会对其存在的区间(5,30]加GAP锁。
Next-Key锁:
Next-Key锁是行锁和GAP锁的合并(MySQL使用它来避免幻读)。
当按照id(非唯一索引,不是主键)进行更新或删除的时候会先对id索引进行next_key锁,防止幻读,因为新增加的记录只能在30的左边和30的右边或者就是30。那么锁住范围后就能保证防止“幻读”。
注意:如果用到无索引的字段,那么MySQL会在存储引擎层面将所有的记录加锁,然后由MySQL Server过滤,如果不满足会调用unlock_row把不满足条件的记录释放锁(这里违背了二段锁协议)。
(2)、Mvcc(多版本并发控制):
Mvcc是多版本的并发控制协议,Innodb中的乐观锁实现。读不加锁,读写不冲突。通过它提高MySQL的读取操作的性能。并能解决MySQL的重复读问题。
MVVC在每一行记录的后面加两个隐含列(记录创建版本号和删除版本号)。这里的版本号指的是事务的版本号(每个事务启动的时候,都有一个递增的版本号)。在更新时进行版本号的递增,插入时新建一个版本号,同时旧版本数据存储在undo日志中。
● 比如插入一条记录(事务id为1)
● 如果把这条记录name更新为dog
在更新操作的时候,采用的是先标记旧的那行记录为已删除,并且删除版本号是事务版本号,然后插入一行新的记录的方式。
● 删除这条记录时
删除操作的时候,就把事务版本号作为删除版本号
执行查询操作需要符合如下规则才能被查出来
1) 删除版本号 大于 当前事务版本号,就是说删除操作是在当前事务启动之后做的。
2) 创建版本号 小于或者等于 当前事务版本号 ,就是说记录创建是在事务中(等于的情况)或者事务启动之前。
mvcc分为快照读和当前读。
快照读只是针对于目标数据的版本小于等于当前事务的版本号,也就是说读数据的时候可能读到旧的数据,但是这种快照读不需要加锁,性能很高。
当前读是读取当前数据的最新版本,但是更新等操作会对数据进行加锁,所以当前读需要获取记录的行锁,存在锁争用的问题。
下面是快照读和当前读的常见操作:
快照读:就是select * from table ....;
当前读:特殊的读操作(加共享锁或排他锁),插入/更新/删除操作,需要加锁。
select from table where ? lock in share mode;
select from table where ? for update;
其实Mysql实现的Mvcc并不纯粹,因为在当前读的时候需要对记录进行加锁,而不是多版本竞争。下面是具体操作时的Mvcc机制:
1. SELECT时,读取创建版本号<=当前事务版本号,删除版本号为空或>当前事务版本号。
2. INSERT时,保存当前事务版本号为行的创建版本号
3. DELETE时,保存当前事务版本号为行的删除版本号
4. UPDATE时,插入一条新纪录,保存当前事务版本号为行创建版本号,同时保存当前事务版本号到原来删除的行
总结:
RC提交读,采用行锁和MVCC的当前读,有幻读和不可重复读问题。RR可重读,采用Next-key(默认)和MVCC的快照读,无幻读和可重复读。
mysql的锁机制和事务隔离级别有关。并不是说所有的读操作都不加锁,写操作加锁,加什么锁也和索引类型、有无索引有关。
感谢分享:
http://www.jianshu.com/p/bcc614524024
http://www.jianshu.com/p/edbe22beaecb