1.mysql中的事务隔离级别
未提交读:事务中的修改未被提交也能被别的事务看见。该级别隔离等级最低,但是性能最好,容易引起脏读,一般不采用。
读提交:一个事务只要没有提交,别的事务看见的都是原始状态。大多数数据库默认的隔离级别就是这个(mysql不是)。但是会引起不可重复读的问题。(比如A事务一开始查询某个值是1,另一个B事务更改了这个值,那么A事务再去查询这个值就不一样了)
可重复读:可以避免读提交的问题,但是会引起新的问题,幻读:比如A事务一开始读取了某一系列数据,B事务在A事务还未提交时插入了几条数据,A事务读取的数据会增加。innodb引擎的MVCC机制解决了这个问题。
串行化:最高级别的隔离,几个事务之间会存在锁的竞争。
示例:
脏读:
事务A
start transaction;
select * from t_books;
此时读到的结果为
图1
事务B中执行
start transaction;
update t_books set author=’332’ where uuid=’SDf’;
但是并没有提交
事务A中接着执行select * from t_books;
图2
如果事务B中执行rollback操作,再在事务A中执行select * from t_books;读到的数据又成了
图3
不可重复读:
事务A中执行
start transaction;
select * from t_books;
读到的值为图1的结果
在事务B中执行
start transaction;
update t_books set author=’332’ where uuid=’SDf’;
此时在事务A中接着执行select * from t_books;
那么读到的结果还是图1结果,
事务B执行commit命令,此时在事务A中接着执行select * from t_books;
那么读到的结果是图2结果,由于已经commit,所以事务B无法执行rollback操作;
但是会出现不可重复读:因为一开始事务A读到的是图1结果,因为事务A并不知道有事务B,所以一会他读到的数据变成图2了。
幻读:
事务A中执行
start transaction;
select * from t_books;
读到的值为图1的结果
在事务B中执行
start transaction;
添加一行记录
此时在事务A中接着执行select * from t_books;
结果将会多一行
如果此时事务Brollback操作,那么事务A执行select * from t_books;又会变成图1结果。
用生活中的例子:
脏读:程序员月初发工资,老板和财务说发30000,财务就招办,然后程序员收到短信,收到工资收入30000,过了一会老板说搞错了,只能发20000,跟财务说,你咋不跟我确认就发了,于是从银行上扣除10000,结果程序员发现工资又变成了20000,气死了。
不可重复读:程序员银行卡里有7万,准备去买个积家手表,结果他老婆拿着他的工资卡先买了一个爱马仕包包,花去4万,然后程序员再去查看发现工资卡里只有3万了,败家娘们!
重复读:重复读,就是在开始读取数据(事务开启)时,不再允许修改操作
事例:程序员拿着工资卡去享受生活(卡里当然是只有7万),当他买单时(事务开启,不允许其他事务的UPDATE修改操作),收费系统事先检测到他的卡里有7万。这个时候他的老婆不能转出金额了。接下来收费系统就可以扣款了。
分析:重复读可以解决不可重复读问题。写到这里,应该明白的一点就是,不可重复读对应的是修改,即UPDATE操作。但是可能还会有幻读问题。因为幻读问题对应的是插入INSERT操作,而不是UPDATE操作。
幻读:程序员的老婆拿着他的公司卡去消费,花了1千元,然后他去查看她今天的消费记录(全表扫描FTS,程序员事务开启),看到确实是花了1千元,就在这个时候,他老婆花了2万买了一部macpro,即新增INSERT了一条消费记录,并提交。当程序员打印他老婆的的消费记录清单时(程序员事务提交),发现花了2.1万元,似乎出现了幻觉,这就是幻读。
事务的最高级别序列化可以解决幻读问题,但是性能太差。如何在保证重复读的隔离级别上又能解决幻读问题呢,这就是mysql的innodb引擎中的mvcc机制。
—————-华丽的分割线———————–
2.InnoDB的MVCC
在介绍MVCC之前先了解下mysql中并发控制的几种技术
MVCC (Multiversion Concurrency Control),即多版本并发控制技术,它使得大部分支持行锁的事务引擎,不再单纯的使用行锁来进行数据库的并发控制,取而代之的是把数据库的行锁与行的多个版本结合起来,只需要很小的开销,就可以实现非锁定读,从而大大提高数据库系统的并发性能
读锁:也叫共享锁、S锁,若事务T对数据对象A加上S锁,则事务T可以读A但不能修改A,其他事务只能再对A加S锁,而不能加X锁,直到T释放A上的S 锁。这保证了其他事务可以读A,但在T释放A上的S锁之前不能对A做任何修改。
写锁:又称排他锁、X锁。若事务T对数据对象A加上X锁,事务T可以读A也可以修改A,其他事务不能再对A加任何锁,直到T释放A上的锁。这保证了其他事务在T释放A上的锁之前不能再读取和修改A。
表锁:操作对象是数据表。Mysql大多数锁策略都支持(常见mysql innodb),是系统开销最低但并发性最低的一个锁策略。事务t对整个表加读锁,则其他事务可读不可写,若加写锁,则其他事务增删改都不行。
行级锁:操作对象是数据表中的一行。是MVCC技术用的比较多的,但在MYISAM用不了,行级锁用mysql的储存引擎实现而不是mysql服务器。但行级锁对系统开销较大,处理高并发较好。
简单介绍下innodb和myisam两种存储引擎的区别:两种类型最主要的差别就是Innodb 支持事务处理与外键和行级锁。而MyISAM不支持.(更多内容请自行百度)
Innodb通过为每行记录都实现了四个隐藏字段:
1。创建时间(用事务ID表示)
2. 删除时间(用事务ID表示)
3。7字节的回滚指针
4。隐藏的ID
来实现MVCC
1. Innodb的事务相关概念
为了支持事务,Innbodb引入了下面几个概念:
redo log
redo log就是保存执行的SQL语句到一个指定的Log文件,当Mysql执行recovery时重新执行redo log记录的SQL操作即可。当客户端执行每条SQL(更新语句)时,redo log会被首先写入log buffer;当客户端执行COMMIT命令时,log buffer中的内容会被视情况刷新到磁盘。redo log在磁盘上作为一个独立的文件存在,即Innodb的log文件。
undo log
与redo log相反,undo log是为回滚而用,具体内容就是copy事务前的数据库内容(行)到undo buffer,在适合的时间把undo buffer中的内容刷新到磁盘。undo buffer与redo buffer一样,也是环形缓冲,但当缓冲满的时候,undo buffer中的内容会也会被刷新到磁盘;与redo log不同的是,磁盘上不存在单独的undo log文件,所有的undo log均存放在主ibd数据文件中(表空间),即使客户端设置了每表一个数据文件也是如此。
rollback segment
回滚段这个概念来自Oracle的事物模型,在Innodb中,undo log被划分为多个段,具体某行的undo log就保存在某个段中,称为回滚段。可以认为undo log和回滚段是同一意思。
下面就如下一图来展示数据库更新数据的过程
一开始数据库的数据为最上面的图,ID默认为1,后面两列为空,
当事务一更新时候,将数据更新成tx1所示的行,并将数据保存在redo log中,并且将上面一行的数据保存在undo log中,更新事务ID,并将回滚指针指向undo log中那部分数据。
事务二更新时,将数据编程tx2所示行,并保存在redo log中,将tx1的数据保存在undo log中,指针指向tx1,tx2和row record通过指针相连。
下面分别以select、delete、 insert、 update语句来说明MVCC是如何解决幻读问题的。
SELECT
Innodb检查每行数据,确保他们符合两个标准:
1、InnoDB只查找版本号小于等于当前事务版本的数据行,这确保当前事务读取的行都是事务之前已经存在的,或者是由当前事务创建或修改的行
2、删除版本一定是未定义的或者大于当前事务的版本号,确定了当前事务开始之前,行没有被删除
符合了以上两点则返回查询结果。
INSERT
InnoDB为每个新增行记录当前系统版本号作为创建ID。
DELETE
InnoDB为每个删除行记录当前系统版本号作为行的删除时间。
UPDATE
InnoDB复制了一行。这个新行的版本号使用了系统版本号。它也把系统版本号作为了删除版本。
还是以1.中的t_books表为例子
create table t_books(
id int primary key auto_increment,
name varchar(20),
price varchar(13));
insert into t_books values(NULL,’语文’,’12’) ;
insert into t_books values(NULL,’数学’,‘13’);
insert into t_books values(NULL,’英语’,‘13’);
此时表中数据为
id name price 创建时间(事务ID) 删除时间(事务ID)
1 语文 12 1 undefined
2 数学 13 1 undefined
3 英语 13 1 undefined
事务1中执行
start transaction;
select * from t_books //1
select * from t_books //2
commit;
此时t_books中的数据为将(创建时间和删除时间两列列出来)
id name price 创建时间(事务ID) 删除时间(事务ID)
1 语文 12 1 undefined
2 数学 13 1 undefined
3 英语 13 1 undefined
事务二开启
如果事务1在执行到1结束这里的时候
事务2执行
start transaction;
insert into t_books values(NULL,’物理’,’18’) ;
commit;
此时t_books中的数据为将(创建时间和删除时间两列列出来)
id name price 创建时间(事务ID) 删除时间(事务ID)
1 语文 12 0 undefined
2 数学 13 0 undefined
3 英语 13 0 undefined
4 物理 15 2 undefined
那么事务1在执行2时候查出的数据会和执行1一样,因为物理这条记录的事务ID大于事务的事务ID,所以不会被检索出来,这样避免了幻读。
同理,如果事务1在执行到1结束这里的时候执行了
delete from t_books where name =‘语文’;
此时t_books中的数据为(创建时间和删除时间两列列出来)id name price 创建时间(事务ID) 删除时间(事务ID)
1 语文 12 1 2
2 数学 13 1 undefined
3 英语 13 1 undefined
根据select的规定,删除时间大于事务版本号的将被列出来,所以事务1的1 、2操作结果是一样的,也避免了幻读。
如果事务2在事务1 的1步骤结束之后执行
update t_books set price=‘16’ where name =‘语文’;
此时数据库中的数据是这样的
name price 创建时间(事务ID) 删除时间(事务ID)
1 语文 12 1 2
2 数学 13 1 undefined
3 英语 13 1 undefined
4 语文 16 2 undefined
根据select 的规则,第4条数据也不会被检索出来,事务1中1和2操作是一致的,也避免了幻读产生。
MVCC机制只在重复读和不可重复读下生效,另外两种隔离级别不兼容。