概念
事务
原子性:事务必须是一个自动工作的单元,要么全部执行,要么全部不执行.
一致性:事务结束的时候,所有的内部数据都是正确的。 隔离性:并发多个事务时,各个事务不干涉内部数据,处理的都是另外一个事务处理之前或之后的数据。 持久性:事务提交之后,数据是永久性的,不可再回滚。锁
这里主要说2个行级锁,共享锁(s),排它锁(x).
共享锁:事务T对数据对象a加了s锁,那么这个事务T只能对数据读而不能写,而其他事务也不能对这个数据加其他锁,只能加S锁,直到这个事务T执行完毕,这就保证了这个数据在事务T中不变。 排它锁:事务T对数据对象加了X锁,事务T可以对数据修改读取,但是其他事务无法对数据加其他任意锁,也不能读取和修改该数据对象。四大隔离级别
Read uncommitted (读未提交):这个可能造成脏读。其效果如下,2个事务A,B。A查询数据,不提交。B修改数据,不提交。A查询数据,发现B未提交的数据已经查询出来了。
Read committed (读已提交):这个可以避免脏读,只能读取到提交后的数据。但是可能造成不可重复读问题。2个事务A,B。A查询数据,不提交。B修改数据,提交。A查询数据,发现B未提交的数据已经查询出来了,造成了2次查询效果不一致问题。 Repeatable read (可重复读):这个可以避免可重复读,但是无法避免幻读。2个事务A,B。A查询数据,不提交。B在A的查询范围内insert一条数据,提交。A查询数据,发现多了几条数据。造成了幻读 Serializable (串行化):可以避免脏读,不可重复读,幻读的问题,但是新能较低。mysql的Innodb的上述情况。
预备知识
2PL:Two-Phase Locking
这个是2阶段锁机制。说的是锁操作分为两个阶段:加锁阶段与解锁阶段,并且保证加锁阶段与解锁阶段不相交。也就是说,一个事务里面,对于要加锁的数据加了锁,直到事务结束(正常commit或者rollback),才会释放锁。
mvcc和innodb的mvcc
mvcc:多版本并发控制,适用于读多于写的情况,一行数据多个版本,读取数据可能是某个历史版本。可以有效见面加锁。
Innodb的mvcc实现,网上有多个版本解释了innodb的mvcc实现,但是很多都无法自圆其说,这里找到一个版本是专业的DBA写的(我比较信服的一个版本)这里简述下: 1.在Mysql中MVCC是在Innodb存储引擎中得到支持的,Innodb为每行记录都实现了三个隐藏字段: 6字节的事务ID(DB_TRX_ID) 7字节的回滚指针(DB_ROLL_PTR) 隐藏的ID。 MVCC 在mysql 中的实现依赖的是 undo log 与 read view undo log是为回滚而用,具体内容就是copy事务前的数据库内容(行)到undo buffer,在适合的时间把undo buffer中的内容刷新到磁盘。 read view: 主要用来判断当前版本数据的可见性。在innodb中,创建一个新事务的时候,innodb会将当前系统中的活跃事务列表(trx_sys->trx_list)创建一个副本(read view),副本中保存的是系统当前不应该被本事务看到的其他事务id列表。当用户在这个事务中要读取该行记录的时候,innodb会将该行当前的版本号与该read view进行比较。 系统维护一个事务id,每次开启新事物都会+1。当修改数据时,才会修改对应数据行的事务id,并且指向以前的版本。 RR特性:只有在当前事务开启前提交的数据才可见哦。 RC特性:只要提交的数据都可见。 1.设该行的当前事务id为trx_id_0,read view中最早的事务id为trx_id_1, 最迟的事务id为trx_id_2。 2.如果trx_id_0< trx_id_1或者trx_id_0=当前事务id的话,那么表明该行记录所在的事务已经在所有新事务创建之前就提交了,那么无论是RR还是RC都是绝对可见的,所以该行记录的当前值是可见的。跳到步骤6. 3.如果trx_id_0>trx_id_2的话,那么表明该行记录所在的事务在本次新事务创建之后才开启,那么显然该行记录的当前值不可见.跳到步骤5。 4.如果trx_id_1<=trx_id_0<=trx_id_2, 那么从trx_id_1到trx_id_2进行遍历,如果trx_id_0等于他们之中的某个事务id的话,说明这个记录行修改时的事务在本事务开启前没有提交,那么这行数据不可见。否则就是在本事务开启前就已经提交了,那么可见了跳到步骤5. 5.从该行记录的DB_ROLL_PTR指针所指向的回滚段中取出最新的undo-log的版本号,将它赋值该trx_id_0,然后跳到步骤2. 6.将该可见行的值返回。 总结:开启一个新事务,获取到一个read view,当前事务是最大的,因为开启一个加1一下,然后查询数据,只要查询的数据的行事务id等于当前事务号,或者<当前事务号,且不在read view中,那么说明这个行事务id是在本事务开启前就提交了,所以这行是可见的,如果在read view里面或者>当前事务号,那么都是在本事务开启前没有提交的,那么对于RR来说都是不可见的。只能回滚到上一条数据中,在进行判断。至于RR和RC的区别在于,RR是事务开启时获得一份ReadView,而RC则是每次语句都获取一次ReadView,这个造成了不同隔离级别的快照读的可见性的区别了。
RC总结如下: 开启一个新事务,不获取到一个read view,而是在查询时获取到这个Read View,只要查出的这个数据的事务id不在这个read view里面,那么就是可见的了。因为在这个read view里面,对于当前查询来说都是为提交的啊,因为是活跃的事务,而不在这个里面的都是已经提交的事务,对于RC来说,只要是提交的事务,都是可见的。 只要你在我之前提交了,那么我的read view里面就没有你,那么我就可见你了。RC的判断很简单,每次查询时都会取一个read view,只要这个行数据不在这个我得read view里面即可。 这个就完美解释了不同隔离级别数据的可见性问题了Innodb的四大隔离级别与标准的区别
RU,RC.Serializable和标准一样,但是RR不一样,RR解决了幻读的问题。 怎么实现的后面说。
一条简单的语句如何加锁?加什么锁。
delete from user where id=1;
我拿这个sql作为demo,来说下如何加锁的。加什么锁,怎么加锁,是分情况来讨论的我们以加锁方式的不同来划分。 1.(RR,id是主键)(RC,id是主键) 这个就是在数据记录上id=1的数据上加一个排他锁。 2.(RC,id是唯一索引)(RC,id不是唯一索引),(RR,id是唯一索引) 这3个情况是对应的二级索引,id=1的索引加上锁,且对应的id=1的数据也会加上锁。 3.(RR,id是非唯一索引) 这种情况为了防止幻读,有了一个叫做Gap锁,用于锁住索引之间的间隙,比如,id=1的记录有2条,那么会加2个X锁,和3个Gap锁,用于锁住这2个索引和其间隙,当然对应的id=2的2个记录也会被锁住。 4.(RC 无索引) 没有索引,那么所有记录都会被锁住加上X锁,但是mysql对其优化了,会释放id!=1的记录。(显然违背了2pl原则)。 5.(RR 无索引) 没有索引,所有记录都会加x锁和Gap锁,当然mysql对其也做了优化,就是所谓的semi-consistent read,最后只会对id=1的记录加x锁,但是不会加Gap锁