1、事务的ACID四个特性
原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability)。
持久性(Durability)
redo日志保存的是执行成功但没写入磁盘的操作。当断电/数据库崩溃等状况发生时把redo日志写到磁盘,保证了事务的持久性(Durability)。
原子性(Atomicity)
undo日志是保存事务执行一部分尚未提交的操作,当断电/数据库崩溃,保证了事务的原子性(Atomicity)。
隔离性
数据库使用锁的方式保证隔离性。
一致性
由前面三个特性保证,也是最重要的特性。
2、如何保证一致性
1、悲观锁:
共享锁(读锁):一个线程给数据加上共享锁后,其他线程只能读取数据,不能修改。
排它锁(写锁):一个线程给数据加上排它锁后,其他线程不能读取也不能修改。
按锁粒度分:
表锁:给整个表加锁,如:select * from tab_no_index where id = 1 for update; 如果tab_no_index没有索引,未完成时其他进程访问该表任何资源将等待.
行锁:InnoDB行锁是通过给索引上的索引项加锁来实现的,只有通过索引条件检索数据,InnoDB才使用行级锁,否则,InnoDB将使用表锁,如表锁的id加上索引
页锁 间隙锁:当我们用范围条件而不是相等条件检索数据,并请求共享或排他锁时,InnoDB会给符合条件的已有数据记录的索引项加锁。
如Select * from emp where empid > 100 for update; InnoDB不仅会对符合条件的empid值为101的记录加锁,也会对empid大于101(这些记录并不存在)的“间隙”加锁。
2、乐观锁:
通过加锁控制,可以保证数据的一致性,但是同样一条数据,不论用什么样的锁,只可以并发读,并不可以读写并发(因为写的时候加的是排他锁所以不可以读),为了实现并发,提高效率,引入乐观锁概念。
乐观锁没有真正的加锁,只是一种概念,InnoDB使用版本链(Multi-Version Concurrency Control ,多版本并发控制)来实现乐观锁。
原理:
在事务的第二三隔离级别使用(隔离级别下面会介绍)
1,写事务发生时,首先复制一份旧数据,以事务号区分
2,写事务操作新克隆的数据,直至提交
3,并发读的任务可以继续从旧数据读取数据,从而实现不相互阻塞
3、隔离性分为四个级别:
数据库事务有不同的隔离级别,不同的隔离级别对锁的使用是不同的,锁的应用最终导致不同事务的隔离级别。
1、读未提交:(Read Uncommitted)
2、读已提交(Read Committed)
3、可重复读(Repeatable-Read) mysql数据库所默认的级别
4、序列化(serializable)
并发事务执行过程中可能遇到的一些问题:按照严重性来排序:脏读 > 不可重复读 > 幻读。下面介绍下这三种问题以及四种隔离级别如何解决这些问题的:
刚刚说了从上到下是的四个级别是因为加锁的力度不同,越往下加锁越严格,隔离性越好,数出错概率越少,但是也会对性能影响严重,造成不能同时访问的问题。所以要选一个适合自己业务的隔离性级别,达到可容忍的数据出错跟并发量的平衡。
脏读:一个事务读到了另一个未提交事务修改过的数据。
1)A修改事务级别为:未提交读。并开始事务,对user表做一次查询
2)B事务更新一条记录
3)此时B事务还未提交,A在事务内做一次查询,发现查询结果已经改变
由试验得知:在一个进程的事务当中,我更改了其中的一行数据,但是我修改完之后就释放了锁,这时候另一个进程读取了B事务修改的数据,这种情况叫脏读,下一级别是怎么解决这个问题的呢。
读已提交(Read Committed)等级下解决脏读办法
1、把释放锁的位置调整到事务提交之后,此时在事务提交前,其他进程是无法对该行数据进行读取的,包括任何操作。
2、mysql中在该级别开始使用之前提到的MVCC机制,简单来说就是:B事务要修改数据时复制一份旧数据,以事务号区分,在复制的数据上修改直至提交。A事务可以继续从旧数据读取数据,从而实现不相互阻塞。
不可重复读:一个事务只能读到另一个已经提交的事务修改过的数据,并且其他事务每对该数据进行一次修改并提交后,该事务都能查询得到最新值。
1)把隔离性调为READ-COMMITTED(读取提交内容)设置A的事务隔离级别,并进入事务做一次查询
2)B开始事务,并对记录进行修改
3)A再对user表进行查询,发现记录没有受到影响
4)B提交事务
5)A再对user表查询,发现记录被修改
可重复读(Repeatable-Read) 等级下解决不可重复读办法:
MySQL第一次读的时候仍然会保持选择读最新提交事务的数据,当第一次之后,之后再读时,mysql会取第一次读取的数据作为结果。这样就保证了同一个事务多次读取数据时数据的一致性。而不是每次select都去取最新数据
幻读:一个事务先根据某些条件查询出一些记录,之后另一个事务又向表中插入了符合这些条件的记录,原先的事务再次按照该条件查询时,能把另一个事务插入的记录也读出来
幻读和不可重复读(Read Committed)都是读取了另一条已经提交的事务,所不同的是不可重复读注重的是修改的数据,而幻读针对的是新增删除的数据。
比如:A事务读取或更新了表里的所有行,B事务往该表里插入一个新行,在事务提交后。原来读取或更改过数据的事务又第二次读取了相同的数据,这时候这个事务中两次读取的结果集行数就不一样。这就是幻读。
序列化(serializable)等级下解决幻读方法:
该隔离级别会锁住你要操作的整个表的数据,如果另一个进程事务想要操作表里的任何数据就需要等待获得锁的进程操作完成释放锁。可避免脏读、不可重复读、幻读的发生。