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 | +-------------------------+ */





