事务
一、事务的介绍
事务:表示操作集合,不可分割,要么全部成功,要么全部失败
事务的开始取决于一个DML语句
事务的结束:
1、正常的commit或者rollback
2、自动提交,一般情况下进行关闭操作,效率太低
3、用户关闭会话之后会自动提交事务
4、系统崩溃或断电的时候会回滚事务
注意:在mysql常用的存储引擎中只有InnoDB支持事务
二、事务的四个特性(ACID)
1、原子性:一个操作集合(事务)要么全部成功,要么全部失败
原子性是通过undo log实现的。在操作数据之前会将数据备份到undo log中。当前行的历史版本数据在undolog中
当delete一条数据时,undo log中会记录一条add操作;
当add一条数据时,undo log中会记录一条delete操作;
当update一条语句时,undo log中会记录一条相反的update语句;
2、一致性:保证数据的一致性,经过n多个操作之后,数据的状态不会改变(转账时总金额不会改变)
3、隔离性:各个事务之间不会产生影响。严格的隔离性会降低效率,某些情况下会降低隔离级别来提高效率
隔离性中有四个隔离级别,隔离级别是通过锁机制完成的
4、持久性:所有数据的修改都必须持久化到存储介质,不会因为程序的关闭而导致数据的丢失
持久性是通过redo log实现的。当数据发生修改的时候,innodb引擎会先将记录写道redo log中并更新内存,此时更新算是完成了,同时innodb引擎会在合适的时机将记录操作到磁盘中
注意:数据库所有特性中都是为了保证数据的一致性
三、锁的机制
为了解决在并发访问的时候,数据不一致的问题,需要给数据加锁
加锁的同时需要考虑‘粒度’的问题:
操作对象有:①数据库
②表
③行
一般情况下,锁的粒度越小,效率越高,粒度越大,效率越低
四、事务的隔离级别
利用mysql数据库命令行进行模拟:
①开启两个命令窗口
②关闭事务自动提交:set autocommit = 0(select @@autocommit查看事务是否自动提交,1为开启,0为关闭)
注意:autocommit在不考虑隔离级别的时候主要针对增删改操作,不针对表创建
在一个事务中创建表,即使没有commit,另一个事务中也可以找到对应表名
③准备测试数据
创建数据库:create database tran;
切换数据库(两个窗口都执行):use tran;
创建表:create table psn(id int primary key,name varchar(10)) engine=innodb;
插入数据:insert into psn values(1,'zhangsan');
insert into psn values(2,'lisi');
insert into psn values(3,'wangwu');
commit;
1、读未提交:会造成脏读
A:set session transaction isolation level read uncommitted.
设置当前会话 事务 隔离 级别 读未提交
start transaction; 开启一项新的事务
B:set session transaction isolation level read uncommitted.
start transaction;
A:select * from psn;
B:select * from psn; 这时两个窗口数据一致
A:update psn set name = 'x'
A:select * from psn; 发现数据已经修改
B:select * from psn; 发现数据已经修改,但此时A窗口中事务未提交
解释:A事务并未提交事务,但此时B窗口已经读到未提交的事务
2、读已提交:会造成不可重复读
A:set session transaction isolation level read committed.
设置当前会话 事务 隔离 级别 读已提交
start transaction; 开启一项新的事务
B:set session transaction isolation level read committed.
start transaction;
A:select * from psn;
B:select * from psn; 这时两个窗口数据一致
A:update psn set name = 'y' where id = 1; 此时A窗口中数据发生改变
B:select * from psn; 此时B窗口中数据未发生改变,屏蔽了脏读的情况
A:commit;
B:select * from psn; 此时数据已经改变
解释:在B窗口内,出现了情况不相同的两种数据情况。
相当于你去银行取钱,在插卡的时间内,10点显示你的余额有1000,10点05分显示你的余额有500,在这期间你的银行卡是未拔出的。这是不合理的。
若此时B窗口在提交事务后进行查询,则会显示正常数据。
3、可重复读:会造成幻读,只在插入和删除的时候会产生
A:set session transaction isolation level repeatable read;
设置当前会话 事务 隔离 级别 可重复读
start transaction; 开启一项新的事务
B:set session transaction isolation level repeatable read;
start transaction;
A:select * from psn;
B:select * from psn; 这时两个窗口数据一致
A:update psn set name = 'y' where id = 2; 此时A窗口中数据发生改变
commit;
B:select * from psn; 此时B窗口中数据未发生改变,屏蔽了不可重复读的情况
commit;
A:start transation;
B:start transation;
A:select * from psn;
B:select * from psn; 此时数据一致性
A:insert into psn values(4,'e');
commit;
B:select * from psn; 发现无id为4的数据
B:insert into psn values(4,'e'); 在B窗口中执行A窗口中的插入语句,报错主键重复
解释:在B窗口中执行A窗口中的插入语句,报错主键重复,说明此时B窗口中已经查到id为4的数据,但未显示
4、序列化
给所有select语句中都加入了lock in share mode,性能很低
五、保证事务的一致性-MVCC
MVCC--->多版本并发控制
本质上是保证读写操作之间没有冲突
当前读:读取为最新版本数据,共享锁、排他锁都是读取的最新版本
快照读:读取为历史版本数据。提高数据库并发查询能力
MVCC实现原理
MVCC实现原理主要依赖三个隐式字段、undolog、readView来实现
隐式字段:
DB_TRX_ID:事务id,最近修改日志的id,与行绑定
DB_ROLL_PTR:回滚指针,当回滚时指向上一个事务
DB_ROW_ID:如果数据没有主键,那么innodb会生成一个6字节的row_id
DB_ROW_ID和Innodb中row_id联系:Innodb为聚簇索引,当插入一条数据时要和主键绑定,有主键用主键,无主键用唯一键,若都没有,会自动生成6字节的row_id与row_id绑定
ReadView
最大作用是做可见性判断,把各个版本的DB_TRX_ID取出
ReadView属性
trx_list:一个数值列表,用来维护ReadView生成时刻系统正在活跃的事务id
up_limit_id:记录trx_list中最小id
low_limit_id:ReadView生成时刻系统尚未分配的下一个事务id
RC与RR隔离级别在InnoDB快照读有什么不同?
RC隔离级别下:ReadView视图生成的时间是执行查询的sql语句之前,每一次进行快照读都会生成新的readview
RR隔离级别下:ReadView视图生成的时间是在事务开始后就会立刻生成,每一次进行快照读都会沿用之前的readview
如果要解决读一致性问题,保证事务中前后两次读取数据结果一致,实现事务隔离,应该怎么做?
生成一个数据请求时间点的一致性数据快照,并用这个快照来提供一定级别的一致性读取(MVCC)
1、MVCC产生的效果:建立了一个快照,同一个事务无论查多少次都是相同的数据
2、一个事务能看到的版本
①第一次查询之前已经提交的事务的修改
②本事务的修改
3、一个事务不能看见的数据版本
①在本事务第一次查询之后创建的事务(事务ID比我的事务ID大)
②活跃的(未提交的)事务的修改
六、面试题:在InnoDB引擎下,如何在隔离级别为可重复读下,解决幻读
在隔离级别为可重复读的情况下,使用MVCC+临键锁解决幻读
注意:在只有快照读时不会产生幻读问题,在当前读和快照读同时出现才会出现幻读问题
事务A:select * from tab;
事务B:select * from tab;
此时查询结果相同
事务B:insert into tab values(xxx);
事务A:select * from tabl; 不会查到新插入的xxx,因为事务A使用的是之前产生的快照读
事务A:update tab set value = yyy where id =1;
事务A:select * from tab; 此时查询结果会出现事务B插入的xxx,因为读取的是当前读,即为最新的数据
这就模拟了幻读问题
解决方法:
事务A:select * from tab where 条件 for update; 此时已经给索引加锁
事务B:insert into tab values(nnn); 此时会进行等待,事务B被阻塞,只有在事务A被提交后事务B才会执行
七、如果数据库发生死锁,如何定位是哪条sql产生的死锁
作者:http://cnblogs.com/lyc-code/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接,否则保留追究法律责任的权力。