1、事务四大属性html
分别是原子性、一致性、隔离性、持久性。数据库
一、原子性(Atomicity)网络
原子性是指事务包含的全部操做要么所有成功,要么所有失败回滚,所以事务的操做若是成功就必需要彻底应用到数据库,若是操做失败则不能对数据库有任何影响。session
二、一致性(Consistency)并发
一致性是指事务必须使数据库从一个一致性状态变换到另外一个一致性状态,也就是说一个事务执行以前和执行以后都必须处于一致性状态。举例来讲,假设用户A和用户B二者的钱加起来一共是1000,那么无论A和B之间如何转帐、转几回帐,事务结束后两个用户的钱相加起来应该还得是1000,这就是事务的一致性。性能
三、隔离性(Isolation)优化
隔离性是当多个用户并发访问数据库时,好比同时操做同一张表时,数据库为每个用户开启的事务,不能被其余事务的操做所干扰,多个并发事务之间要相互隔离。关于事务的隔离性数据库提供了多种隔离级别,稍后会介绍到。spa
四、持久性(Durability).net
持久性是指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即使是在数据库系统遇到故障的状况下也不会丢失提交事务的操做。例如咱们在使用JDBC操做数据库时,在提交事务方法后,提示用户事务操做完成,当咱们程序执行完成直到看到提示后,就能够认定事务已经正确提交,即便这时候数据库出现了问题,也必需要将咱们的事务彻底执行完成。不然的话就会形成咱们虽然看到提示事务处理完毕,可是数据库由于故障而没有执行事务的重大错误。这是不容许的。命令行
https://blog.csdn.net/xiaokang123456kao/article/details/75268240
在数据库操做中,在并发的状况下可能出现以下问题:
脏读:一个事务读取到了另一个事务没有提交的数据。
A修改了数据,随后B又读出该数据,但A由于某些缘由取消了对数据的修改,数据恢复原值,此时B获得的数据就与数据库内的数据产生了不一致
幻读:同一事务中,用一样的操做读取两次,获得的记录数不相同。
A读取数据,随后B又插入了数据,此时A再读数据是发现先后两次获取的数据行集不一致
不可重复读:在同一事务中,两次读取同一数据,获得内容不一样。
A用户读取数据,随后B用户读出该数据并修改,此时A用户再读取数据时发现先后两次的值不一致
丢失更新:事务T1读取了数据,并执行了一些操做,而后更新数据。事务T2也作相同的事,则T1和T2更新数据时可能会覆盖对方的更新,从而引发错误。
A,B两个用户读同一数据并进行修改,其中一个用户的修改结果破坏了另外一个修改的结果,好比订票系统
并发控制的主要方法是经过锁,在一段时间内禁止用户作某些操做以免产生数据不一致
1. 更新丢失(Lost update)
若是多个线程操做,基于同一个查询结构对表中的记录进行修改,那么后修改的记录将会覆盖前面修改的记录,前面的修改就丢失掉了,这就叫作更新丢失。这是由于系统没有执行任何的锁操做,所以并发事务并无被隔离开来。
第1类丢失更新:事务A撤销时,把已经提交的事务B的更新数据覆盖了。
第2类丢失更新:事务A覆盖事务B已经提交的数据,形成事务B所作的操做丢失。
解决方法:对行加锁,只容许并发一个更新事务。
2. 脏读(Dirty Reads)
脏读(Dirty Read):A事务读取B事务还没有提交的数据并在此基础上操做,而B事务执行回滚,那么A读取到的数据就是脏数据。
解决办法:若是在第一个事务提交前,任何其余事务不可读取其修改过的值,则能够避免该问题。
3. 不可重复读(Non-repeatable Reads)
一个事务对同一行数据重复读取两次,可是却获得了不一样的结果。事务T1读取某一数据后,事务T2对其作了修改,当事务T1再次读该数据时获得与前一次不一样的值。
解决办法:若是只有在修改事务彻底提交以后才能够读取数据,则能够避免该问题。
4. 幻象读
指两次执行同一条 select 语句会出现不一样的结果,第二次读会增长一数据行,并无说这两次执行是在同一个事务中。通常状况下,幻象读应该正是咱们所须要的。但有时候却不是,若是打开的游标,在对游标进行操做时,并不但愿新增的记录加到游标命中的数据集中来。隔离级别为 游标稳定性 的,能够阻止幻象读。例如:目前工资为1000的员工有10人。那么事务1中读取全部工资为1000的员工,获得了10条记录;这时事务2向员工表插入了一条员工记录,工资也为1000;那么事务1再次读取全部工资为1000的员工共读取到了11条记录。
解决办法:若是在操做事务完成数据处理以前,任何其余事务都不能够添加新数据,则可避免该问题。
二、事务的隔离级别
为了解决数据库并发问题,数据库提供了几种隔离级别。如下隔离级别由低到高。
Read uncommitted(未受权读取、读未提交): 若是一个事务已经开始写数据,则另一个事务则不容许同时进行写操做,但容许其余事务读此行数据。该隔离级别能够经过“排他写锁”实现。这样就避免了更新丢失,却可能出现脏读。也就是说事务B读取到了事务A未提交的数据。 Read committed(受权读取、读提交): 读取数据的事务容许其余事务继续访问该行数据,可是未提交的写事务将会禁止其余事务访问该行。该隔离级别避免了脏读,可是却可能出现不可重复读。事务A事先读取了数据,事务B紧接了更新了数据,并提交了事务,而事务A再次读取该数据时,数据已经发生了改变。 Repeatable read(可重复读取): 可重复读是指在一个事务内,屡次读同一数据。在这个事务尚未结束时,另一个事务也访问该同一数据。那么,在第一个事务中的两次读数据之间,即便第二个事务对数据进行修改,第一个事务两次读到的的数据是同样的。这样就发生了在一个事务内两次读到的数据是同样的,所以称为是可重复读。读取数据的事务将会禁止写事务(但容许读事务),写事务则禁止任何其余事务。这样避免了不可重复读取和脏读,可是有时可能出现幻象读。(读取数据的事务)这能够经过“共享读锁”和“排他写锁”实现。 Serializable(序列化): 提供严格的事务隔离。它要求事务序列化执行,事务只能一个接着一个地执行,但不能并发执行。若是仅仅经过“行级锁”是没法实现事务序列化的,必须经过其余机制保证新插入的数据不会被刚执行查询操做的事务访问到。序列化是最高的事务隔离级别,同时代价也花费最高,性能很低,通常不多使用,在该级别下,事务顺序执行,不只能够避免脏读、不可重复读,还避免了幻像读。 隔离级别越高,越能保证数据的完整性和一致性,可是对并发性能的影响也越大。对于多数应用程序,能够优先考虑把数据库系统的隔离级别设为Read Committed。它可以避免脏读取,并且具备较好的并发性能。尽管它会致使不可重复读、幻读和第二类丢失更新这些并发问题,在可能出现这类问题的个别场合,能够由应用程序采用悲观锁或乐观锁来控制。大多数数据库的默认级别就是Read committed,好比Sql Server , Oracle。MySQL的默认隔离级别就是Repeatable read。 --------------------- 做者:想做会飞的鱼 来源:CSDN 原文:https://blog.csdn.net/xiaokang123456kao/article/details/75268240 版权声明:本文为博主原创文章,转载请附上博文连接!
* 为何须要锁?
在并发环境下,若是多个客户端访问同一条数据,此时就会产生数据不一致的问题,如何解决,经过加锁的机制,常见的有两种锁,乐观锁和悲观锁,能够在必定程度上解决并发访问。SQLSERVER经过锁来提供ACID属性,处理并发访问.
* 什么是悲观锁?
悲观锁,正如其名,具备强烈的独占和排他特性。它指的是对数据被外界(包括本系统当前的其余事务,以及来自外部系统的事务处理)修改持保守态度,所以,在整个数据处理过程当中,将数据处于锁定状态。悲观锁的实现,每每依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能真正保证数据访问的排他性,不然,即便在本系统中实现了加锁机制,也没法保证外部系统不会修改数据)。
悲观锁主要包含共享锁和排他锁。
共享锁:又称读锁/S锁,共享锁是在执行select操做时使用的锁机制。共享锁就是多个事务对于同一数据能够共享一把锁,都能访问到数据,可是只能读不能修改。
排他锁:又称写锁/X锁,排它锁是在执行update,delete等对数据有修改操做时使用的锁。排他锁就是不能与其余锁并存,如一个事务获取了一个数据行的排他锁,其余事务就不能再获取该行的其余锁,包括共享锁和排他锁,可是获取排他锁的事务是能够对数据就行读取和修改。
共享锁(S锁):用于读取资源所加的锁。拥有共享锁的资源不能被修改。共享锁默认状况下是读取了资源立刻被释放。
排他锁(X锁): 和其它任何锁都不兼容,包括其它排他锁。排它锁用于数据修改,当资源上加了排他锁时,其余请求读取或修改这个资源的事务都会被阻塞,知道排他锁被释放为止。
共享锁:
T1: select * from A;(加共享锁A)
T2: select * from A;(加共享锁B)
* 什么是乐观锁?
乐观锁机制采起了更加宽松的加锁机制。乐观锁是相对悲观锁而言,也是为了不数据库幻读、业务处理时间过长等缘由引发数据处理错误的一种机制,但乐观锁不会刻意使用数据库自己的锁机制,而是依据数据自己来保证数据的正确性。
实现乐观锁通常来讲有如下2种方式:
a. 使用版本号
使用数据版本(Version)记录机制实现,这是乐观锁最经常使用的一种实现方式。通常是在数据表中加上一个数据版本号version字段,表示数据被修改的次数,当数据被修改时,version值会加一。当咱们提交更新的时候,判断数据库表对应记录的当前版本信息与第一次取出来的version值进行比对,若是数据库表当前版本号与第一次取出来的version值相等,则予以更新,不然认为是过时数据。
b. 使用时间戳
乐观锁定的第二种实现方式和第一种差很少,一样是在须要乐观锁控制的table中增长一个字段,字段类型使用时间戳(timestamp), 和上面的version相似,也是在更新提交的时候检查当前数据库中数据的时间戳和本身更新前取到的时间戳进行对比,若是一致则OK,不然就是版本冲突。
* 悲观锁与乐观锁区别与联系?
* 悲观锁与乐观锁的使用场景?
悲观锁:比较适合写入操做比较频繁的场景,若是出现大量的读取操做,每次读取的时候都会进行加锁,这样会增长大量的锁的开销,下降了系统的吞吐量。
乐观锁:比较适合读取操做比较频繁的场景,若是出现大量的写入操做,数据发生冲突的可能性就会增大,为了保证数据的一致性,应用层须要不断的从新获取数据,这样会增长大量的查询操做,下降了系统的吞吐量。
实际生产环境里边,若是并发量不大且不容许脏读,彻底可使用悲观锁定的方法,这种方法使用起来很是方便和简单。可是若是系统的并发很是大的话,悲观锁定会带来很是大的性能问题,因此就要选择乐观锁定的方法。
悲观锁假定其余用户企图访问或者改变你正在访问、更改的对象的几率是很高的,所以在悲观锁的环境中,在你开始改变此对象以前就将该对象锁住,而且直到你提交了所做的更改以后才释放锁。悲观的缺陷是不管是页锁仍是行锁,加锁的时间可能会很长,这样可能会长时间的限制其余用户的访问,也就是说悲观锁的并发访问性很差。
乐观锁则认为其余用户企图改变你正在更改的对象的几率是很小的,所以乐观锁直到你准备提交所做的更改时才将对象锁住,当你读取以及改变该对象时并不加锁。可见乐观锁加锁的时间要比悲观锁短,乐观锁能够用较大的锁粒度得到较好的并发访问性能。可是若是第二个用户刚好在第一个用户提交更改以前读取了该对象,那么当他完成了本身的更改进行提交时,数据库就会发现该对象已经变化了,这样,第二个用户不得不从新读取该对象并做出更改。这说明在乐观锁环境中,会增长并发用户读取对象的次数。
乐观锁例子:
T1:(用户A) begin tran1
T1:(用户A) select * from user where id = 3; (version=1)
T2:(用户B) begin tran2
T2:(用户B) select * from user where id = 3; (version=1)
T1:(用户A) update user set name='amy', version=version+1 where id = 3 and version = 1;(受影响的行: 1)
T2:(用户B) update user set name='tom', version=version+1 where id = 3 and version = 1;(受影响的行: 0)
T1:(用户A) end tran1
T2:(用户B) end tran2
如何查看锁
一、使用sys.dm_tran_locks这个DMV
SELECT l.request_session_id,
2 DB_NAME(l.resource_database_id),OBJECT_NAME(p.object_id),
3 l.resource_description,l.request_type,
4 l.request_status,request_mode
5 FROM sys.dm_tran_locks AS l
6 LEFT JOIN sys.partitions AS p
7 ON l.resource_associated_entity_id=p.hobt_id
二、使用Profiler来捕捉锁信息
如何查看死锁
问题再现:使用SQL Server2008数据库,右键点击tempdb数据库,查看属性。
有时会弹出错误提示框:已超过了锁请求超时时段。 (Microsoft SQL Server,错误: 1222)
经过SQL命令行,查看是否有死锁进程,具体命令如图所示,其中【tempdb】是要访问的数据库名。通过查询,得知存在一个死锁进程【2973】,占用了资源,使正常的请求没法获得及时响应。
执行Kill进程命令,解锁进程,释放资源,具体代码如图所示。
执行完kill进程命令后,再查询一次进程,发现无死锁进程。数据库访问恢复正常。
3、死锁的预防与优化
预防死锁
预防死锁就是破坏四个必要条件中的某一个和几个,使其不能造成死锁。有以下几种办法:
1)破坏互斥条件
破坏互斥条件有比较严格的限制,在SQL Server中,若是业务逻辑上容许脏读,则能够经过将隔离等级改成未提交读或使用索引提示。这样使得读取不用加S锁,从而避免了和其它查询所加的与S锁不兼容的锁互斥,进而减小了死锁出现的几率。
2)破坏请求和等待条件
这点因为事务存在原子性,是不可破坏的,由于解决办法是尽可能的减小事务的长度,事务内执行的越快越好。这也能够减小死锁出现的几率。
3)破坏不剥夺条件
因为事务的原子性和一致性,不剥夺条件一样不可破坏。但咱们能够经过增长资源和减小资源占用两个角度来考虑。
增长资源:好比说经过创建非汇集索引,使得有了额外的资源,查询不少时候就再也不索要锁基本表,转而锁非汇集索引,若是索引可以"覆盖(Cover)"查询,那更好不过。所以索引Include列不只仅减小书签查找来提升性能,还能减小死锁。
减小资源占用:好比说查询时,能用select col1,col2这种方式,就不要用select * .这有可能带来没必要要的书签查找
最大限度减小死锁的方法
按照同一顺序访问数据库资源,上述例子就不会发生死锁啦
优化死锁的一些建议
(1)对于查询频繁的表尽可能使用汇集索引;
(2)设法避免一次性影响大量记录的SQL语句,特别是INSERT和UPDATE语句;
(3)设法让UPDATE和DELETE语句使用合适的索引;
(4)使用嵌套事务时,避免提交和回退冲突;
(5)对数据一致性要求不高的查询使用 WITH(NOLOCK)
(6)减少事务的体积,事务应最晚开启,最先关闭,全部不是必须使用事务的操做必须放在事务外。
(7)查询只返回你须要的列,不建议使用 SELECT * FROM 这种写法。
参考文章: http://www.matools.com/blog/190438028
---------------------
做者:想做会飞的鱼
来源:CSDN
原文:https://blog.csdn.net/xiaokang123456kao/article/details/75268240