ACID作用及实现原理
1. 原子性(Atomicity)
事务被视为不可分割的最小单元,事务的所有操作要么全部提交成功,要么全部失败回滚。
回滚可以用回滚日志来实现,回滚日志记录着事务所执行的修改操作,在回滚时反向执行这些修改操作即可。
2. 一致性(Consistency)
数据库在事务执行前后都保持一致性状态。在一致性状态下,所有事务对一个数据的读取结果都是相同的。
3. 隔离性(Isolation)
一个事务所做的修改在最终提交以前,对其它事务是不可见的。
4. 持久性(Durability)
一旦事务提交,则其所做的修改将会永远保存到数据库中。即使系统发生崩溃,事务执行的结果也不能丢失。
使用重做日志来保证持久性。
四大隔离级别,以及不可重复读和幻影读的出现原因
未提交读(READ UNCOMMITTED)
事务中的修改,即使没有提交,对其它事务也是可见的。
提交读(READ COMMITTED)
一个事务只能读取已经提交的事务所做的修改。换句话说,一个事务所做的修改在提交之前对其它事务是不可见的。
可重复读(REPEATABLE READ)
保证在同一个事务中多次读取同样数据的结果是一样的。
可串行化(SERIALIZABLE)
强制事务串行执行。
需要加锁实现,而其它隔离级别通常不需要。
产生并发不一致性问题主要原因是破坏了事务的隔离性,解决方法是通过并发控制来保证隔离性。并发控制可以通过封锁来实现,但是封锁操作需要用户自己控制,相当复杂。数据库管理系统提供了事务的隔离级别,让用户以一种更轻松的方式处理并发一致性问题。
封锁的类型以及粒度,两段锁协议,隐式和显示锁定
锁粒度:行级锁和表级锁
类型:读写锁和共享锁
两端锁是实现可串行化的充分条件不是必要条件
MySQL 的 InnoDB 存储引擎采用两段锁协议,会根据隔离级别在需要的时候自动加锁,并且所有的锁都是在同一时刻被释放,这被称为隐式锁定。
InnoDB 也可以使用特定的语句进行显示锁定:
SELECT ... LOCK In SHARE MODE; SELECT ... FOR UPDATE;
乐观锁和悲观锁
悲观锁:悲观锁认为并发是每时每刻都在发生的。因此为了防止并发,我们在update数据库的数据行之前,需要先把这行数据先锁定起来。其他的任务如果想要update当前的数据行,需要等待当前的任务完成,亦或是选择放弃等待,给外界提示。通过这样的方式。悲观锁实现了更新数据行的串行化,即每个更新语句之间是串联的执行的。悲观锁由数据库提供支持,oracle mysql均提供 select for update 这种语句,它对查询出来的行进行加锁。这种加锁的方式,第一个加锁成功后,后面任务会一直尝试加锁到加上为止。oracle 还提供了select for update nowait 语句,它会尝试加锁,一旦加锁失败就会立即返回加锁失败。悲观锁的优点是更新的方式简单,缺点是更新的速度变慢了。
乐观锁:乐观锁认为并发并不是每时每刻都在发生。有可能会发生,但是大概率不会发生。乐观锁是通过在db行上加上版本的方式来实现的。每次更新之前,都查询出来要更新行的版本值是多少,然后在更新的时候,更新的条件上要带上查询出来的版本,更新的内容需要把版本值加1.通过这种行为模式,如果更新的返回值是1,代表在更新的这一刻是没有并发,如果更新的返回值是0,代表着数据行被别人更新过了,程序需要做另外的动作。乐观锁的优点是更新数据的速度提高了,缺点是一旦并发发生的概率大了,程序需要处理更新失败的情况。
MVCC 原理,当前读以及快照读,Next-Key Locks 解决幻影读
多版本并发控制。InnoDB为每行记录添加了一个版本号(系统版本号),每当修改数据时,版本号加一。
在读取事务开始时,系统会给事务一个当前版本号,事务会读取版本号<=当前版本号的数据,这时就算另一个事务插入一个数据,并立马提交,新插入这条数据的版本号会比读取事务的版本号高,因此读取事务读的数据还是不会变。
1. 快照读
使用 MVCC 读取的是快照中的数据,这样可以减少加锁所带来的开销。
select * from table ...;
2. 当前读
读取的是最新的数据,需要加锁。以下第一个语句需要加 S 锁,其它都需要加 X 锁。
select * from table where ? lock in share mode; select * from table where ? for update; insert; update; delete;