欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

T-SQL:事务锁下的并发处理(十五)

程序员文章站 2022-10-20 16:51:28
1.事务 在sql servce 中 事务是一个工作单元 可能包含查询和修改数据以及修改数据定义等多个活动 也可以显示或隐式定义事务边界 显示定义事务 BEGIN TRAN 开始 如果要提交事务 使用 COMMIT TRAN 撤销事务(回滚) ROLLBACK TRAN 如下示例 一个简单的显示事务 ......

1.事务

在sql  servce 中 事务是一个工作单元 可能包含查询和修改数据以及修改数据定义等多个活动 也可以显示或隐式定义事务边界

显示定义事务 begin tran 开始  如果要提交事务 使用 commit tran   撤销事务(回滚) rollback tran  如下示例

begin tran;
insert into sales.orders
      (custid, empid, orderdate, requireddate, shippeddate, 
       shipperid, freight, shipname, shipaddress, shipcity,
       shippostalcode, shipcountry)
    values
      (85, 5, '20090212', '20090301', '20090216',
       3, 32.38, n'ship to 85-b', n'6789 rue de l''abbaye', n'reims',
       n'10345', n'france');
commit tran

一个简单的显示事务    也可以是隐式事务

go
 insert into sales.orders
      (custid, empid, orderdate, requireddate, shippeddate, 
       shipperid, freight, shipname, shipaddress, shipcity,
       shippostalcode, shipcountry)
    values
      (85, 5, '20090212', '20090301', '20090216',
       3, 32.38, n'ship to 85-b', n'6789 rue de l''abbaye', n'reims',
       n'10345', n'france');
go

通过go 在当前批执行完成时自动 提交事务   当然默认情况下sql server 将每个单独的语句作为一个事务 每个语句结束后sql server 会自动提交事务 也可通过设置会话来改变默认设置 

set implicit_transactions on

设置为on 后不需要指定begin tran 语句开始事务 但必须以commit tran或 tollback tran 标记结束

事务有4个属性-原子性,一致性,隔离性,持续性 首字母缩写为acid

原子性(atomicity): 事务是一个原子工作单元,事务中的所有修改要么提交,要么撤销。在事务提交指令记录到事务日志之前 如果系统出现了故障,重新启动时,sql server 会撤销所做的修改。  如果事务中出现错误 默认会自动回滚   也可以通过 @@trancount 检测事务事务完成 或者说是 当前环境是否在事务中 如果在就返回 1 没有就是0 

select    @@trancount

一致性(consistency):指数据状态, 在隔离级别中  每个级别都要是 一致性级别 只有事务保持一致性级别才能访问。在约束中 也指 主外键 书屋会转换数据库的一致性状态到另一个一致性状态 保持一致性

隔离性(isolation):隔离是一种控制访问数据的机制,确保事务所范围数据是在其所期望的一致性级别中的数据。在sql server 中支持两种不同的模式来处理隔离:基于锁的传统模式和行版本控制(新模式) 但是默认的是 锁模式  而且当前加锁也是共享锁 ,如果数据状态不一致 读取数据就会被阻止 直到状态一致 。而改成行版本控制模式 读取就不需要等待 也不会加共享锁 在不需要及时显示的数据时 这种模式是提高并发的处理方式。 具体实现要看使用的隔离级别。

持续性(durability):数据修改在写入到数据库磁盘之前,总是先写入数据库的事务日志磁盘。提交后,指令记录在事务日志磁盘上,在尚未修改磁盘数据之前,事务是持续的,回滚也只是删除事务日志的所有修改记录。

下面是一个完整的事务示例:

begin tran;

  declare @neworderid as int;


begin try
  insert into sales.orders
      (custid, empid, orderdate, requireddate, shippeddate, 
       shipperid, freight, shipname, shipaddress, shipcity,
       shippostalcode, shipcountry)
    values
      (85, 5, '20090212', '20090301', '20090216',
       3, 32.38, n'ship to 85-b', n'6789 rue de l''abbaye', n'reims',
       n'10345', n'france');

  set @neworderid = scope_identity();

  select @neworderid as neworderid;

  insert into sales.orderdetails(orderid, productid, unitprice, qty, discount)
    values(@neworderid, 11, 14.00, 12, 0.000),
          (@neworderid, 42, 9.80, 10, 0.000),
          (@neworderid, 72, 34.80, 5, 0.000);

commit tran;
end try
begin catch
rollback tran;
end catch

2.锁

锁是事务保护数据资源而获得的控制资源,防止其他事务的冲突或不兼容访问。

锁主要有两种锁模式 排他锁和共享锁

当你试图修改数据时 事务会请求数据资源的一个排他锁,它会一直到事务结束才会解除 期间任何其他事务请求都会被阻塞。对于单条语句事务 只要这条语句结束锁就会自动解除。对于多条语句事务 就只有当他完成所有语句执行并通过 commit tran 或 rollback tran 命令时事务才会解除锁

排他锁:如果一个事务在修改行,直到事务完成,其他事务都不能修改相同的行。但是能不能读取相同行 取决于它的隔离级别。

共享锁:在读取事务加锁是默认加的是共享锁  sql读取的默认隔离级别 read committed  因为此隔离级别 会让事务请求读取资源时默认加上共享锁  多个事务可以同时拥有相同数据资源共享锁。 此模式下也会因为并发造成幻读 虽然在修改数据时,无法修改锁和持续时间,但可以通过改变隔离级别 在读取数据时控制锁定的处理方式。

其实锁的本质就是数据的隔离级别 在通过控制隔离级别也能达到加锁的效果 ,而且效果更好。

在sql 中默认的隔离级别 read committed snapshot 这种隔离依靠行版本控制,而不是锁,在此模式下 读取者不需要共享锁,因为不需要等待,依赖行版本控制技术提供隔离。   如果一个事务在read committed隔离级别下 修改数据行 直到事务完成,另一个事务都不能读取相同行。这种并发控制称 “悲观式并发”  如果一个事务在read committed snapshot隔离级别下 修改数据行  此时另一个事务读取相同行 会获得最后一次提交的可用状态。这种并发控制称 “乐观式并发” 在乐观并发中可以很好的解决修改和展示并发问题。

  这种事务之间的并发处理已称为 锁兼容性

 

请求锁模式 请求排他锁 请求共享锁
请求排他锁
请求共享锁

交叉的否代表不兼容 请求锁模式会被拒绝 交叉是 表示兼容 请求锁模式会被接收

那么锁都可以锁些什么资源呢?

锁定的资源包括rid ,键,行,页,对象,表,数据库,范围,分配单元,堆,b树。

锁请求流程规则是什么呢?

例如:获取一个行上的排他锁  事务必须首先获取一个行所在页的意向排他锁和一个拥有该页对象的意向排他锁, 同样的 共享锁也是此步骤。

为什么要申请意向锁?

为了在更高级别有效的检测是否有锁不兼容请求,并防止授予这些锁请求,这就是为什么在排他锁请求相同数据行时被阻断的原因。

例如:一个事务持有行上锁,而另一个事务在该行所在的页或者表请求不兼容锁比如排他锁 有第一个事务的行上锁 有一个 表意向锁  这个时候请求就被拒绝了

但意向锁不会拒绝更低级别对象的锁请求  

例如: 一个页上的意向锁不会阻断在该页的其他事务的排他锁  我们可以通过一个表来细致的了解这些锁的兼容性请求。

请求锁模式 请求排他锁 请求共享锁 请求意向排他锁 请求意向共享锁
请求排它锁
请求共享锁
请求意向排他锁
请求意向共享锁

 

 

通过这些锁得到我们最理想的并发处理 锁定所需要的内容,即受影响行数 。锁是需要内存资源和内部管理开销, 当需要锁时要考虑当前系统资源情况。

有一个情况不得不说  在行锁中超过5000个时 会自动升级锁 到表锁 然后每加1250个锁都会触发默认的锁升级

可以通过alter table语句设置lock_escalation 的表选项控制锁升级。 也可以禁用。 也可以更改升级方式 比如 分区级别( 把一个表物理的组织成小单元c成为分区)   锁到此语句结束了下次讲  如何优雅的排除并发导致的阻塞数据处理。

 

上一篇: 简短故事

下一篇: 大学生的集训