种隔离级别,本身没有优劣之分,完全取决于应用的场景。
本质上,他们是在
隔离性(紊乱程度)
和
灵活性(并发性)
之间博弈。简单的说,灵活性越高,隔离性越差。反之亦然。
我的理解是:如果不太计较数据出现的少许紊乱,可以使用用最低级别的隔离性(保持高并发性)
,比如
刷微信
这类事情,你不能让用户人等着
朋友圈数据同步完了再出来结果。
同样的,发奖金这样的事情是极其敏感的。你不能因为来了几个新人,就要求把老员工的奖金拿去平均了(总奖金/员工数量=单人奖金 ,计算奖金的时候应该隔离新员工的INSERT,请刚入职的人不要恨我),这时候要强调隔离性(绝不能乱!),并发慢一点我们无所谓(反正有时间等,大家也理解,当然也不能太慢了)。
世界就是如此,不存在又灵活又能保持一致的数据库解决方案
(可能我眼界窄,也许已经有了),即不存在:
超人+蝙蝠侠
这样的完美个体存在。于是,我们需要在超人(灵活性)和蝙蝠侠(隔离性)之间做出选择。
我按照自己的理解来说明,这些隔离级别存在的问题。
先建立原料,创建一个Testtb
CREATE
TABLE
TESTTB
(
ID
INT,
NAME
VARCHAR(32)
)
|
未提交读的问题
所谓未提交读,可以这么简单理解:查询语句不发出锁。这是个与世无争的存在,速度也是最快的。
BEGIN
TRAN
SELECT
*
FROM
TESTTB
WITH(NOLOCK)
COMMIT
|
但是,他存在一个问题(快是有代价的),我们用2个并发查询(SQL SERVER 新建2个查询,事务1先执行)
--脏读事务1
BEGIN
TRAN
INSERT
INTO
TESTTB
VALUES(1,'EFG')
--这个时候运行事务2
WAITFOR
DELAY
'00:00:10'
INSERT
INTO
TESTTB
VALUES(1,'EFG')
ROLLBACK
|
--事务2
BEGIN
TRAN
SELECT
*
FROM
TESTTB
WITH(NOLOCK)
COMMIT
|
请注意这个ROLLBACK ,事务1 是回滚的。你猜事务2读到了什么,事务最后回滚的,他居然读出了一个数据,这种情况就叫脏读
(可以这么理解,你想在微信上吐槽,已经点了发送,但突然想到可能得罪某人,连忙点了取消;刚好此时某个家伙刷一下微信,居然读出那条吐槽。就这么个感觉)
前面说了,对于微信这样的要求高并发的系统,未提交读是无所谓的。但是作为企业应用你可能想解决这个问题。
我们做下面这个测试
--事务1
BEGIN
TRAN
INSERT
INTO
TESTTB
VALUES(1,'EFG')
--这个时候运行事务2
WAITFOR
DELAY
'00:00:10'
INSERT
INTO
TESTTB
VALUES(1,'EFG')
ROLLBACK
|
--事务2
BEGIN
TRAN
SELECT
*
FROM
TESTTB
COMMIT
|
我们的事务2的结果是怎么样的呢?
我们发现有趣的事情
- 事务2被阻塞了
结果是什么也没有
问题的关键就在这个阻塞上面(没错,如果你工作中碰到用户说现场阻塞了,就是这么回事)。SQL SERVER数据库使用了 已提交读 作为隔离级别的时候。会发出一个共享锁
(S锁,oracle ,mysql,sybase 我不知道,但估计是一个意思:一份文件正在被写的时候,其他人不能读。)
也就是说,这个SELECT 正在等待 INSERT 事务提交。 INSERT 完了,它才执行
(如果你有 windows 核心编程背景,就理解为INSERT 产生了一个信号量,SELECT 之前 要用WaitForSIngleObject 判断有无信号)
使用已提交读,就解决了这个脏读的问题。SQL SERVER 默认用这个隔离级别。所以,你没有看到WITH (xxx) 这样的锁\隔离级别声明。
已提交读的问题
已提交读能在大多数情况下工作良好,但是,他也有郁闷的缺陷。
我们先往Testtb中插入一条数据
INSERT
INTO
TESTTB
VALUES(1,'EFG')
|
让我们来看下面的并发事务
--事务1
BEGIN
TRAN
SELECT
NAME
FROM
TESTTB
WHERE
ID
= 1
WAITFOR
DELAY
'00:00:10'
--阻塞之后执行事务2
SELECT
NAME
FROM
TESTTB
WHERE
ID
= 1
COMMIT
|
--事务2
BEGIN
TRAN
UPDATE
TESTTB
SET
NAME
=
'变!'
WHERE
ID
= 1
COMMIT
|
事务1执行结果是怎样的?
什么情况?事务1,读到2次不同的值。这就出现了前后不一致的情况。这就是不可重复读。
这种感觉就是:你通过淘宝买了iphone6 ,商家给你发了个iphone6 (事务开始了),中间某个环节出了差错。你收到货了(事务结束),打开一看,是iphon4。就这种感觉
在企业应用中,前后状态不一致,可能是不允许的。
那么我们看看如何解决
--事务1
BEGIN
TRAN
SELECT
NAME
FROM
TESTTB
WITH(REPEATABLEREAD) WHERE
ID =1
--阻塞之后执行事务2
WAITFOR
DELAY
'00:00:10'
SELECT
NAME
FROM
TESTTB
WITH(REPEATABLEREAD) WHERE
ID =1
COMMIT
|
--事务2
BEGIN
TRAN
UPDATE
TESTTB
SET
NAME
=
'给老子变!'
WHERE
ID
= 1
COMMIT
|
看看事务1的输出结果
而且执行事务2的时候,他一直在阻塞,直到事务1提交,才运行。
这个阻塞,一样的,就好读比一个文档,我加了一条规矩(隔离级别):哥们在读的时候,旁的人都给我等着!于是事务2就老老实实的等着。
对于前面 已提交读就没这个功能,他的规矩是:别人在写的时候,我等着。
而未提交读的规矩是啥呢?: 写啥呢?我瞅瞅! 没写完不要紧的,我就拍个照,绝不浪费您的时间!
就这样,通过WITH(REPEATABLEREAD) 隔离级别提示,我们解决了不可重复读的问题。
可重复读的问题
很完美了 对吗? 有了可重复读 隔离级别,我们能保持前后数据一致。
现在我们来修改需求,我们要求计算表中记录数量。我们看下面的代码
--事务1
BEGIN
TRAN
SELECT
COUNT(1)
FROM
TESTTB
WITH(REPEATABLEREAD)
WAITFOR
DELAY
'00:00:10'
--阻塞之后执行事务
SELECT
COUNT(1)
FROM
TESTTB
WITH(REPEATABLEREAD)
COMMIT
|
--事务2
BEGIN
TRAN
INSERT
INTO
TESTTB
VALUES (2,'A')
INSERT
INTO
TESTTB
VALUES (3,'B')
INSERT
INTO
TESTTB
VALUES (4,'C')
COMMIT
|
至今为止,我们数据库里面只有一条记录对吗?
我们看看事务1输出结果
即使我们增加了REPEATABLEREAD 关键字,但是得到结果前后数量不一致。这就是幻读
运用你的直觉(我认为这是一种天赋)!你感觉到了 REPEATABLEREAD
好像没有阻塞INSERT ,但在上一个例子中,他成功的阻塞了UPDATE.
why?
我是这么理解的,UPDATE 和 DELETE 有一个共同点是,对一个已经存在的记录进行操作。也就是说能被SELECT 到。因此可以加锁。但是INSERT在执行的时候数据是不存在的。你无法给记录加锁!所以,你无法阻塞任何INSERT语句。虽然INSERT 语句也发出一个排它锁,但是数据产生之前,你只能尝试阻塞这空气一般的存在。因为,你不知道它在哪儿。
解决幻读
SQL 在4个隔离级别中,最高级隔离别就是Serializable (可序列化),老实说我真不理解这个名字。他和JAVA /C# 中的
序列化好像完全没关系。根本就无法通过名字理解这个隔离级别的意思。因此,我私下给他重新取了个名"不可新增读"。
顾名思义:在SELECT执行的时候,不可INSERT.
来,我们修改我们的隔离级别。
--事务1
BEGIN
TRAN
SELECT
COUNT(1)
FROM
TESTTB
WITH(SERIALIZABLE)
WAITFOR
DELAY
'00:00:10'
--阻塞之后执行事务
SELECT
COUNT(1)
FROM
TESTTB
WITH(SERIALIZABLE)
COMMIT
|
--事务2
BEGIN
TRAN
INSERT
INTO
TESTTB
VALUES (5,'AA')
INSERT
INTO
TESTTB
VALUES (6,'BB')
INSERT
INTO
TESTTB
VALUES (7,'CC')
COMMIT
|
再次运行你的直觉! 上一次INSERT 之后,我们有4条数据了! 这次依次执行执行事务1,2之后,我们得到结果是多少呢?
我认为是2条都是4.
果然,WITH(SERIALIZABLE) 隔离级别解决了幻读的问题。保证了前后读取一致。这个隔离级别是最严格的。他连INSERT 都给阻塞了(它是怎么做到的,原理是什么?参考
《SQL SERVER 2008 查询性能优化》P340 页
老实说它也解释的不够详细,(没有证实,纯属猜测)我是这么认为的, SQL SERVER 不是有一个自动增长设置吗?
像不像C++中的vector 的 capacity 属性?
预分配一些空间,这样不用等到insert 的时候才去分配空间,那么我分配给这个表的预留空间
是不是也有ID? 即有许多没有用的空间的集合?
当我指定了SERIALIZABLE 隔离级别时,我用一个
信号量把
这块集合个阻塞起来!)
总结
上面的例子分析了 脏读,不可重复度,幻读 。
分别解释了 他们是什么,以及简单的原因。并用代码做出了演示。
事实上,这些隔离级别是层层递进的。下面这幅图来源于《企业应用架构模式》P 52
- SQL Server 2008 数据库镜像部署实例之三 配置见证服务器
SQL Server 2008 数据库镜像部署实例之三 配置见证服务器 前面已经完成了镜像数据库的配置,并进行那个了故障转移测试.接下来将部署见证服务器,实现自动故障转移. 一.关于见证服务器 1.若 ...
- SQL Server事务的隔离级别
SQL Server事务的隔离级别 ########## 数据库中数据的一致性 ########## 针对并发事务出现的数据不一致性,提出了4个级别的解决方法: 隔离级别 第一类丢失更新 脏读 ...
- php连接sql server 2008数据库
原文:php连接sql server 2008数据库 关于php连接sql server 2008的问题,2000的版本可以直接通过php中的配置文件修改,2005以上的版本就不行了,需要使用微软公司 ...
- phpstudy连接SQL Server 2008数据库 以及 php使用sql server出现乱码解决方式
开始也尝试自己配置php安装环境,找到一个详细的百度经验http://jingyan.baidu.com/article/154b46315242b328ca8f4101.html,前面有问题也一一去 ...
- Eclipse连接SQL Server 2008数据库 以及问题总结
Eclipse中使用SQL server 2008数据库 一.准备材料 要能够使用数据库就要有相应的JDBC,所以我们要去Microsoft官网下载 https://www.microsoft.com ...
- C# VS2010结合SQL Server 2008数据库编程实现方法
SQL Server 数据库在C#编程中经常用到,如何实现在具体项目中数据库和具体应用的结合是我们经常遇到的问题,我们这次主要针对如何使用SQL Server 数据库展开,下面是具体的操作以及简单的代 ...
- sql server 2008 数据库管理系统使用SQL语句创建登录用户步骤详解
介绍了sql server 2008 数据库管理系统使用SQL语句创建登录用户步骤详解 --服务器角色: --固定服务器角色具有一组固定的权限,并且适用于整个服务器范围. 它们专门用于管理 SQL S ...
- Eclipse连接SQL Server 2008数据库
一.准备材料 要能够使用数据库就要有相应的JDBC,所以我们要去Microsoft官网下载 https://www.microsoft.com/zh-cn/download/details.aspx? ...
- 如何转换SQL Server 2008数据库到SQL Server 2005
背景介绍: 公司一套系统使用的是SQL SERVER 2008数据库,突然一天收到邮件,需要将这套系统部署到各个不同地方(海外)的工厂,需要在各个工厂部署该数据库,等我将准备工作做好,整理文档 ...
随机推荐
- 【VS开发】【C++开发】正确使用auto_ptr智能指针
1, auto_ptr类 auto_ptr是一个模板类,定义如下: template <typename Type>class auto_ptr {...}: 它存储的是一个指向Type的 ...
- GB和GIB的区别
天啦撸,这么多年才知道这个东西! Gibibyte(giga binary byte)是信息或计算机硬盘存储的一个单位,简称GiB.由来“GiB”,“KiB”,“MiB”等是于1999年由国际电工协会 ...
- liunx -bash:ls:command not found,执行命令总是报找不到
解决办法: 使用绝对命令vi打开profile /bin/vi /etc/profile 添加: export PATH=/usr/local/sbin:/usr/local/bin:/sbin:/ ...
- Java面试 - 重载(Overload)和重写(Override)的区别?
1.重载是在同一个类中,可声明多个同名方法,但参数列表不同(参数顺序,个数,类型).而重写是在子类中,对从父类中继承的方法进行重新编写,但方法名,参数列表(参数顺序,个数,类型),返回值类型必须保持一 ...
- activate-power-mode安装与设置
Window-->activate-power-mode-->去掉combo/shake,其他三个全勾上,现在用起来就很爽了,赶紧体验吧.
- scrapy 爬取纵横网实战
前言 闲来无事就要练练代码,不知道最近爬取什么网站好,就拿纵横网爬取我最喜欢的雪中悍刀行练手吧 准备 python3 scrapy 项目创建: cmd命令行切换到工作目录创建scrapy项目 两条命 ...
- Hadoop的理解笔记
1.2Hadoop与云计算的关系1.什么是云计算:一种基于互联网的计算,在其中共享的资源.软件和信息以一种按需的方式提供给计算机和设备 , 就如同日常生活中的电网一样. 什么是Hadoop:Hadoo ...
- go if 判断 完成随机分数的评级
1 go中 所有的大括号要跟在 当前语句的后面不能换行 例如: if a>0 { func getUser(){ for { 2关于随机分数的生成 种子的设置放到循环中会是重复的数字,这是可以 ...
- Nginx学习笔记(三):Nginx 请求处理
Request Nginx 中的 ngx_http_request_t 是对一个 http 请求的封装: 一个 http 请求包含:请求行.请求头.请求体,响应行.响应头.响应体 Nginx 处理请求 ...
- SQL语句中加中括号[ ]的作用
有些可能是SQL里面的保留字,但是你又用了它做字段名 比如Action,用[Action] 就可以避免这个问题,如果直接Action SQL就要报错了. 解决较长的中文名表名可能会被不识别的问题.
|