读-读:不存在任何问题,也不需要并发控制。
读-写:有线程安全问题,可能会造成事务隔离性问题,可能遇到脏读,幻读,不可重复读
写-写:有线程安全问题,可能会存在更新丢失问题,比如第一类更新丢失,第二类更新丢失
事务未提交时数据存在于数据库系统的缓存中,而在事务提交后,数据才会被写入到磁盘上的数据库文件中。
简而言之,MVCC 就是因为大佬们,不满意只让数据库采用悲观锁这样性能不佳的形式去解决读-写冲突问题,而提出的解决方案,所以在数据库中,因为有了 MVCC,所以我们可以形成两个组合:
MVCC + 悲观锁
MVCC解决读写冲突,悲观锁解决写写冲突
MVCC + 乐观锁
MVCC 解决读写冲突,乐观锁解决写写冲突
这种组合的方式就可以最大程度的提高数据库并发性能,并解决读写冲突,和写写冲突导致的问题
事务的四大特征
原子性:事务不可分割,要么全部成功,要么全部失败
一致性:事务的一致性要求数据库在任何时候都保持一致的状态,不会出现数据的矛盾或不完整。(数据总额不能变)
隔离性:并发执行的事务之间互不干扰。
持久性:事务一旦提交, 其对数据库的修改是永久性的
MySQL支持四种隔离级别,分别有:
读 未提交(read uncommitted)可以读到未提交的数据。会造成脏读,不可重复读和幻读。
读 已提交(read committed)只会读已提交的数据。它能解决脏读的问题的,但是解决不了不可重复读和幻读。
可重复 读(repeatable read)它确保同一事务的多个实例在并发读取数据时,会看到同样的数据行。它能解决脏读和不可重复读,但是解决不了幻读,这个也是mysql默认的隔离级别。
第四个是串行化(serializable)这是最高的隔离级别,它通过强制事务排序,使之不可能相互冲突,从而解决幻读问题。简言之,它是在每个读的数据行上加上共享锁。在这个级别,可能导致大量的超时现象和锁竞争。
编者按:事务顺序执行,没有并行,完全杜绝幻读。
面试官:并发事务带来哪些问题?
候选人:
脏读,读到其他事务还未提交的数据。
不可重复读:一个事物先后读取两次同一条记录。在第一次读取后,其他事务修改了该数据,导致第二次读到的数据和第一次不同。
幻读(Phantom read):
select的时候没有发现某条记录,插入的时候又发现记录已经存在,好像自己出现了幻觉一样。
幻读错误的理解
说幻读是 事务A 执行两次 select 操作得到不同的数据集,即 select 1 得到 10 条记录,select 2 得到 15 条记录。这其实并不是幻读,既然第一次和第二次读取的不一致,那不还是不可重复读吗,所以这是不可重复读的一种。
正确的理解
幻读,并不是说两次读取获取的结果集不同,幻读侧重的方面是某一次的 select 操作得到的结果所表征的数据状态无法支撑后续的业务操作。更为具体一些:select 某记录是否存在,不存在,准备插入此记录,但执行 insert 时发现此记录已存在,无法插入,此时就发生了幻读。
mvcc即多版本并发控制。指维护一个数据的多个版本,使得读-写操作没有冲突
MVCC的实现靠 记录的3个隐式字段、undo log,Read View来实现。
3个记录的隐式字段:DB_ROW_ID、DB_TRX_ID、DB_ROLL_PTR
DB_ROW_ID(隐式主键):当数据表没有主键时,InnoDB 会以该字段构建b+树。
DB_TRX_ID(事务ID):记录最后一次修改该记录的事务的id。
DB_ROLL_PTR(回滚指针):指向这条记录的上一个版本,配合undo log形成版本链。
undo log,会通过回滚指针,在内部会形成一个记录的版本链。
read View
read View包含四个核心字段,当前活跃的事务ID的集合、最小的活跃的事务ID、最大的事务ID+1、创建当前Read View的事务ID。
在可重复读的隔离级别下,事务第一次select快照读时才会生成读视图,后续复用该读视图。生成read View后,根据read View中的信息按照一定规则从记录的版本链中读取记录。
大致规则如下:
依次用版本链中的事务ID去和Read View中的信息比较,判断哪条记录能被当前事务访问。
1.事务ID小于最小的活跃的事务ID,那肯定能被当前事务看到。
2.事务ID大于等于最大的事务ID+1,那肯定不能被当前事务看到。
3.判断完上述两条,事务ID肯定在最小和最大之间,然后判断事务ID如果在当前活跃的事务ID的集合中,如果在,说明readView生成之前,你这个事务还在执行,还没提交,那对肯定不能被当前事务看到,不然就是读到未提交的数据了。
如果不在说明在当前事务生成Read View之前,这条记录已经提交了,肯定能被当前事务看到。
示例:
在以下案例中用到的是数据库的默认隔离级别,可重复读。
事务5在第一次快照读时生成read View。根据read View从版本链中读取记录时,事务3对事务5是不可见的,会读到事务2。
在第二次快照读时复用上一次的read View。
根据read View进行第二次读取时,事务4提交的age=10对事务5是不可见的,事务3也是不可见的。还是会读到事务2。
这样就解决了不可重复读问题。
Undo log(回滚日志) 记录了事务对数据所做的修改操作的逆操作,即撤销操作。当事务进行修改时,会先将修改前的数据记录到 undo log 中,以便在事务回滚或者发生异常时能够恢复到事务开始之前的状态。
保证事务的原子性和一致性。
redo log(重做日志)
在执行update操作后,会先去缓冲池中修改对应的数据,然后提交事务后把缓冲池中的数据保存到磁盘文件中。但是这个保存过程可能会发生问题,比如数据库宕机了,内存中的数据没有了,数据就无法保存到磁盘文件中了。这时就需要用到redo log日志,redo log日志会记录所做的修改操作,在数据库宕机恢复后重新执行一遍。
mysql的锁按粒度分为:全局锁、表锁、行锁。
按功能分为:(共享)读锁、写锁。
获取读锁的事务只能读,不能写(会被阻塞),其他事务也是只能读,不能写。
获取写锁的事务既能读又能写,其他事务既不能读,也不能写(会被阻塞)。
一个获取了读锁之后,其他事务也能获取这个读锁,但不能获取写锁
一个事务获取了写锁之后,其他事务不能获取读锁,也不能获取写锁
1.备份数据库时,最好在备份之前先加一个全局读锁。
如果不加全局锁,就会出现不一致的情况。(如备份库存表时,库存还没减去备份完库存表,事务对库存表进行了修改,同时生成了订单,就会出现备份的数据库中库存没减去,但有订单的情况)
1、加全局读锁:flush tables with read lock;
2、//在windows的命令窗口输入这行
mysqldump -u root -p 123 > 123备份1.sql
3、//接下来输入密码
4、解锁 unlock tables;
对表加锁 :lock tables table_name read/write;
对表解锁:unlock tables;
行锁是通过对索引上的索引项加锁而实现的。
select。。。不加锁
select。。。lock in share mode(加锁用分享模式) 手动加读锁
select。。。for update(为了修改加锁)手动加写锁
insert 。。。 自动加读锁
update。。。自动加读锁
delete。。。自动加读锁