先来看看四种事务隔离级别和三种读
● 读未提交,一个事务可以读到其他事务做了修改但是还没提交的数据,会出现脏读、不可重复度、幻读等问题;
● 读已提交,一个对数据进行了修改的事务要提交之后,其他事务才能读取到,会出现不可重复读和幻读;
● 可重复读,同一个事务内多次查询的数据保持一致,会出现幻读;
● 串行化,在修改的数据上加读锁和写锁,保持到事务结束后才能释放,因为加了锁,所以效率也降低了不少;
● 脏读,一个事务读取到其他事务还未提交的数据;
● 不可重复读,一个事务A读取事务,另一个事务B将此数据修改,A再去查数据,发现前后不一样,也就是不可重复读是前后数据不一致,发生在数据行上;
● 幻读,前后的数据记录量不一致,发生在数据表上;
MVCC也就是多版本并发控制,是处理数据库读写冲突的一种手段,为了提高数据库高并发场景下的吞吐性能;MVCC自在RC、RR两种隔离级别下工作,其他两个隔离级别够和MVCC不兼容, 因为读提交总是读取最新的数据行, 而不是符合当前事务版本的数据行。而可重复读则会对所有读取的行都加锁。他的实现主要是依靠三个隐藏的字段(事务Id、回滚指针、隐藏主键ID)、undolog、和read view读视图来实现的,通过三个隐藏的字段和undolog可以形成一个版本链,这个版本链的头节点就是当前最新的记录,而读视图呢主要是用来判断版本链中的数据,哪个版本是当前事务可见的,readview主要是将当前活跃的读写事务放到一个m_ids的数组中,
● 如果被访问版本的事务Id小于m_ids数组中最小的事务Id,表明生产该版本的事务在生成readview前已经提交,所以该版本可以被当前事务访问;
● 如果被访问版本的事务Id大于m_ids数组中最大的事务Id,表明生成该版本的事务,所以是在生成readview之后才生成,所以该版本不可以被当前事务访问;
● 如果被访问版本的事务Id在m_ids数组最大值和最小值之间,那就需要先判断这个版本的事务Id是不是在m_ids这个数组中,说明创建readview时生成该版本的事务还是活跃的,那么该版本还不可以被访问如果不在,说明创建readview时,生成该版本的事务已经被提交,该版本可以被访问;
● 某个版本对当前事务不可见的话,那就继续判断版本链的下一条记录接着判断;
RR级别下,是如何解决快幻读的问题的?
● 快照读和当前读:快照读指的就是普通的select,而当前读就是加锁的select和update、delete以及insert;
对于快照读的场景,通过MVCC版本管理是可以来解决幻读的,RR为什么能解决?在第一次执行简单的select语句就生成了一个快照,并且在后边的select查询都是沿用第一次快照读的结果;但是在当前读的场景下,MVCC就不能了,而是被next-key-locks干掉的,next-key-locks是record lock行锁和gap 间隙锁的结合,锁定的是一个范围,如果查询的数据为索引记录行,就只会锁定记录行,降级为record lock(唯一索引上的等值查询),如果为范围就会锁定一个范围;()
记录锁是加在索引上的锁,如果列上没有锁怎么办?
● 如果锁的是非主键索引,会在索引上加锁,然后再去主键索引加锁;
● 如果表上没有索引,会在隐藏的主键索引上加锁,如果没有主键,则先找第一非空的唯一索引作为主键索引,如果都没有,则就是用row_id来维护一个逐渐索引;
● 如果要锁的列没有索引,进行全表记录加锁;
mysql中的锁
乐观锁与悲观锁
● 乐观锁,每次读数据的时候都认为其他人不会修改,所以不会上锁,而是在更新的时候去判断在此区间有没有其他人更新了数据,乐观锁适用于读多的场景,可以提高吞吐量;
● 悲观锁,每次读数据的时候都认为别人会修改,所以每次读数据的时候都会上锁,别人想读这个数据的时候就会被阻塞;行锁,表锁,读锁,写锁等都是用的这种锁机制,适合写多的场景;
共享锁与排他锁
● 共享锁,又叫读锁或者S锁,加上读锁后,在事务结束之前,其他事务访问此数据的话,只能加共享锁,只能对其进行读操作不能写操作,除此之外的其他所有类型的额锁都不能再加了; lock in share mode
● 排他锁,又叫写锁或者X锁,某个事务对数据加锁后,在这个事务结束之前,其他事务不能对其加任何锁,可以读写,但是不能写,for update
行锁与表锁
● 表锁,开销小,加锁快;不会出现死锁;锁的粒度大,发生锁冲突概率高,并发度低;
● 行锁,开销大,加锁慢;会出现死锁;锁的粒度小,发生锁冲突的概率低,并发度高;如果查询条件用到了索引或者主键,则用到的是行锁,否则就是表锁;