MySQL是怎么解决幻读的问题的?
幻读就是事务执行过程中,在查询一个范围的数据时,有新的数据插入到这个范围,导致两次查询的数据不一致。因为读分为快照读和实时读,
快照读
我们普通的SELECT语句都是普通读,也就是读取的数据都是事务开始时那个状态的数据,普通读的幻读问题主要是通过MVCC来解决的,具体可以看上面的MVCC中的查询操作。
实时读
SELECT *** FOR UPDATE 在查询时会先申请X锁
SELECT *** IN SHARE MODE 在查询时会先申请S锁
就是实时读,就是读取的是实时的数据,而不快照数据,读的时候会加Next-Key Lock锁住当前的记录,以及左右两个区间的间隙,这样在读的时候就不能往我们的查询范围插入数据了。
MySQL中有哪些锁?
全局锁
就是对整个数据库加锁,让整个数据库处于只读状态,所有更新操作停止。(如果是主库就不能执行更新语句,从库也不能执行同步过来的bin log)
最常用的场景是对数据库加锁,让数据库只能读,然后对整个数据库做逻辑备份(就是将所有数据生成SQL写入备份文件。)
做逻辑备份有三种方式:
1.全局锁
对数据库执行
Flush tables with read lock
命令让整个库处于只读状态。
2.利用innodb的事务隔离性(可重复读)
就是通过官方自带的逻辑备份工具mysqldump来进行逻辑备份时,可以设置一个参数-single-transaction,这样导数据的时候就会开启一个事务,这样利用innodb的mvcc机制可以保证在事务执行过程中,读到的数据都跟事务开始时的一致,并且执行过程中,其他事务可以执行更新操作, 不会对他造成影响。这种方法必须要求数据库所有表的引擎都是innodb才行。
3.set global readonly=true
执行这个命令也可以让全库只能读,但是第一有些系统会使用readonly来做一个操作,例如根据readonly是否为true判断数据库是否是从库,第二是如果执行这个命令后,客户端断开连接后,数据库会一直处于只读状态,如果是FTWRL命令发送异常会释放全局锁。(如果是从库,设置read-only对super user权限无效)
表级锁
表级别的锁有两种,一种是表锁,一种是元数据锁MDL。
表锁 lock table
就是使用lock table user_table read/write命令来对表进行加读锁或者写锁 加读锁后,表对所有线程都是只能读,即便是当前线程也只能读表,不然会数据不一致。 加写锁后,表是对当前线程写,其他线程不能读,不然会数据不一致。 可以通过unlock tables来解锁,客户端断开时也会自动释放锁,但是影响所有线程,影响面太大了。
元数据锁MDL(MetaData Lock)
分为读锁和写锁,加读锁时,所有的线程都可以读表,加写锁时,只能一个线程写,其他的不能读。 锁不用显式使用,是访问一个表时,自动加上的。 对表执行普通SQL语句对表数据进行增删改查时,会加读锁。 对表结构做修改时,会加写锁。
元数据锁是为了修改表结构不会出现问题而设计的,因为一边修改表结构一边读数据可能会读到脏数据,所以在增删改查时会申请读锁,在这个期间不能修改表结构,要修改表结构需要先申请写锁,申请成功后对表结构进行修改,在这个期间不能进行增删改查。
自增锁
插入语句主要分为两种: 1.能确定插入行数的,例如插入一条或者多条数据,INSERT... 2.不能确定行数的,例如从一个表查询出满足条件的数据,然后插入另外一个表,INSERT...SELECT
在所有模式中,如果一个事务回滚,这些自增值将被“丢失”。
innodb_autoinc_lock_mode为0 这种是tradition模式,每次执行一条插入语句时都会去申请表级别的auto_increment锁
innodb_autoinc_lock_mode为1 这种是consecutive模式,执行不确定数量的插入语句时,才会去申请表级别的auto_increment锁, 执行确定数量的插入语句时,只需要执行前去获取 AUTO_INCREMENT 计数器的互斥锁并在获取主键后直接释放, 不需要等待当前语句执行完成。
innodb_autoinc_lock_mode为2 交叉模式 所有的插入语句都不需要获取表级别的 AUTO_INCREMENT 锁, 如果binlog_format为statement模式,如果从服务器上的计数器的值可能会与主服务器不一致, 可能会有同一行数据在主从数据库上id不一样的情况,如果binlog_format为row模式,那么就不影响。
意向锁
意向锁定的主要目的是表明有人正在锁定表中的行,或者打算锁定表中的行。意向锁的作用主要在于,当一个事务去申请表级别的排斥锁X,共享锁S时,需要去判断是否有其他事务在修改数据行,或者让数据行处于只读状态。假如没有意向锁,可能需要查询每一行数据,判断是否加了行锁。而如果有意向锁的情况下,可以快速进行判断,只需要判断当前表是否有加意向锁就可以了,减小了性能开销。
意向共享锁(IS锁)
事务让一行数据只能读,需要申请对这行数据加行级别的锁共享锁S,在申请S锁之前会主动申请表级别的共享意向锁IS锁。
意向排斥锁(IX锁)
事务在更新某一行数据时,需要申请对这行数据加行级别的锁排斥锁X,在申请X锁之前会申请IX
意向锁之间是兼容的,IS锁和IX是兼容,因为可能我们对第一行数据加S锁,那么会申请IS锁,对第二行数据加X锁,此时跟第一行的数据的S锁不冲突,所以也会先申请IX锁,由此可见,IS锁和IX之间不冲突,IS锁,IX锁与行级别的S,行级别的X之间也不冲突。
意向锁只是跟表级别的S,X锁可能会冲突,
场景1:假设一个事务要加表级别的S锁,让整个表只能被读。那么如果当前有意向锁IX,说明有其他事务在改数据,那么不能加,只能进行等待,等事务改完是否意向锁IX。
场景2:假设当前事务要加表级别的S锁时,让整个表只能被读。只有IS意向锁,没有IX锁,说明只是有其他事务在让数据只能被读取,不能被修改,那么加表级别S锁,也不会其他事务造成影响。
场景3:假设当前事务要加表级别的X锁时,让整个表只能被这个事务写,不能被其他事务读。如果现在有其他事务加了意向读锁IS,说明有其他事务在让一些数据行只能被读,或者是一些写锁IX,说明其他事务让一些数据行正在被修改。那么当前要加表级别的X锁就不行,会跟其他事务冲突,只能等其他事务执行完毕才能申请成功。
表级别的S锁 | 表级别的X锁 | |
意向读锁IS | 兼容 | 不兼容 |
意向写锁IX | 不兼容 | 不兼容 |
那么意向锁的作用是什么呢?
假如没有意向锁,我们执行lock table read命令来申请表锁,让整个表只能读,在获得表级别的只读锁之前,需要执行的步骤是:
1.数据库会先判断当前表是否加了表级别的排斥锁,因为这个时候要是加了排斥锁,是只能由加了那个排斥锁的事务来更新数据,其他事务都不能读数据,只能阻塞等待。
2.如果当前表没有加表级别的排斥锁,那么就需要对每一行数据进行判断,判断是否加了行级别的X锁,如果加了只能阻塞等待,这样需要对一行进行判断,性能开销太大了。
所以才有了意向锁,在获得表级别的只读锁之前,需要执行的步骤是:
1.第一步还是跟上面的步骤一一样
2.第二步只需要判断当前锁是否加了表级别的意向排斥锁,因为如果加了意向排斥锁,说明正在有事务在对数据加行锁,对数据进行更新,这样避免了对每一行数据进行判断,判断是否加了行锁。