以“转账”为例,实际上是两条UPDATE语句:
UPDATE User SET Balance = Balance + 150 WHERE Id = 1 UPDATE User SET Balance = Balance - 150 WHERE Id = 6一旦中间出现故障(比如网络故障/系统断电……),就会造成 Id=1 的用户余额增加而 Id=3 的用户没有减少的情况。
解决的办法就是将上述两条语句放到一个事务中。
BEGIN TRANSACTION -- 开始事务 UPDATE User SET Balance = Balance + 199 WHERE Id = 1 UPDATE User SET Balance = Balance - 199 WHERE Id = 6 -- 因违法CHECK(Balance>0)约束而报错 COMMIT TRANSACTION -- 提交事务
说明事务的:(记忆小窍门:ACID)
实际上,各种关系数据库对上述1、2、4条要求都执行得比较严格,但对于3隔离性因为并发(复习)的原因,不得不做出妥协。
为了提高数据库性能,就要允许数据库被并发的访问,就不能严格遵守事务隔离性要求,所以数据库是允许在一个事务的进行过程中,有另一个事务同时进行的。
数据库一般也允许用户自行设置数据库事务的
#冲击12K,^_^
如下表所示:
隔离级别 |
解决的问题 |
读未提交 |
丢失的更新 |
读已提交 |
脏读 |
可重复读 |
不可重复读 |
序列化/串行化 |
幻影读 |
从上到下:
两个连接窗口,每个窗口都可以启动事务。
说明:一个窗口(session/query)中的多次操作不会形成并发。
在当前连接中:
SET TRANSACTION ISOLATION LEVEL isolation_level
T-SQL | mysql |
DBCC USEROPTIONS |
SELECT @@transaction_isolation; |
另外,任何单条SQL语句,都自动被包裹在事务中执行:
UPDATE Student SET Age = 66 WHERE Id = 1; -- 等于 BEGIN TRAN UPDATE Student SET Age = 66 WHERE Id = 1; COMMIT
先设置事务隔离级别为:
保证不会出现:丢失的更新(后更新会覆盖之前的更新)
但是,会出现:脏读(Dirty Read)
将事务2的隔离级别设置成:
就可以保证不会出现:脏读。事务2需等待事务1完成之后才能获得查询结果。
但是会出现:不可重复读 (Unrepeatable Read)
#理解#:在同一次事务中,针对同一(行)数据是查询,(按照事务隔离性的要求)应该得到相同的结果。
将事务1的的隔离级别设置为:
就可以保证不会出现:不可重复读。上述第2步操作无法完成,需要等待事务1完成才能继续进行。
但是会出现:幻影读(Ghost Read)
将事务1的隔离级别给成:
就可以保证不会出现幻影读。事务2要一直等待事务1完成之后才能执行。
#冲击18K,^_^
锁,可以视为对数据的一种标记。大致来说,
这样就能实现事务隔离。
上述事务隔离级别可以通过对数据:
来实现。
强烈建议:在学习之前尽可能的熟练事务隔离级别。
共享(Shared)锁:可用于只读的SELECT语句,所以又称之“读锁”。被加共享锁的数据,不影响其他事务读取。
排他(Exclusive)锁:用于INSERT/UPDATE/DELET语句(以下简称“写数据”),所以又称之“写锁”。
我们可以把锁加在行上,也可以加在表上,一个锁能“控制”的数据单位,就被称之锁的“粒度”。
一次事务要锁多少数据,3行5行,1张表3张表,这是锁的“范围”。
粒度是数据库锁机制可以决定的,范围是SQL语句(比如WHERE条件)决定的。
@想一想@:粒度是大好,还是小好?
粒度小:并发性更好。比如“锁3行”,肯定比“锁3张表”影响的数据更少,其他事务可以操作的数据更多。
但如果要锁的粒度太小,同样范围,用表锁只需要3个锁,用行锁可能就要几千几万个,加锁解锁性能消耗更多。
#体会#:没有银弹。凡是选择,必有代价……
把加锁的范围扩大到所有相关表,就可以消除幻影读。
带WHERE子句的UPDATE语句,也会涉及到查询。
@想一想@:这里的查询,加什么锁?
如果使用S锁,就很容易形成死锁:
当然加X锁可以避免死锁,但性能不高呀!
所以需要引入更新(Update)锁:
在查找过程中使用的就是U锁;一旦查找到需要的数据,U锁就转变成X锁。
死锁出现条件:
死锁不是等待。而是彻底的锁死在那里了!是无论怎么等待,都没办法活过来的那种……^_^
示范:一个最简单的死锁(注意WHERE条件要使用主键,@想一想@:为什么?)
BEGIN TRAN UPDATE Student SET Score = Score + 10 WHERE Id = 1;
BEGIN TRAN UPDATE BangMoney SET Balance -= 10 WHERE [Name] = N'陈元';
UPDATE BangMoney SET Balance -= 10 WHERE [Name] = N'陈元';提示:执行中……,需要等着了
UPDATE BangMoney SET Balance += 10 WHERE [Name] = N'幸龙泰';
只能依靠SQL SERVER来自行裁判其中一个事务作为victim,进行回滚。
多快好省!前端后端,线上线下,名师精讲
更多了解 加: