数据库(三) 事务、引擎与锁机制

一、事务

事务:事务是并发控制的基本单位。即一组操作序列,这些序列要么都执行,要么都不执行,它是一组不可分割的工作单位;事务的提出主要是为了解决并发情况下保持数据一致性的问题,事务具有以下四个基本特征:

(1)原子性(Atomic):事务中包含的每个操作都被看成一个逻辑单元,这个逻辑单元中的操作要么全部成功,要么全部失败;

(2)一致性(Consistency):事务执行的结果必须是使数据库从一个一致性状态变到另一个一致性状态。也就是事务执行前和事务执行后数据内在的逻辑始终是成立的;比如转账前后两个人的存款总和始终不变;

(3)隔离性(Isolation):一个事务的执行不能被其他事务干扰;

(4)持久性(Durability):一个事务一旦提交,它对数据库中数据的改变就是永久性的,接下来的操作或故障不应该对其执行结果有影响;

原子性一定能保证一致性吗?

不,原子性不能完全保证一致性。因为在多个事务并行进行的情况下,即使保证了每个事务的原子性,仍然可能导致数据不一致的结果。比如一个事务的执行结果被另一个事务覆盖掉了;

事务ACID特性可能遭到破坏的原因有:

(1)多个事务并行运行时,不同的事务操作交叉执行;此时需要保证多个事务的交叉运行不影响这些事务的原子性;

(2)事务在运行过程中被强行停止;此时必须保证被强行终止的事务对数据库和其他事务没有任何影响;

二、隔离级别

(1)读未提交(Read uncommitted):如果一个事务已经开始写数据,则另外一个事务不允许同时进行写操作,但允许其他事务读取此行数据。该隔离级别可以通过排他锁实现。避免了丢失修改,但是却可能出现脏读;

(2)读已提交(Read committed):读取数据的事务允许其他事务继续访问该行数据,但是未提交的写事务将会禁止其他事务访问该行。读已提交是在读未提交的基础上避免了脏读;但是问题又来了,假如某行记录有一列值为0,事务T1正在对其进行更新操作,把值update为1,同时事务T2正在对该行记录进行查询操作。由于T2不允许读取T1未提交的数据,此时T2读到的值为0;假设T1已经提交,但是T2并未结束,并且又对该记录进行了已查询,发现此列数据的值变成了1,那么问题出现了:T2在对同一数据进行相同的两次查询操作时,得到的数据的值并不一致,此时发生了不可重复读的问题;

(3)可重复读(Repeatable read):读取数据的事务将会禁止写事务,但是允许读事务;而写数据的事务则禁止其他事务。可以通过共享锁和排他锁避免不可重复读和脏读,但有时可能出现幻读;

(4)序列化(Serializable):提供严格的事务隔离,它要求事务序列化执行,即事务只能一个接着一个执行,不能并发执行。如果仅仅通过行级锁是无法实现事务序列化的,必须通过其他机制保证新插入的数据不会被刚执行查询操作的事务访问到。序列化是最高的事务隔离级别,同时代价也花费最高,但是性能最低,一般很少用。在该级别下,事务顺序执行,不仅可以避免脏读、不可重复读,还可以避免幻读;

隔离级别越高,越能保证数据的完整性和一致性,但是对并发性能的影响也越大。对于多数应用程序,比如Sql Server和Oracle,默认的是读已提交。对于MySQL,默认隔离级别是可重复读;

如果不设置隔离级别,则会出现以下问题:

(1)脏读:一个事务处理过程里读取到了另一个未提交的事务中的数据。事务T1修改某一数据并将其写回磁盘,事务T2读取同一数据后,T1由于某种原因被撤销,此时被T1修改过的数据恢复原值,T2读到的数据就与数据库中的数据不一致,即T2读到的数据就为脏数据;

(2)不可重复读:在同一个事务内,针对同一数据进行多次相同的查询但是返回的结果不同;当事务T1读取某一数据之后,事务T2对其进行了修改,当事务T1再次读取该数据时,得到与前一次不同的值;

(3)丢失修改:事务T1撤销时把已经提交的事务T2更新的数据覆盖了是第一类丢失修改;两个事务T1和T2读入同一数据并修改,T2提交的结果破坏了T1提交的结果,导致T1的修改被丢失;

(4)幻读:事务T1按一定条件从数据库中读取某些数据记录之后,事务T2删除或者插入了一些记录,当T1再次按相同条件读取数据时,发现某些记录消失或者多了出来;

幻读和不可重复读的区别:

幻读和不可重复读都是在同一次事务中按照相同的条件查询发现结果不一致,区别在于,不可重复读的重点在于修改:同样的条件,读取出来发现值不一样;而幻读的重点在于新增或者删除:同样的条件,第一次和第二次读取出来的记录数不一样;

从控制的角度来看,区别更大。对于不可重复读,只需要锁住满足条件的记录;而对于幻读,它是由于并发事务增加或者删除记录导致的,不能像不可重复读通过记录加锁解决,因为对于新增的记录根本无法加锁,需要将事务序列化,才能避免幻读;

三、数据库范式

第一范式1NF:属性不可分;

第二范式2NF:所有属性完全依赖于主键;

第三范式3NF:消除传递依赖;

范式具有包含关系,比如第三范式包含第二范式,第二范式包含第一范式。

第一范式—>第二范式---->第三范式

(从左到右)数据冗余越来越少,查询越来越复杂;
(从右到左)有数据冗余,但查询简单;

四、引擎

Innodb引擎:提供了对数据库ACID事务的支持,并且实现了SQL标准的四种隔离级别,该引擎提供了行级锁和外键约束。该引擎没有保存表的行数,有的时候需要扫描全表。当需要使用数据库,且并发性较高时,使用Innodb会提升效率。但是使用行级锁也不是绝对的,如果在执行一个SQL语句时MySQL不能确定要扫描的范围,Innodb同样会锁全表;

MyIASM引擎:不提供对数据库事务的支持,也不支持行级锁和外键。存储了表的行数,如果表的读操作远远多于写操作且不需要数据库事务的支持,那么MyIASM是首选;

区别:
(1)Innodb支持事务,而MyIASM不支持;

(2)Innodb支持外键,而MyIASM不支持;

(3)Innodb支持行级锁,MyIASM支持表级锁;

(4)Innodb不支持全文索引,MyIASM支持;

五、锁模式

共享锁(S锁):若事务T对数据A加上S锁,则事务T可以读A但是不能修改A,其他事务只能对A加S锁而不能加X锁,直到T释放A上的S锁为止。这就保证了其他事务可以读A,但在T释放A上的S锁之前不能对A进行任何修改;

排他锁(X锁):若事务T对数据A加上X锁,则只允许T读取和修改A,其他任何事务都不能对A加任何类型的锁,直到T释放A上的锁为止。这就保证了其他事务在T释放A上的锁之前不能再读取和修改A;

死锁出现:如果两个事务获得了资源上的S锁,然后试图同时更新数据,则两个事务都要转换S锁为X锁,并且每个事务都等待另一个事务释放S锁的情况下,就产生了死锁;

更新锁(U锁):可以避免这种潜在的死锁问题。一次只有一个事务可以获得资源的U锁,如果事务修改资源,则U锁转换为X锁,否则,转换为S锁;

悲观锁:每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁;

乐观锁:假设认为数据一般情况下不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果发现冲突了,则让返回用户错误的信息,让用户决定如何去做。一般的实现乐观锁的方式就是记录数据版本;

发布了622 篇原创文章 · 获赞 150 · 访问量 31万+

猜你喜欢

转载自blog.csdn.net/feizaoSYUACM/article/details/82933901