一、Transaction(事务)是什么:sql
事务是做为单一工做单元而执行的一系列操做。包括增删查改。数据库
二、事务的种类:架构
事务分为显示事务和隐式事务:并发
隐式事务:就是日常咱们使用每一条sql 语句就是一个事务,只不过他们执行完成以后事务就跟着结束了。性能
显示事务:就是须要咱们来手写了,这个时候就能够进行控制事务的开始和结束了。spa
1 --显式事务(对事物能够进行控制) 2 3 --开始事务 4 begin transaction; 5 update [Sales.Shippers] 6 set companyname='顺丰' where shipperid=5; 7 8 select * from [Sales.Shippers]; 9 10 --结束事务: 11 --第一种:事务的回滚 12 rollback; 13 14 --第二种:事务的提交 15 commit;
三、事务很重要的四个属性: 线程
一、原子性:事务必须是原子工做单位。——在事务中修改数据,要么全都执行,要么全都不执行。在事务执行完成以前(调提交指令写入到sql的事务日志以前),出现问题或重启,sql server 会回滚全部的修改事务。 可是也有例外的错误不会回滚事务————例如:主键冲突和锁超时等。 错误日志会 捕获这些错误的指令,并记录日志里面,而后执行一些操做(例如:回滚事务)3d
二、一致性:发生在同一进程的事物里面的 修改和 查询是不会产生冲突的。保持访问的数据的一致性。版本控制
三、隔离性:控制数据访问的机制; 说明: 一个事务正在对一个表的数据正在修改, 尚未执行完成;;这时另外一个事务,想要查询里面的数据,是不能查到的,必须等到 修改的事务执行完成。:sql server 采用的 “锁”的机制,将正在修改的事务 处理的表的数据 锁定。这样是为了保证数据同步,数据的一致性。日志
四、持久性: 当一个事务的指令 已经提交到 事务日志里面,即便磁盘上的数据尚未修改,这个时候数据库的服务中止,在服务重启的时候还会将事务日志里的指令执行(进行回复处理)。保证数据的持久性。
上面将基本的事务介绍了一下,下面开始介绍并发。因此必需要介绍就是事务的“锁”。
四、事务中的锁
事务中都含有什么锁呢?
最经常使用的锁:排它锁(独占锁)和共享锁,还有其余的锁,这里就不作介绍了,好比:更新锁、架构锁、意向锁等。
五、排它锁和共享锁
排它锁:
当一个事务执行更新修改操做的时候会申请排它锁,主要是在写操做里面使用。须要注意的两点:一、一个事务含有排它锁,就不能含有其余任何锁。二、一条数据只能被一个排它锁锁住,就不能再被其余排他锁锁定。
共享锁:
主要是在读操做中使用,而且多个事务能够同时对一条数据使用共享锁。
排它锁和共享锁最重要的区别:排它锁是不能被控制他的处理方式和时间,可是共享锁是能够控制其隔离级别来控制其处理的时间。
1 begin transaction; 2 update [Sales.Shippers] set companyname='顺丰' where shipperid=5; 3 --事务尚未查询完成,为这条数据 加上一个 排它锁。这时这条数据就不能被其余进程 访问到
事务尚未执行完成,再开一个线程,执行查询操做
1 select * from [Sales.Shippers] where shipperid=5
由于读操做默认使用的共享锁,可是这个时候这条数据已经被其余线程的排它锁锁住,因此会形成阻塞,直到排它锁释放。
六、隔离级别
首先要先明白三点:
一、用于控制并发用户如何读写数据的操作。
二、读操做默认使用共享锁;写操做须要使用排它锁。
三、读操做可以控制他的处理的方式,写操做不能控制它的处理方式
隔离级别分为六种:
read uncommited(读取未提交数据),read commited(读取已提交数据)读取的默认方式,repeatable read(可重复读),serializable(可序列化),snapshot(快照),read commited snapshot(已经提交读隔离)(后两个是sql server 2005 里面 引入的)。隔离的强度依次递增。
一、read uncommitted:
1 select * from [Sales.Shippers] where shipperid=3;
查询结果:
在本线程内执行:
1 begin transaction; 2 update [Sales.Shippers] set companyname='圆通' where shipperid=3;
在另一个线程内 使用 read uncommitted 隔离级别 查询数据:
1 --设置读操做的隔离级别 2 set transaction isolation level read uncommitted; 3 select * from [Sales.Shippers] where shipperid=3;
查询结果:
若是这个时候将那个事务回滚,那么这个时候 查询到的数据就是“脏数据”。
总结:
read uncommitted:最低的隔离级别:查询的时候不会请求共享锁,因此不会和排它锁产生冲突(不会等待排它锁执行完),查询效率很是高,速度飞快。可是缺点:会查到“脏数据”(排它锁的事务已经将数据修改,还没提交,这个时候查询到的数据 是已经更改过的。若是事务回滚,就是“脏数据”)
优势:查询效率很是高,速度很是快。
缺点:会产生“脏数据”
适用性:
适用于 像聊天软件的 聊天记录,会是软件的运行速度很是快。 可是不适用于 商务软件。尤为是银行
二、read committed
读取的默认隔离级别就是read committed 和上面正好相反。若是上面状况,采用read committed 隔离级别查询的话查到的就是尚未更改以前的数据。
因此在这里就再也不演示。
三、repeatable read:
查询的时候会加上共享锁,可是查询完成以后,共享锁就会被撤销。好比一些购票系统,若是查到票了,当买的时候就没有,这是不行的。因此要在查询到数据以后作一些延迟共享锁,进而阻塞排它锁来修改。(若是查询的事务没有提交,不会释放共享锁,这个时候独占锁就不能访问这条数据)
注意:一、repeatable 只会锁定查询的数据 ,而 其余行数据还能够进行 修改(更新、删除)(下面那条语句共享锁只会锁定 shipperid为4 的行)
二、其余进行插入数据,而且插入的数据知足第一次开始事务时的 查询的筛选条件的时候;第二次查询的时候就会将新插入的数据 查询出来。这就叫作“幻读”(解决幻读,须要更高级别的隔离,就是下面的serializable)
在查询线程里面执行sql语句:
1 set transaction isolation level repeatable read; 2 begin transaction; 3 select * from [Sales.Shippers] where shipperid=4;
而后在 另一个线程内执行修改语句:
update [Sales.Shippers] set companyname='shit' where shipperid=4;
这个时候会将更改的线程阻塞掉:
幻读演示:
在线程1 内 输入:
1 set transaction isolation level serializable; 2 begin transaction; 3 select * from [Sales.Shippers] where companyname='顺丰'
结果:
在线程2 内 输入 插入语句:
1 insert into [Sales.Shippers] (companyname,phone) values('顺丰',112321211)
结果:
在 用 共享锁 执行查询语句:
1 select * from [Sales.Shippers] where companyname='顺丰'
结果:
四、serializable(可序列化)
更高级的 隔离。用户解决“幻读”(上面提到的)。就是使用上面的(repeatable read) 加上共享锁 并不撤销,若是锁定的 一行数据,那么 其余的进程 还能够对 其余的数据进行操做,也能够 进行新增和删除的操做。 因此若是想要在查询的时候,不能对整张表进行任何操做,那么就要 将表的结构也 锁定 (就须要使用 更强的 锁定)
在查询线程执行sql语句:
1 set transaction isolation level serializable; 2 begin transaction; 3 select * from [Sales.Shippers] where companyname='顺丰'
那么在另一个线程执行下面两个语句,不论那一条语句都会阻塞住:
1 insert into [Sales.Shippers] (companyname,phone) values('顺丰',112321211) --由于插入的数据知足 查询的筛选的 条件条件了
结果:
总结:
可序列话 隔离读操做:用户 解决 幻影数据(将标的数据和表的结构都锁定),是并发下降...隔离级别越高,并发越低,可是效率越低,因此不是要肯定使用 最好不要使用
下面两种隔离级别是在 sql server 2005才出现的,隔离级别更高:
五、snapshot(快照)
为数据产生一个临时数据库,当sql server 数据更新以前将当前数据库复制到 tempdb数据库里面,查询就是从tempdb数据库中查询,可是不能再 使用 snapshot 线程的事务内执行 修改操做,由于不能修改 旧版本数据库(tempdb),会报错。
snapshot隔离级别,读操做 不适用 共享锁,使用的是“行版本控制”,因此读数据的性能效率很高,可是修改操做性能就下降的不少。
由于是将 数据库 中的数据 复制到 tempdb 数据库中,因此不会产生 幻读。
--设置数据库支持快照隔离级别: alter database ssdemo set allow_snapshot_isolation on;--这个时候会产生一个临时数据库(写操做的排它锁锁定的是 现实存在的数据库,,读操做的读取的是 临时数据库)
在一个线程中执行 更新操做,用排它锁锁定当前数据
begin transaction; --使用 排它锁(独占锁)X,锁定 下面的那条数据 update [Sales.Shippers] set companyname='飞凤' where shipperid=3;
这个时候在在另一个线程中查询这条数据(默认的隔离级别),就会将当前线程阻塞。
若是使用 snapshot 隔离级别查询就不会阻塞。
1 set transaction isolation level snapshot; 2 --下面的就能够 从临时数据库中查询到数据 3 begin transaction; 4 --没有使用 共享锁 ,使用的行版本控制,因此无论数据是否加了 独占锁,均可以到 tempdb中 读取数据(若是数据修改了才到tempdb中读取数据) 5 select * from [Sales.Shippers] where shipperid=3;--查询到的 是尚未完成更新以前的数据
可是同时也会带来两个问题:
一、当 另一个事务 已经提交,可是这边的查询到数据仍是没有修改。由于 每次查询到的快照是针对于 本次回话对应的那个 transaction 的,由于在这个事务里面是没有修改的,因此查询到的数据是没有修改的。
二、(更新问题)由于 那边的数据已是 飞凤公司了,可是这里仍是 联邦,因此,在这个事务里面是不能对表进行修改,由于访问的是临时数据库,想要对 数据库修改是不可能的(sql server 就会报错,阻止修改)
针对于上面两个问题,因此下面 更高的隔离级别出现了 read committed snapshot:
六、read committed snapshot
首先开启数据库的 read committed snapshot 隔离级别:
1 --设置 数据库 为 读取已经提交的快照 开启 2 alter database ssdemo set read_committed_snapshot on;
在一个线程中执行:
begin transaction; update [Sales.Shippers] set companyname='联邦' where shipperid=3;
在另一个线程中:
1 --不用显示声明使用 read committed snapshot 隔离级别,由于设置完 read_committed_snapshot 隔离级别启动,默认就是 read commited snapshot 隔离级别 2 begin transaction; 3 select * from [Sales.Shippers] where shipperid=3;--查询到是 已经提交以后的数据 4 5 update [Sales.Shippers] set companyname='xiaoxiao' where shipperid=3;
这个时候查询到的数据是尚未更改以前的,若是将 前面的那个回话提交,那么在查询 查询到的数据是 提交修改以后的数据。因此解决了上面的问题1.
若是在修改的话。也是在第一个 更新线程中的事务更新以后的数据进行执行修改的操做,不会报错。