事务等级
1.读取未提交(RU)
所有事务可以读到其他未提交事务的执行结果。
缺点:
- 性能没有很大提高
- 某事物把未提交的数据读取,为脏读(Dirty Read)。
2.读取已提交(RC)
一个事务只能看见已经提交事务所做的改变。
缺点:
- 当某个事务执行时,多次查询,会查询到不一样的结果。(不可重复读,重复读同一条数据结果可能不一致)
3.可重复读(默认事务隔离级别)(RR)
确保同一事务的多个实例在并发读取数据时,会看到同样的数据行。
缺点:
- 在同一个事务中范围查询,别的事务在该范围间添加/删除/修改数据,会导致该事务中多次读取结果不一致,称之为幻读(Phantom Read)。
解决方案:
- mvcc
- 间隙锁
mvcc实现
MVCC在MySQL InnoDB中的实现主要是为了提高数据库并发性能,用更好的方式去处理读写冲突,做到即使有读写冲突时,也能做到不加锁,非阻塞并发读。
当前读:读取最新版本数据
使用场景
- 1.使用共享锁 select lock in share mode;
- 2.使用排他锁 select … for update;update;insert;delete;
快照读:读取历史版本数据
使用场景
- 不加锁的select读,非阻塞读。
MVCC实现
前提是,事务级别不是串行。基于并发控制考虑,快照读的实现是基于多版本并发控制,即MVCC,可以认为MVCC是行锁的。避免枷锁,降低开销;
MVCC模块在MySQL中的具体实现是由三个隐式字段,undo日志、read view三个组件来实现的。
隐式字段:
- DB_TRX_ID 事务号:记录创建这条记录,最后一次修改该记录事务的ID。每处理一个事务,其值自动 +1。
- DB_ROLL_PTR 回滚指针:指向这条记录的上一个版本 (存储于rollback segment回滚段里),通过这个指针才能查找之前版本的数据。
- DB_ROW_ID 隐含ID:如果数据库没有设置主键,那么InnoDB会以DB_ROW_ID 产生一个聚簇索引
- 创建和删除版本号:记录当前数据创建和删除的版本
read view全局属性:
首先要知道Read View中的三个全局属性:
- trx_list: 一个数值列表,用来维护Read View生成时刻系统正活跃的事务id (1,2,3)
- up_limit_id: 记录trx_list列表中事务最小的id(1)
- low_limit_id: Read View生成时刻系统尚未分配的下一个事务id(4)
MVCC判断顺序
判断是不符合执行下一个,符合条件读取read view数据。
- DB_TRX_ID < up_limit_id(事务列表中最小的id),表示别的事务可能修改数据,读取read view数据。
- DB_TRX_ID >= low_limit_id(未分配事务id),这该事务会读取别的事务的read view。
- DB_TRX_ID是否活跃在事务中,若果在事务中,已生成read view修改事务当前事务看不见。
间隙锁
加锁的基本单位为next-key lock。
当范围查询具体条件不是相等条件检索数据
,并请求共享锁或排他锁
时,innoDB会给符合条件的已有数据记录索引项加锁。
next-key-lock锁的区间为前开后闭区间。
共享锁(读锁/S锁):多个事务对于同一数据可以共享一把锁,都能访问到数据,但是只能读不能修改
触发条件:
排他锁(写锁/X锁):不能与其他所并存,如一个事务获取了一个数据行的排他锁,其他事务就不能再获取该行的其他锁,包括共享锁和排他锁,但是获取排他锁的事务是可以对数据就行读取和修改。
触发条件:
锁的粒度为id=1
添加for update
关键字,使用排他锁,等该事务完成之后,才能对id=1这行数据进行读写(获取共享锁或者排他锁)
select * from user where id = 1 for update;
共享查lock in share mode
,获取共享锁。
select * from user where id = 1 lock in share mode;
不使用锁,不管是否使用排他锁或者共享锁,都能查询结果。
select * from user where id = 1
间隙锁的触发条件:
范围查询并且查询未命中记录,查询条件必须命中索引、间隙锁只会出现在REPEATABLE_READ(重复读)的事务级别中。
select * from user_info where id > 1 and id < 4 for update
状态锁
状态锁包括意向共享锁和意向排它锁,把他们区分为状态锁的一个核心逻辑,是因为这两个锁都是都是描述是否可以对某一个表进行加表锁的状态。
- 当一个事务试图对整个表进行加锁(共享锁或排它锁)之前,首先需要获得对应类型的意向锁(意向共享锁或意向共享锁)
- 当一个事务试图对整个表进行加共享锁之前,首先需要获得这个表的意向共享锁。
- 当一个事务试图对整个表进行加排它锁之前,首先需要获得这个表的意向排它锁。
为什么要意向锁:
- innodb加锁的方式是基于索引,并且加锁粒度是行锁。
- 事务A对整表进行修改,需要判断是否表内的某一行有锁
- 需要遍历整个索引才能判断是否有锁,太浪费性能了。添加意向锁的状态,表示该表的某行是否有锁在使用。
4.串行化(SC)
所有事务按照次序依次执行,最高的安全性
缺点:
- 效率会大大下降,应用程序的性能会急剧降低
核心日志
1.bin-log
记录执行的数据,记录的是数据的逻辑日志,例如执行力什么更新语句等等,有STATEMENT或者SQL形式。
fsync刷盘时机:
redo log 为了提高性能也使用了缓存redo log buffer ,可以通过 innodb_flush_log_at_trx_commit 来配置刷盘策略,默认 = 1 ,不会丢数据。除了事务提交时刷盘,InnoDB存储引擎还有一个后台线程,每隔1秒,执行一次 write + fsync 刷盘。
- 0 :每次事务提交时不进行刷盘操作,mysql挂了会丢失1秒数据
- 1 :每次事务提交时都将进行刷盘操作 write + fsync,不会丢数据
- 2 :每次事务提交时只执行write,mysql挂了不会丢数据,服务器挂了会丢失1秒数据
根据my.cnf配置设置开启binlog
# 开启binlog 存放位置
log-bin = mysql-bin
# 最大的大小
max_binlog_size = 1G
# binlog的刷盘时机
sync-binlog = 1
2.undo-log
回滚日志,根据binlog的执行日志,生成一条可以返回原本数据版本的记录
3.redo-log
redo日志是根据原有的数据版本生成的一份数据备份,属于物理数据。
重做日志:保证数据的一致性。
重做日志的数据保存流程: