一、事务
事务是访问数据库的一个操作序列,数据库通过一系列事务来存取数据。
事务特性ACID:
原子性:要么都执行,要么都不执行。
一致性:事物执行完成后数据库从一个一致状态到另一个一致状态。
隔离性:事物所做的操作在提交之前,对其他事物不可见。
永久性:事物一旦提交,对数据库做的改变会永久的存储在数据库中
当事物串行执行,隔离性一定是可以满足的,只要满足原子性,就保证了一致性。
当事物并发执行,事物要满足原子性和隔离性才能保证一致性。
事物的持久话是为了应付数据库崩溃。
二、并发下的一致性问题
并发环境下由于隔离性难以保证而引发的一致性问题:
1.丢失更新。事物2覆盖了事物1的修改。
2. 脏读:读的数据还没有提交,如果回滚了,那么读到的数据就是无效的。
3.不可重复读:在一事务里,读同一数据读了两次,结果两次结果不一样。
4.幻读:在一个事务里面的操作中发现了未被操作的数据。比如就是在一个事务修改数据的时候,切换到了另一个事务B,B插入了一个数据,再切换到A的时候,A发现多了一个他没有修改过的数据。
脏读和幻读有点类似:脏读是其他事务修改了数据。幻读是其他事务新增了数据。
产生并发不一致主要是破坏了事物的隔离性。解决办法是通过并发控制解决隔离,并发控制可以通过封锁来实现,但是封锁需要用户自己控制。数据库管理系统提供了事物的隔离级别,让用户用更轻松的方式处理并发一致性问题。
三、封锁
封锁粒度
MySQL从锁的粒度来看可以分为:行级锁和表级锁。
尽可能锁的数量少,并发程度高。但是加锁消耗资源,锁的粒度越小,系统开销越大。所以在选择的时候需要在锁的粒度和开销之间做权衡。
封锁类型
1.读写锁
写锁又叫排他锁,读锁叫共享锁。一个事物对数据对象A加了X锁,就可以对A进行读取和更新,加锁期间其他事物不能对A加任何锁。
一个事物对数据对象A加了S锁,就可以读取,其他事物也能加S。
2.意向锁
使用意向锁可以更容易的支持多粒度封锁。
一般一个事物想要对一张表加锁的时候,会先检查表上有没有其他锁,消耗一定的时间。
意向锁是指一个事务想要在表中的某个数据行上加X/S锁,关于加锁的规定是:
一个事务在获得某个数据行对象的S锁之前,必须先获得表的IS锁或更强的锁。
一个事务在获得某个数据行对象的X锁之前,必须先获得表的IX锁。
所以说,如果事物T想要对表A加X锁,任意的意向锁都是兼容的,毕竟只是想要对表加锁。
问题:如果两个IX,同时加锁了呢?
封锁协议
三级封锁协议
1.一级封锁协议
事务T要修改数据A时必须加X锁,直到T结束才释放。解决丢失更新。
2.二级封锁协议
在一级的基础上,要求读数据A必须加上S锁,读完就释放,解决读脏数据。
如果不加读锁,很明显读出来脏数据了。
3.三级封锁协议
事务在读数据A时必须加S锁,直到事务结束了才释放S锁,在这期间就不能又其他事务对A加X锁,从而避免在读的期间数据发生改变。
乍一看好像二级和三级差不多,区别是:例如一个读取数据的事务T1前后读了两次,如果第一次读完就释放了,进来一个事务T2给他修改了,过了一会事务T1又进来了,这个时候读的就和第一次不一样了,所以三级是读事务到整个结束再释放。解决不可重复读。
两段锁协议
是指每个事务的执行可以分为加锁和解锁分两个阶段进行。
事务开始后就处于加锁阶段,一直到执行ROLLBACK和COMMIT之前都是加锁阶段。ROLLBACK和COMMIT使事务进入解锁阶段,即在ROLLBACK和COMMIT模块中DBMS释放所有封锁。
若并发执行的所有事务都遵循两段锁协议,那么这些事务的任何并发调度策略都是可串行化的。
可串行化指的是,通过并发控制,使得并发执行的事务结果u与某个串行执行的事务结果相同。
注意:注意两段锁协议和防止死锁的一次封锁法的异同之处。一次封锁法要求每个事务必须一次将所有要使用的数据全部加锁,否则就不能继续执行,因此一次封锁法遵守两段锁协议;但是两段锁协议并不要求事务必须一次将所有要使用的数据全部加锁,因此遵守两段锁协议的事务可能发生死锁。
MySQL隐式与显式锁定
MySQL的InnoDB存储引擎采用两段锁协议,会根据隔离级别在需要的时候自动加锁,并且所有的锁都是在同一时刻被释放,这被称为隐式锁定。
四、事务的隔离级别
隔离级别就是为了并发下的一致性问题,事务的隔离级别越高,在并发下产生的问题就越少,但付出的性能损耗也更大。一般有4种,但Spring有5种。
Spring默认的一种,如果spring配置事务时将isolation设置为这个值的话,那么将使用底层数据库的默认事务隔离级别。
1.读未提交(read uncommitted):事务中的修改,即使没有提交也可以读。可能导致脏、幻、不可重复。
2.读提交(read committed):读已经提交的数据。肯定没有脏读,可能导致幻、不可重复。Oracle默认
3.重复读(repeatable read): 在数据读出来之后加锁,读完这个事务也不结束,加了个锁,别的事务不可以改变这个记录,解决了脏读、不可重复。Mysql默认
4.可串行化(serializable):不管多少个事务,挨个运行完一个事务的所有子事务之后才可以允许别的事务的子事务。Oracle也支持。
一般就用读提交。
五、多版本并发控制
(首先为了理解mvcc作出补充)为了支持事务,InnoDB引入了下面几个概念:
redo log
redo log就是保存执行的SQL语句到一个指定的Log文件,当MySQL执行recovery时重新执行redo log记录的SQL操作即可。
undo log与redo log相反,undo log是为回滚而用,具体内容就是copy事务前的数据库内容(行)到undo buffer,在适合的时间把undo buffer中的内容刷新到磁盘。
rollback segment,回滚段这个概念来自Oracle的事务模型,在InnoDB中,undo log被划分为多个段,具体某行的undo log就保存在这个段中,
1.多版本并发控制(MVCC)
mvcc是InnoDB实现事务隔离的一种方式,用于实现读提交和可重复读两种。
剩下两种未提交读每次读最新的数据,不需要mvcc,可串行化需要对所有读取的行都加锁,单纯的mvcc无法实现。
在mvcc当中,每个事务都有一个编号,依次递增,事务的编号也表示系统版本号。
mvcc在每一行数据后面都隐藏这两个列:创建版本号,删除版本号。创建版本号是创建当前数据行快照时的事务编号,也是当前版本号。删除版本号:如果该快照的删除版本号>当前事务版本号,说明快照有效。
mvcc把所有快照存储到Undo日志中,undo日志通过回滚指针把一个数据行的所有快照连起来。
2.如何实现可重复读隔离界别:
当开始一个新事物,他的版本号肯定会>所有行快照的创建版本号。
insert:用当前系统版本号作为数据行快照的创建版本号。
delete:用当前系统版本号作为数据行快照的删除版本号。
update:先用当前系统版本号作为更新前数据行快照的删除版本号,再把系统版本号作为更新后数据行快照的新建版本号。相当于先删除再添加。
select:一个读事务T,当他要读取的时候,只能读取数据行快照的创建版本号小于事务T的版本号,如果版本号大于或者等于T,说明这是其他事务的最新修改,还不能读取,(为什么)除此之外,数据行快照的删除版本号必须大于事务T的版本号,如果小于等于T,说明数据行已经被删除,不应该读他。
如果有一个事务正在修改该数据行,那么它可以读取事务本身所做的修改,而不用和其它事务的读取结果一致。
3.快照读和当前读
快照读是使用mvcc读取的是快照中的数据,可以减少加锁带来的开销。
当前读是读最新的数据,需要加锁,第一行读取需要加S锁,后面都要加X锁。
select * from table where ? lock in share mode;
select * from table where ? for update;
insert;
update;
delete;
六、Next-Key Locks
在可重复读隔离界别下,使用MVCC和间隙锁可以解决幻读问题。
(直接select for update也可以解决幻读)
索引锁:锁定一个记录上的索引,而不是记录本身。
如果表没有设置索引,InnoDB会自动在主键上创建隐藏的聚簇索引,因此索引锁依然能够使用。
间隙锁:锁定索引之间的间隙。比如锁住id是0到10的,即便没有5-7,5-7也会被锁。
Next-Key Locks:他是间隙锁和索引锁的结合,不仅锁定记录的索引,也锁定索引之间的间隙。
这到底有啥用,没明白。
其他:
范式:
1NF:属性不可再分
2NF:在1NF基础上消除部分函数依赖,就是说非主属性完全函数依赖于候选键。
3NF:在2NF的基础上,非主属性没有传递函数依赖候选键。
BCNF:满足3NF,消除每一属性对候选键的传递依赖。
4NF:满足BCNF,消除非平凡且非函数依赖的多值依赖。
5NF:消除不是由候选码所蕴含的连接依赖。
https://blog.csdn.net/SevenGirl2017/article/details/77678233
整理https://www.cnblogs.com/xrq730/p/5087378.html
https://github.com/CyC2018