文章目录
- 1.引入事务
- 事务的版本支持
- 事务提交方式
- 实验结论
- 用户问题
- 2.隔离性
- 如何理解隔离性
- 隔离级别
- 查看与设置隔离性
- 4.四种隔离级别的场景
- 读未提交
- 读已提交
- 可重复读
- 串行化
1.引入事务
当客户端A检查还有一张票时,将票卖掉,还没有执行更新数据库的时候,客户端B检查了票数,发现大于0,于是又买了一次票。然后客户端A将票数更新回数据库。于是就出现了同一张票被卖了两次的情况。
所以数据库的 CURD 应该满足什么属性能解决上面的问题?
买票的过程得是原子的吧
买票互相应该不能影响吧
买完票应该要永久有效吧
买前,和买后都要是确定的状态吧
什么是事务?
事务就是一组DML(Data Manipulation Language)语句组成,这些语句在逻辑上存在相关性,这一组DML语句要么全部成功,要么全部失败,是一个整体。MySQL提供一种机制,保证我们达到这样的效果。事务还规定不同的客户端看到的数据是不相同的。
事务就是要做的或所做的事情,主要用于处理操作量大,复杂度高的数据。假设一种场景:你毕业了,学校的教务系统后台MySQL 中,不在需要你的数据,要删除你的所有信息(一般不会:) ), 那么要删除你的基本信息(姓名,电话,籍贯等)的同时,也删除和你有关的其他信息,比如:你的各科成绩,你在校表现,甚至你在论坛发过的文章等。这样,就需要多条MySQL 语句构成,那么所有这些操作合起来,就构成了一个事务。
正如我们上面所说,一个MySQL 数据库,可不止你一个事务在运行,同一时刻,甚至有大量的请求被包装成事务,在向MySQL 服务器发起事务处理请求。而每条事务至少一条SQL ,最多很多SQL ,这样如果大家都访问同样的表数据,在不加保护的情况,就绝对会出现问题。甚至,因为事务由多条SQL 构成,那么,也会存在执行到一半出错或者不想再执行的情况,那么已经执行的怎么办呢?
一个完整的事务,绝对不是简单的sql 集合,还需要满足如下四个属性:
- 原子性:一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。
- 一致性:在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设规则,这包含资料的精确度、串联性以及后续数据库可以自发性地完成预定的工作。
- 隔离性:数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别,包括读未提交(Read uncommitted ) 读提交(read committed)、可重复读(repeatable read)和串行化(Serializable )
- 持久性:事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。
上面四个属性,可以简称为ACID
原子性(Atomicity,或称不可分割性)
一致性(Consistency)
隔离性(Isolation,又称独立性)
持久性(Durability)。
为什么会出现事务
事务被MySQL 编写者设计出来,本质是为了当应用程序访问数据库的时候,事务能够简化我们的编程模型,不需要我们去考虑各种各样的潜在错误和并发问题.
可以想一下当我们使用事务时,要么提交,要么回滚,我们不会去考虑网络异常了,服务器宕机了,同时更改一个数据怎么办。
因此事务本质上是为了应用层服务的.而不是伴随着数据库系统天生就有的.
我们后面把MySQL 中的一行信息,称为一行记录
事务的版本支持
在 MySQL中只有使用了 Innodb 数据库引擎的数据库或表才支持事务,MyISAM 不支持,
查看数据库引擎
mysql> show engines;
mysql> show engines \G
MyISAM 不支持。-- 表格显示-- 行显示
*************************** 1. row ***************************
Engine: InnoDB -- 引擎名称
Support: DEFAULT -- 默认引擎
Comment: Supports transactions, row-level locking, and foreign keys -- 描述
Transactions: YES -- 支持事务
XA: YES
Savepoints: YES -- 支持事务保存点
*************************** 2. row ***************************
Engine: MRG_MYISAM
Support: YES
Comment: Collection of identical MyISAM tables
Transactions: NO
XA: NO
Savepoints: NO
*************************** 3. row ***************************
Engine: MEMORY --内存引擎
Support: YES
Comment: Hash based, stored in memory, useful for temporary tables
Transactions: NO
XA: NO
Savepoints: NO
*************************** 4. row ***************************
Engine: BLACKHOLE
Support: YES
Comment: /dev/null storage engine (anything you write to it disappears)
Transactions: NO
XA: NO
Savepoints: NO
*************************** 5. row ***************************
Engine: MyISAM
Support: YES
Comment: MyISAM storage engine
Transactions: NO -- MyISAM不支持事务
XA: NO
Savepoints: NO
事务提交方式
事务的提交方式常见的有两种:
自动提交
手动提交
查看事务提交方式
mysql> show variables like 'autocommit';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit | ON |
+---------------+-------+
1 row in set (0.41 sec)
用 SET 来改变 MySQL的自动提交模式:
mysql> SET AUTOCOMMIT=0;
Query OK, 0 rows affected (0.00 sec) #SET AUTOCOMMIT=0 禁止自动提交
mysql> show variables like 'autocommit';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit | OFF |
+---------------+-------+
1 row in set (0.00 sec)
实验结论
- 只要输入begin或者start transaction,事务必须要通过commit提交才会持久化,与是否设置set autocommit无关。
- 事务可以手动回滚,同时,当操作异常,MySQL会自动回滚对于InnoDB 每一条SQL 语言都默认封装成事务,自动提交。(select有特殊情况,因为MySQL 有MVCC )
- 如果没有设置保存点,也可以回滚,只能回滚到事务的开始。直接使用 rollback(前提是事务还没有提交)
- 如果一个事务被提交了(commit),则不可以回退(rollback)。
- 可以选择回退到哪个保存点。
- InnoDB 支持事务,MyISAM 不支持事务。开始事务可以用start transaction 或者begin;
- mysql通过原子性,隔离性,持久性来达到一致性。
一个请求的完成通常需要多条有相关/逻辑的sql组成,这些sql集合我们称为事务。程序员/DBA角度看来,事务是一些sql语句,在上层用户/mysql的使用者看来,事务可以完成某种功能。由于服务器同一时间要处理多条事务/请求,且mysqld是多线程处理事务,故会涉及到安全问题,为了使得操作安全,人们设置了ACID四性。
MySQL同时需要处理多个事务,需要管理—先描述再组织—把多条sql打包成事务对象放到事务队列!
事务不是天然存在,而是在使用MySQL一段时间后,出现了各种问题,人们才意识到需要事务!事务出现之前,ACID需要程序员考虑,而事务出现之后,程序员只需要考虑sql的编写,其他由mysql完成!
mysql的服务尽量不要暴露在公网上,暴露了,允许访问,但不一定允许你登录! 为什么在Linux能登录,在windows不行:MySQL有自己的账号管理体系,并不是通过账号密码就能登陆上!还要有允许登录的权限!【MySQL用户登录表配置:用户管理那一篇讲到这个】windows登录:mysql -hIp -uroot -p -P3306
用户问题
多客户端访问:显示多客户端
证明事务的开始与回滚:回滚到指定保存点
回滚到最开始
证明未commit,客户端崩溃,MySQL自动会回滚(隔离级别当前为读未提交)。要么不操作 要么操作完 ctrl+\ 或者直接关闭会话 赵六都会消失【-ctrl+\异常终止MySQL】
证明commit了,客户端崩溃,MySQL数据不会在受影响,已经持久化
begin手动开启必须手动结束,与auto commit是否开启无关
autocommit只在当前客户端有效
现象出结论:autocommit影响的是非事务下的sql!准确的说,上面的begin下,如果没有手动提交(取决于手动commit与auto无关),而客户端崩溃,此时处于事务中,操作会回滚。而没有begin时,单sql也是一个事务,只不过单sql一单执行完毕,就会auto提交(取决于autocommit)。如果auto==off则客户端崩溃且在崩溃之前没有commit操作会回滚(如果手动commit不会,因为已经持久化了)。之前我们讲sql,auto默认是on的所以我们感知不到事务的概念!
2.隔离性
如何理解隔离性
MySQL服务可能会同时被多个客户端进程(线程)访问,访问的方式以事务方式进行。
一个事务可能由多条SQL构成,也就意味着,任何一个事务,都有执行前,执行中,执行后的阶段。而所谓的原子性,其实就是让用户层,要么看到执行前,要么看到执行后。
执行中出现问题,可以随时回滚。所以单个事务,对用户表现出来的特性,就是原子性。
但毕竟所有事务都要有个执行过程,那么在多个事务各自执行多个SQL的时候,就还是有可能会出现互相影响的情况。比如:多个事务同时访问同一张表,甚至同一行数据。
就如同你妈妈给你说:你要么别学,要学就学到最好。至于你怎么学,中间有什么困难,你妈妈不关心。那么你的学习,对你妈妈来讲,就是原子的。那么你学习过程中,很容易受别人干扰,此时,就需要将你的学习隔离开,保证你的学习环境是健康的。
数据库中,为了保证事务执行过程中尽量不受干扰,就有了一个重要特征:隔离性。数据库中,允许事务受不同程度的干扰,就有了一种重要特征:隔离级别。
隔离级别
- 读未提交【Read Uncommitted】: 在该隔离级别,所有的事务都可以看到其他事务没有提交的执行结果。(实际生产中不可能使用这种隔离级别的),但是相当于没有任何隔离性,也会有很多并发问题,如脏读,幻读,不可重复读等,我们上面为了做实验方便,用的就是这个隔离性。
- 读提交【Read Committed】 :该隔离级别是大多数数据库的默认的隔离级别(不是 MySQL 默认的)。它满足了隔离的简单定义:一个事务只能看到其他的已经提交的事务所做的改变。这种隔离级别会引起不可重复读,即一个事务执行时,如果多次 select, 可能得到不同的结果。
- 可重复读【Repeatable Read】: 这是 MySQL 默认的隔离级别,它确保同一个事务,在执行中,多次读取操作数据时,会看到同样的数据行。但是会有幻读问题。
- 串行化【Serializable】: 这是事务的最高隔离级别,它通过强制事务排序,使之不可能相互冲突,从而解决了幻读的问题。它在每个读的数据行上面加上共享锁,。但是可能会导致超时和锁竞争(这种隔离级别太极端,实际生产基本不使用)
隔离级别如何实现
隔离,基本都是通过锁实现的,不同的隔离级别,锁的使用是不同的。常见有,表锁,行锁,读锁,写锁,间隙锁(GAP),Next-Key锁(GAP+行锁)等。不过,我们目前现有这个认识就行,先关注上层使用。
A更新B查询是先更新还是先查询好?
有人说先更新,毕竟查询时都想要查询最新的数据,这种理解并不恰当,应该是:B事务到来时,看到他这个时间点应该看到的数据!隔离性的体现;谁先来谁先执行,要考虑,先执行不一定先结束,等上一个结束我在执行!
两个小孩在浑浊的水里游泳,A浮出水面后,B要想看到对方,自己必须浮出水面!
隔离级别谈论的都是读写并发
读读就不用说了,并发读随便读无安全问题。
写写必须加锁串行化执行。
读写,也是MySQL最常见的并发操作,针对读写设计隔离级别。在保证安全的前提下,尽可能的提高读写并发,这也是mysql的应用场景。
查看与设置隔离性
-- 查看
mysql> SELECT @@global.tx_isolation; --查看全局隔级别
+-----------------------+
| @@global.tx_isolation |
+-----------------------+
| REPEATABLE-READ |
+-----------------------+
1 row in set, 1 warning (0.00 sec)
mysql> SELECT @@session.tx_isolation; --查看会话(当前)全局隔级别
+------------------------+
| @@session.tx_isolation |
+------------------------+
| REPEATABLE-READ |
+------------------------+
1 row in set, 1 warning (0.00 sec)
mysql> SELECT @@tx_isolation; --默认同上
+-----------------+
| @@tx_isolation |
+-----------------+
| REPEATABLE-READ |
+-----------------+
1 row in set, 1 warning (0.00 sec)
-- 设置当前会话 or 全局隔离级别语法
SET [SESSION | GLOBAL] TRANSACTION ISOLATION LEVEL {READ UNCOMMITTED | READ
COMMITTED | REPEATABLE READ | SERIALIZABLE}
--设置当前会话隔离性,另起一个会话,只影响当前会话
mysql> set session transaction isolation level serializable; -- 串行化
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT @@global.tx_isolation;
+-----------------------+
| @@global.tx_isolation |
+-----------------------+
| REPEATABLE-READ |
+-----------------------+
1 row in set, 1 warning (0.00 sec)
mysql> SELECT @@session.tx_isolation;
+------------------------+
| @@session.tx_isolation |
+------------------------+
| SERIALIZABLE |
+------------------------+
1 row in set, 1 warning (0.00 sec)
mysql> SELECT @@tx_isolation;
+----------------+
| @@tx_isolation |
+----------------+
| SERIALIZABLE |
+----------------+
1 row in set, 1 warning (0.00 sec)
--设置全局隔离性,另起一个会话,会被影响
mysql> set global transaction isolation level READ UNCOMMITTED;
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT @@global.tx_isolation;
+-----------------------+
| @@global.tx_isolation |
+-----------------------+
| READ-UNCOMMITTED |
+-----------------------+
1 row in set, 1 warning (0.00 sec)
mysql> SELECT @@session.tx_isolation;
+------------------------+
| @@session.tx_isolation |
+------------------------+
| READ-UNCOMMITTED |
+------------------------+
1 row in set, 1 warning (0.00 sec)
mysql> SELECT @@tx_isolation;
+------------------+
| @@tx_isolation |
+------------------+
| READ-UNCOMMITTED |
+------------------+
1 row in set, 1 warning (0.00 sec)-- 注意,如果没有现象,关闭mysql客户端,重新连接。
中途更改global,不会影响当前session,下次开启新的session会以新global为依据。
4.四种隔离级别的场景
读未提交
–几乎没有加锁,虽然效率高,但是问题太多,严重不建议采用。
–一个事务在执行中,读到另一个执行中事务的更新(或其他操作)但是未commit的数据,这种现象叫做脏读
(dirty read)
读已提交
在A提交前后,终端B在同一个事务内部看到了不同的数据。— 不可重复读
自己测试:只要A不提交,B提交与否都查不到最新信息,只有A提交B也提交才能查到最新信息。
不可重复读的问题
A提交了新数据 理应B在运行期间不应该看到新数据!而应该在之后的新事务应该看到。
可重复读
在终端B中,事务无论什么时候进行查找,看到的结果都是一致的,这叫做可重复读!
B也提交才能看到!
上面的终端A中insert操作,会有什么问题?
–多次查看,发现终端A在对应事务中insert的数据,在终端B的事务周期中,也没有什么影响,也符合可重复的特点。但是,一般的数据库在可重复读情况的时候,无法屏蔽其他事务insert的数据。
(为什么?因为隔离性实现是对数据加锁完成的,而insert待插入的数据因为并不存在,那么一般加锁无法屏蔽这类问题),会造成虽然大部分内容是可重复读的,但是insert的数据在可重复读情况被读取出来,导致多次查找时,会多查找出来新的记录,就如同产生了幻觉。这种现象,叫做幻读(phantom read)。很明显,MySQL在RR级别的时候,是解决了幻读问题的(解决的方式是用Next-Key锁(GAP+行锁)解决的。这块比较难,有兴趣同学了解一下)。
串行化
–对所有操作全部加锁,进行串行化,不会有问题,但是只要串行化,效率很低,几乎完全不会被采用
终端A中有更新或者其他操作,会阻塞。直到终端B事务提交。
其中隔离级别越严格,安全性越高,但数据库的并发性能也就越低,往往需要在两者之间找一个平衡点。
不可重复读的重点是修改和删除:同样的条件, 你读取过的数据,再次读取出来发现值不一样了
幻读的重点在于新增:同样的条件, 第1次和第2次读出来的记录数不一样
说明:
mysql 默认的隔离级别是可重复读,一般情况下不要修改
上面的例子可以看出,事务也有长短事务这样的概念。事务间互相影响,指的是事务在并行执行的时候,即都没有commit的时候,影响会比较大。
√:会发生该问题。只有串行化启用了共享锁加锁读。其他都有设置加锁读
顶级理解:
mysql只是工具 它提供了不同隔离级别的隔离策略 具体用哪一种取决于应用场景;aid保证c一致性,acid只是在数据库层面对用户的sql尽可能的提供了策略使得犯错的概率减少。但是一致性是由用户业务逻辑和aid共同支持的,因为数据库再好,用户给了业务逻辑很差的sql,mysql仍然会出错。
一致性(Consistency)
事务执行的结果,必须使数据库从一个一致性状态,变到另一个一致性状态。当数据库只包含事务成功提交的结果时,数据库处于一致性状态。如果系统运行发生中断,某个事务尚未完成而被迫中断,而未完成的事务对数据库所做的修改已被写入数据库,此时数据库就处于一种不正确(不一致)的状态。因此一致性是通过原子性来保证的。
其实一致性和用户的业务逻辑强相关,一般MySQL提供技术支持,但是一致性还是要用户业务逻辑做支撑,也就是,一致性,是由用户决定的。而技术上,通过AID保证C。
优秀文章
文章1
文章2
文章3