2.3 隐式提交数据的情况
- 数据定义语言(Data definition language,缩写为:DDL)
数据库对象,指的就是数据库
、表
、视图
、存储过程
等结构。当使用CREATE
、ALTER
、DROP
等语句去修改数据库对象时,就会隐式的提交前边语句所属于的事务。即:
BEGIN; SELECT ... #事务中的一条语句 UPDATE ...#事务中的一条语句 ... #丰务中的其它语句 CREATE TABLE ...# 此语句会隐式的提交前边语句所属于的事务
- 隐式使用或修改mysql数据库中的表
当使用ALTER USER
、CREATE USER
、DROP USER
、GRANT
、RENAME USER
、REVOKE
.、SET PASSWORD
等语句时也会隐式的提交前边语句所属于的事务
事务控制或关于锁定的语句
当在一个事务还没提交或者回滚时就又使用START TRANSACTION
或者BEGIN
语句开启了另一个事务时,会隐式的提交
上—个事务。即:
BEGIN; SELECT ... #事务中的一条语句 UPDATE ... #事务中的一条语句 ... #事务中的其它语句 BEGIN; #此语句会隐式的提交前面语句所属于的事务
② 当前的 autocommit
系统变量的值为 OFF
,我们手动把它调为 ON
时,也会 隐式的提交
前边语句所属的事务。
③ 使用 LOCK TABLES
、 UNLOCK TABLES
等关于锁定的语句也会 隐式的提交
前边语句所属的事务。
- 加载数据的语句
使用LOAD DATA
语句来批量往数据库中导入数据时,也会隐式的提交
前边语句所属的事务。
关于MySQL复制的一些语句
使用START SLAVE
、STOP SLAVE
、RESET SLAVE
、CHANGE MASTER TO
等语句时会隐式的提交
前边语句所属的事务。
其它的一些语句
使用ANALYZE TABLE
、CACHE INDEX
、CHECK TABLE
、FLUSH
、LOAD INDEX INTO CACHE
、OPTIMIZE TABLE
、REPAIR TABLE
、RESET
等语句也会隐式的提交前边语句所属的事务。
2.4 使用举例1:提交与回滚
我们看下在 MySQL 的默认状态下,下面这个事务最后的处理结果是什么。
演示
#4.案例分析 #SET autocommit = TRUE; #举例1: commit 和 rollback USE atguigudb2; #情况1: CREATE TABLE user3(NAME VARCHAR(15) PRIMARY KEY); SELECT * FROM user3; BEGIN; INSERT INTO user3 VALUES('张三'); #此时不会自动提交数据 COMMIT; BEGIN; #开启一个新的事务 INSERT INTO user3 VALUES('李四'); #此时不会自动提交数据 INSERT INTO user3 VALUES('李四'); #受主键的影响,不能添加成功 ROLLBACK; SELECT * FROM user3; /* +--------+ | NAME | +--------+ | 张三 | +--------+ */ #情况2: TRUNCATE TABLE user3; #DDL操作会自动提交数据,不受autocommit变量的影响。 SELECT * FROM user3; BEGIN; INSERT INTO user3 VALUES('张三'); #此时不会自动提交数据 COMMIT; INSERT INTO user3 VALUES('李四');# 默认情况下(即autocommit为true),DML操作也会自动提交数据。 INSERT INTO user3 VALUES('李四'); #事务的失败的状态 ROLLBACK; SELECT * FROM user3; /* +--------+ | NAME | +--------+ | 张三 | | 李四 | +--------+ */ #情况3: TRUNCATE TABLE user3; SELECT * FROM user3; SELECT @@completion_type; SET @@completion_type = 1; BEGIN; INSERT INTO user3 VALUES('张三'); COMMIT; SELECT * FROM user3; INSERT INTO user3 VALUES('李四'); INSERT INTO user3 VALUES('李四'); ROLLBACK; SELECT * FROM user3; /* +--------+ | NAME | +--------+ | 张三 | +--------+ */
能看到相同的SQL代码,只是在事务开始之前设置了SET @@completion_type = 1;结果就和第一次处理的一样,只有一个“张三”。这是为什么呢?
这里讲解下 MySQL中completion_type参数的作用,实际上这个参数有3种可能:
1.completion=0,这是默认情况。当执行COMNIT的时候会提交事务,在执行下一个事务时,还需要使START TRANSACTION 或者BEGIN来开启。
2.completion=1,这种情况下,当提交事务后,相当于执行了COMMIT AND CHAIN,也就是开启一个链式事务,即提交事务之后会开启一个相同隔离级别的事务。
3.completion=2,这种情况下CONMMIT=COMMIT AND RELEASE,也就是提交后,会自动与服务器断开连接
当我们设置 autocommit=0 时,不论是否采用 START TRANSACTION 或者 BEGIN 的方式来开启事务,都需要用 COMMIT 进行提交,让事务生效,使用 ROLLBACK 对事务进行回滚。
当我们设置 autocommit=1 时,每条 SQL 语句都会自动进行提交。 不过这时,如果你采用 START TRANSACTION 或者 BEGIN 的方式来显式地开启事务,那么这个事务只有在 COMMIT 时才会生效,在 ROLLBACK 时才会回滚。
链事务跳转到4.分类
2.5 使用举例2:测试不支持事务的engine
USE atguigudb3; #举例2:体会INNODB 和 MyISAM CREATE TABLE test1(i INT) ENGINE = INNODB; CREATE TABLE test2(i INT) ENGINE = MYISAM; #针对于innodb表 BEGIN INSERT INTO test1 VALUES (1); ROLLBACK; SELECT * FROM test1; #Empty set (0.00 sec) #针对于myisam表:不支持事务 BEGIN INSERT INTO test2 VALUES (1); ROLLBACK; SELECT * FROM test2; #1
2.6 使用举例3:SAVEPOINT
#举例3:体会savepoint CREATE TABLE user3(NAME VARCHAR(15),balance DECIMAL(10,2)); BEGIN INSERT INTO user3(NAME,balance) VALUES('张三',1000); COMMIT; SELECT * FROM user3; /* +--------+ | NAME | +--------+ | 张三 | +--------+ */ BEGIN; UPDATE user3 SET balance = balance - 100 WHERE NAME = '张三'; UPDATE user3 SET balance = balance - 100 WHERE NAME = '张三'; SAVEPOINT s1;#设置保存点 UPDATE user3 SET balance = balance + 1 WHERE NAME = '张三'; ROLLBACK TO s1; #回滚到保存点 SELECT * FROM user3; #801->800 ROLLBACK; #回滚操作 SELECT * FROM user3; #1000
3. 事务隔离级别
MySQL是一个 客户端/服务器 架构的软件,对于同一个服务器来说,可以有若干个客户端与之连接,每个客户端与服务器连接上之后,就可以称为一个会话( Session )。每个客户端都可以在自己的会话中向服务器发出请求语句,一个请求语句可能是某个事务的一部分,也就是对于服务器来说可能同时处理多个事务。事务有 隔离性 的特性,理论上在某个事务 对某个数据进行访问 时,其他事务应该进行 排队 ,当该事务提交之后,其他事务才可以继续访问这个数据。但是这样对 性能影响太大 ,我们既想保持事务的隔离性,又想让服务器在处理访问同一数据的多个事务时 性能尽量高些 ,那就看二者如何权衡取舍了
3.1 数据准备
创建一个表:
CREATE TABLE student ( studentno INT, name VARCHAR(20), class varchar(20), PRIMARY KEY (studentno) ) Engine=InnoDB CHARSET=utf8;
然后向这个表里插入一条数据:
INSERT INTO student VALUES(1, '小谷', '1班');
现在表里的数据就是这样的:
select * from student; /* +-----------+--------+-------+ | studentno | name | class | +-----------+--------+-------+ | 1 | 小谷 | 1班 | +-----------+--------+-------+ */
3.2 数据并发问题
针对事务的隔离性和并发性怎么做取舍呢?先看一下访问相同数据的事务在 不保证串行执行
(也就是执行完一个再执行另一个)的情况下可能会出现哪些问题:
1. 脏写( Dirty Write
)
对于两个事务 Session A、Session B,如果事务Session A 修改了
另一个 未提交
事务Session B 修改过
的数据,那就意味着发生了 脏写
Session A和Sessione各开启了一个事务,Session B中的事务先将studentno列为1的记录的name列更新为李四,然后Session A中的事务接着又把这条studentno列为1的记录的name列更新为张三’。如果之后Session B中的事务进行了回滚,那么Session A中的更新也将不复存在,这科现象就称之为脏写。这时Session A中的事务就没有效果了,明明把数据更新了,最后也提交事务了,最后看到的数据什么变化也没有。这里大家对事务的隔离级比较了解的话,会发现默认隔离级别下,上面SessionA中的更新语句会处于等待状态,这里只是跟大家说明一下会出现这样现象。
2. 脏读( Dirty Read
)
对于两个事务 Session A、Session B,Session A 读取
了已经被 Session B 更新
但还 没有被提交
的字段。之后若 Session B 回滚
,Session A 读取
的内容就是 临时且无效
的。
Session A和Session B各开启了一个事务,Session B中的事务先将studentno列为1的记录的name列更新
为’张三’,然后Session A中的事务再去查询这条studentno为1的记录,如果读到列name的值为’张三’,而Session B中的事务稍后进行了回滚,那么Session A中的事务相当于读到了一个不存在的数据,这种现象就称之为 脏读 。
3. 不可重复读( Non-Repeatable Read
)
对于两个事务Session A、Session B,Session A 读取
了一个字段,然后 Session B 更新
了该字段。 之后Session A 再次读取
同一个字段, 值就不同
了。那就意味着发生了不可重复读。
在Session B中提交了几个 隐式事务
(注意是隐式事务,意味着语句结束事务就提交了),这些事务都修改了studentno列为1的记录的列name的值,每次事务提交之后,如果Session A中的事务都可以查看到最新的值,这种现象也被称之为 不可重复读
。
4. 幻读( Phantom )
对于两个事务Session A、Session B, Session A 从一个表中 读取 了一个字段, 然后 Session B 在该表中 插入 了一些新的行。 之后, 如果 Session A 再次读取 同一个表, 就会多出几行。那就意味着发生了幻读。
Session A中的事务先根据条件 studentno > 0这个条件查询表student,得到了name列值为’张三’的记录;之后Session B中提交了一个 隐式事务 ,该事务向表student中插入了一条新记录;之后Session A中的事务再根据相同的条件 studentno > 0查询表student,得到的结果集中包含Session B中的事务新插入的那条记录,这种现象也被称之为 幻读 。我们把新插入的那些记录称之为幻影记录。
注意1:
有的同学会有疑问,那如果Session B中删除了一些符合studentno > 的记录而不是插入新记录,那SessionA之后再根据studentno > 0的条件读取的记录变少了,这种现象算不算幻读呢?这种现象不属于幻读,幻读强调的是一个事务按照某个相同条件多次读取记录时,后读取时读到了之前没有读到的记录。
注意2:
那对于先前已经读到的记录,之后又读取不到这种情况,算啥呢?这相当于对每一条记录都发生了不可重复读
的现象。幻读只是重点强调了读取到了之前读取没有获取到的记录
3.3 SQL中的四种隔离级别
上面介绍了几种并发事务执行过程中可能遇到的一些问题,这些问题有轻重缓急之分,我们给这些问题按照严重性来排一下序:
脏写 > 脏读 > 不可重复读 > 幻读
我们愿意舍弃一部分隔离性来换取一部分性能在这里就体现在:设立一些隔离级别,隔离级别越低,并发问题发生的就越多。 SQL标准 中设立了4个 隔离级别 :
READ UNCOMMITTED :读未提交,在该隔离级别,所有事务都可以看到其他未提交事务的执行结果。不能避免脏读、不可重复读、幻读。
READ COMMITTED :读已提交,它满足了隔离的简单定义:一个事务只能看见已经提交事务所做的改变。这是大多数数据库系统的默认隔离级别(但不是MySQL默认的)。可以避免脏读,但不可重复读、幻读问题仍然存在。
REPEATABLE READ :可重复读,事务A在读到一条数据之后,此时事务B对该数据进行了修改并提交,那么事务A再读该数据,读到的还是原来的内容。可以避免脏读、不可重复读,但幻读问题仍然存在。这是MySQL的默认隔离级别。
SERIALIZABLE :可串行化,确保事务可以从一个表中读取相同的行。在这个事务持续期间,禁止其他事务对该表执行插入、更新和删除操作。所有的并发问题都可以避免,但性能十分低下。能避免脏读、不可重复读和幻读。
SQL标准 中规定,针对不同的隔离级别,并发事务可以发生不同严重程度的问题,具体情况如下:
YES表示没有解决
脏写
怎么没涉及到?因为脏写这个问题太严重了,不论是哪种隔离级别,都不允许脏写的情况发生。
不同的隔离级别有不同的现象,并有不同的锁和并发机制,隔离级别越高,数据库的并发性能就越差,4种事务隔离级别与并发性能的关系如下:
3.4 MySQL支持的四种隔离级别
不同的数据库厂商对SQL标准中规定的四种隔离级别支持不一样。比如,Oracle就只支持READ COMNITTED(默认隔离级别)和SERIALIZABLE隔离级别。MySQL虽然支持4种隔离级别,但与SQL标准中所规定的各级隔离级别允许发生的问题却有些出入,MySQL在REPEATABLE READ隔离级别下,是可以禁止幻读问题的发生的,禁止幻读的原因在第16章讲解。
MySQL的默认隔离级别为REPEATABLE READ,可以手动修改一下事务的隔离级别
# 查看隔离级别,MySQL 5.7.20的版本之前: SHOW VARIABLES LIKE 'tx_isolation'; /* +---------------+-----------------+ | Variable_name | Value | +---------------+-----------------+ | tx_isolation | REPEATABLE-READ | +---------------+-----------------+ */ # MySQL 5.7.20版本之后,引入transaction_isolation来替换tx_isolation # 查看隔离级别,MySQL 5.7.20的版本及之后: SHOW VARIABLES LIKE 'transaction_isolation'; /* +-----------------------+-----------------+ | Variable_name | Value | +-----------------------+-----------------+ | transaction_isolation | REPEATABLE-READ | +-----------------------+-----------------+ */ #或者不同MySQL版本中都可以使用的: SELECT @@transaction_isolation; /* +-------------------------+ | @@transaction_isolation | +-------------------------+ | REPEATABLE-READ | +-------------------------+ */