MySQL全面瓦解14:事务

关于事务

我们在数据库中需要执行一个系列的操作的时候,要保证这个系列执行的连续性和完整性,要么整个系列的执行都成功,要么就全部失败(只要有一个步骤失败,其他均回滚到之前的状态),

保证不会存在一部分成功一部分失败的情况。这就是我们事务的职责。下面举个分苹果的例子:

A同学有3个苹果,B同学有2个苹果,如果A同学给一个苹果给B同学,那么A同学只剩下2个苹果,而B同学有了3个。步骤如下

1 update tname  set apples=apples-1 where name = "A";  
2 update tname  += "B";  

当然,这是理想情况下的结果。有可能会出错:A同学减去了一个苹果,然后执行B同学的加苹果的时候,系统宕掉了,B同学没有加成功,A同学的库存中无厘头少了一个苹果。

这时候我们就需要事务支持了,有事务的情况下,就有两种状态,要么都成功,要么都失败。

操作成功:A同学减去一个苹果,B同学增加一个苹果,最终A 2个,B 3个。

操作失败:A同学依旧是3个苹果,B同学依旧是2个苹果。

事务有如下特性(参考官方描述):

1、在 MySQL 中只有使用了 Innodb 数据库引擎的数据库或表才支持事务。
2、事务处理可以用来维护数据库的完整性,保证成批的 SQL 语句要么全部执行,要么全部不执行。
3、事务用来管理 insert,update,delete 语句。

事务的四个特性(ACID)

一般来说,衡量事务必须满足四个特性:ACID,即 原子性(Atomicity,或称不可分割性)、一致性(Consistency)、隔离性(Isolation,又称独立性)、持久性(Durability)。

原子性(Atomicity):一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。

一致性(Consistency):在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设规则,这包含资料的精确度、串联性以及后续数据库可以自发性地完成预定的工作。

隔离性(Isolation):数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别,包括读未提交(read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(Serializable),下面会详细说明。

持久性(Durability):事务处理结束后,对数据的修改就是永久的,会持久化到硬盘上,即便系统故障也不会丢失。

显示和隐式事务

事务分为显示事务和隐式事务,

隐式事务:事务自动开启、提交或回滚,比如insert、update、delete语句,事务的开启、提交或回滚由mysql内部自动控制的

显示事务:事务需要手动开启、提交或回滚,由开发者自己控制。 

自动提交

MySQL中事务默认是隐式事务(即自动提交(autocommit)模式为 ON),执行insert、update、delete操作的时候,数据库自动开启事务、提交或回滚事务。如下所示:

1 mysql> show variables like 'autocommit';
2 +---------------+-------+
3 | Variable_name | Value |
4 5 | autocommit    | ON    6 7 1 row in set 

在自动提交模式下,如果没有start transaction显式地开始一个事务,那么每个sql语句都会被当做一个事务执行提交操作。

通过如下方式,可以关闭autocommit;需要注意的是,autocommit参数是针对连接的,在一个连接中修改了参数,不会对其他连接产生影响。如果你新开一个命令窗口,会恢复到默认值。

 1 mysql> set autocommit = 0 2 Query OK, rows affected
 3 
 4 mysql 5  6  7  8 OFF    9 10 set 

如果关闭了autocommit,则所有的sql语句都在一个事务中,直到执行了commit或rollback,该事务结束,同时开始了另外一个事务。

特殊操作

在MySQL中,存在一些特殊的命令,如果在事务中执行了这些命令,会马上强制执行commit提交事务;比如DDL语句(create table/drop table/alter table)。

不过,常用的select、insert、update和delete命令,都不会强制提交事务。

手动操作事务的两种方式

改变默认事务提交策略

SET AUTOCOMMIT=0 禁止自动提交

SET AUTOCOMMIT=1 开启自动提交

这个跟上面的脚本大概一致,先设置为禁止自动提交事务,这样执行的修改并没有真正的到数据库中,等commit 或者 rollback来最终确认,再手动进行事务操作。

1 --1、设置不自动提交事务
set autocommit=3 --2、手动进行事务操作
4 commit;|rollback

提交示例:

insert into classes values(5,初三五班);
 5 Query OK,1)"> row affected
 6 
 7 mysqlcommit; --提交操作
 8 Query OK,1)"> 9 
10 mysqlselect * from classes;
11 -------+-----------+
12 | classid | classname 13 14 |       1 | 初三一班  15 2 | 初三二班  16 3 | 初三三班  17 4 | 初三四班  18 5 | 初三五班  19 20 5 rows set 

回滚示例:

6,1)">初三六班rollback; --回滚操作
set 

常规启用事务

START|BEGIN 开始一个事务

ROLLBACK 事务回滚

COMMIT 事务确认

这是典型的MySQL事务操作,其中start transaction标识事务开始,commit提交事务,将执行结果写入到数据库。如果sql语句执行出现问题,会调用rollback,回滚所有已经执行成功的sql语句。

当然,也可以在事务中直接使用rollback语句进行回滚。:

1 start transaction; --、开启事务
2 /* 1条或者n条待执行的语句 */
3 rollback; --2、手动进行事务操作 

 提交示例:

> start transaction11  
6 | 初三六班  21 22 6 rows set 

 回滚示例: 

7,1)">初三七班 classes; 
set 

事务保存点的使用

事务保存点(savepoint),指的是对事务执行过程中做位置保存(类似我们打游戏时的存盘点),如果你写了一大堆的语句,但是有部分是你不想回滚的,想保留修改的状态,但是部分是你想回滚的。

这时候使用savepoint是个不错的方法。

; --开启事务
> savepoint point1; --注意:这边设置了一个存盘点
8,1)">初三八班11 Query OK,1)">12 
13 mysqlrollback to point1; --记住这个语法,回滚到存盘点,存盘点之后的语句就丢弃了
14 Query OK,1)">15 
16 mysql17 Query OK,1)">18 
19 mysql  classes; --最后输出,确实只有存盘点之前的成功了
23 24 25 26 27 28 29 7 | 初三七班  30 31 7 rows set 

这边需要注意 savepoint 和 rollback to savepoint 的配合使用。

只读事务的使用

表示在事务中执行的是一些只读操作,如查询,但是不会做insert、update、delete操作,数据库内部对只读事务可能会有一些性能上的优化。

transaction read only

 再只读操作的事务中进行增、删、改操作会报错,如下:

only  classes;
set
17 
18 mysql1792 - Cannot execute statement in a READ ONLY transaction. --这边报出异常

事务的脏读、幻读、不可重复读

脏读:读取未提交数据

脏读就是指当一个事务A正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中,这时,另外一个事务B也访问这个数据,然后使用了这个脏数据。举个例子

时间顺序 A事务 B事务

T1

开始事务  

T2

  开始事务

T3

查询A同学有2个苹果  

T4

给A同学增加一个苹果(未提交

 

T5

  查询A同学有3个苹果(读脏数据
T6 添加苹果操作出现错误,回滚回2个苹果  
 T7 提交事务  

 

 

不可重复读:前后多次读取数据不一致

不可重复读指的是在事务A中先后多次读取同一个数据,读取的结果不一样,因为另外一个事务也访问该同一数据,并且可能修改这个数据,这种现象称为不可重复读。

脏读与不可重复读的区别在于:前者读到的是其他事务未提交的数据,后者读到的是其他事务已提交的数据。

时间顺序 A事务 B事务

T1

开始事务  

T2

  开始事务

T3

   查询A同学有2个苹果

T4

给A同学增加一个苹果(未提交

 

T5

 提交事务  
T6    查询A同学有3个苹果(不可重复读

 

按照正确逻辑,事务B前后两次读取到的数据应该一致,这边一次读到的是2个,一次读到的是3个。

幻读:前后多次读取,数据总量不一致

在事务A中按照某个条件先后两次查询数据库,两次查询结果的条数不同,这种现象称为幻读。不可重复读与幻读的区别可以通俗的理解为:前者是数据变了,后者是数据的行数变了。

时间顺序 A事务 B事务

T1

开始事务  开始事务

T2

  第一次查询库存数据有2条

T3

给A同学增加一个苹果(增加了一条库存数据  

T4

提交事务

 

T5

  第二次查询库存数据有3条


按照正确逻辑,按照正确逻辑,事务B前后两次读取到的数据总量应该一致

不可重复读和幻读的区别

(1)不可重复读是读取了其他事务更改的数据,针对update操作
解决:使用行级锁,锁定该行,事务A多次读取操作完成后才释放该锁,这个时候才允许其他事务更改刚才的数据。

(2)幻读是读取了其他事务新增的数据,针对insert与delete操作
解决:使用表级锁,锁定整张表,事务A多次读取数据总量之后才释放该锁,这个时候才允许其他事务新增数据。

幻读和不可重复读都是指的一个事务范围内的操作受到其他事务的影响了。只不过幻读是重点在插入和删除,不可重复读重点在修改

事务的隔离级别

SQL标准中事务的隔离性(Isolation)定义了四种隔离级别,并规定了每种隔离级别下上述几个(脏读、不可重复读、幻读)问题是否存在

一般来说,隔离级别越低,系统开销越低,可支持的并发越高,但隔离性也越差。隔离级别与读问题的关系如下:

隔离级别的分类

注意:幻读只会在 可重复读 级别中才会出现,其他级别下不存在。

隔离级别 脏读 不可重复读 幻读

读未提交:Read Uncommitted

x

读已提交:Read Committed

× x

可重复读:Repeatable Read

×  x

串行化:Serializable

×

 × ×

查看|修改 隔离级别

查看当前的隔离级别,默认应该都是可重复读(Repeatable Read):

transaction_isolation---------------------+-----------------+
| Variable_name         | Value           | transaction_isolation REPEATABLE-READ set

修改隔离级别:

找到MySQL安装目录中的my.init文件,会看到当前的隔离级别:REPEATABLE-READ。

1 # 隔离级别设置,READ-UNCOMMITTED读未提交,1)">-COMMITTED读已提交,1)">READ可重复读,SERIALIZABLE串行
transactionisolation=REPEATABLE-READ

 修改后重启MySQL即可,下面的各项操作都是在修改了对应的隔离级别之后的操作。 

READ-UNCOMMITTED:读未提交

事物A和事物B,事物B未提交的数据,事物A可以读取到,这里读取到的数据叫做“脏数据”,这种隔离级别最低,一般理论上存在,数据库隔离级别大都高于该级别。

举个例子:学校新增加了7班和8班,教务主任进去录入了8班的班级信息,但是该事务并未提交,而8班班主任正好去查看班级信息,发现是存在的,摩拳擦掌准备录入这个班级的学生信息。可是教务主任发现班级信息写错了,应该先录入7班,于是迅速回滚了该事务。

最后8班的信息消失了。出现上述情况,就是我们所说的脏读 ,两个并发的事务,“事务A:查询班级信息”、“事务B:录入班级信息”,事务A读取了事务B尚未提交的数据。 

数据基础:

 2  3  4 set

 

时间顺序 事务A 事务B
T1 start transaction;  
T2 select * from classes;  
T3   start transaction;
T4   insert into classes values(8,'初三八班');
T5   select * from classes;
T6 select * from classes;  
T7   rollback;
T8 commit;  

说明:

事务A-T2:只有7条数据,事务A-T6:有8条数据,事务B-T6并未提交,此时事务A已经看到了事务B插入的数据,出现了脏读

事务A-T2:只有7条数据,T6-A:有8条数据,查询到的结果不一样,出现不可重复读

结论:读未提交情况下,可以读取到其他事务还未提交的数据,多次读取结果不一样,出现了脏读、不可重复读

READ-COMMITTED:读已提交

事物A和事物B,事物B提交完的数据,事物A才能读取到

这种隔离级别高于读未提交:即对方事物提交之后的数据,我当前事物才能读取到

这种隔离级别可以避免“脏数据” ,但会导致“不可重复读取” 

数据基础跟上面一样

时间顺序 事务A 事务B
T1 start transaction;  
T2 select * from classes;  
T3   start transaction; 
T4   insert into classes values(8,'初三八班');  
T5 select * from classes;  
T6   commit;
T7 select * from classes;  

说明:

事务A-T5:只有7条数据,A看不到B新增的第8条数据,说明没有脏读

事务A-T5:只有7条数据,事务A-T7:读到了8条数据,此时事务B已经提交了事务,事务A读取到了事务B提交的数据,说明可以读取到已提交的数据

事务A-T5 和 事务A-T7:两次读取的数据结果不一样,说明不可重复读

结论:读已提交情况下,无法读取到其他事务还未提交的数据,可以读取到其他事务已经提交的数据,多次读取结果不一样,未出现脏读,出现了读已提交、不可重复读。

REPEATABLE-READ:可重复读

可重复读是MySQL默认事务隔离级别

事务A和事务B,事务B提交之后的数据,事务A读取不到,即对方提交之后的数据,还是读取不到

事务B是可重复读取数据,隔离级别高于读已提交,这种隔离级别可以避免“不可重复读取”,达到可重复读取,但是可能导致“幻读”

数据基础:
8 | 初三八班  8 rows set
时间顺序 事务A 事务B
T1 start transaction;  
T2   start transaction;
T3   insert into classes values(9,'初三九班');
T4 select * from classes;  
T5   commit;
T6   select * from classes;
T7 select * from classes;  
T8 commit;  
T9 select * from classes;  

说明:

事务A-T4、事务A-T7:读到的是八条数据,事务B-T6:有数据,A事务下读不到B事务产生的数据,说明没有脏读

事务A-T7:读到的是9条数据,这时事务B已经commit,事务A看不到事务B已提交的数据,A事务下两次读的结果一样,说明可重复读

事务A-T9:读到的是9条数据。

结论:可重复读情况下,未出现脏读,未读取到其他事务已提交的数据,多次读取结果一致,即可重复读。

SERIALIZABLE:串行

SERIALIZABLE会让并发的事务串行执行。事务A和事务B,事务A在操作数据库时,事务B只能排队等待,这种隔离级别很少使用,吞吐量太低,用户体验差

这种级别可以避免“幻读”,每一次读取的都是数据库中真实存在数据,事务A与事务B串行,而不并发

数据基础同上

时间 窗口A 窗口B
T1 start transaction;  
T2 select * from classes;  
T3   start transaction;
T4   insert into classes values(9,'初三九班');
T5 select * from classes;  
T6 commit;  
T7   commit;

按时间顺序运行上面的命令,会发现事务B-T4这样会被阻塞,直到事务A执行完毕T6步骤。

可以看出来,事务只能串行执行了。串行情况下不存在脏读、不可重复读、幻读的问题了。

隔离级别选择注意点

1、读已提交(READ-COMMITTED)通常用的比较多,也是MySQL默认选项。

2、具体选择哪种需要结合具体的业务来选择,隔离级别越高,并发性也低,比如最高级别SERIALIZABLE会让事物串行执行,并发操作变成串行了,会导致系统性能直接降低

事务典型用法

 1 DROP PROCEDURE IF EXISTS t_test;  
 2 DELIMITER //  
 3 CREATE PROCEDURE t_test()  
 4   BEGIN  
 5     DECLARE t_error INTEGER;  
 6     DECLARE CONTINUE HANDLER FOR SQLEXCEPTION SET t_error  7     START TRANSACTION 8          into students(studentname,score,classid) VALUE(A',1); font-weight: bold">99,1); font-weight: bold">3 9          lala);  --这边执行错误,两个语句都会被回滚
10          IF t_error THEN  
11              ROLLBACK;  -- 有错误回滚
12          ELSE  
13              COMMIT--没错误提交
14          END IF15 END //DELIMITER; 
16 CALL t_test();

 

 这个是典型的用法,score是decimal类型,这两个语句都会被回滚。

MVCC了解

RR解决脏读、不可重复读、幻读等问题,使用的是MVCC(Multi-Version Concurrency Control)协议,即多版本的并发控制协议。

有多个请求来读取表中的数据时可以不采取任何操作,但是多个请求里有读请求,又有修改请求时必须有一种措施来进行并发控制。不然很有可能会造成不一致。
读写锁,解决上述问题很简单,只需用两种锁的组合来对读写请求进行控制即可,这两种锁被称为:

1、共享锁(shared lock),又叫做 读锁

读锁是可以共享的,或者说多个读请求可以共享一把锁读数据,不会造成阻塞。

2、排他锁(exclusive lock),1)">写锁

写锁会排斥其他所有获取锁的请求,一直阻塞,直到写入完成释放锁。 

通过读写锁,可以做到读读可以并行,但是不能做到写读,写写并行。这边了解一下,后续专门一篇来说明MVCC的原理和实现机制分析。 

总结

1、认识ACID(原子性、一致性、隔离性、持久性)特性及其实现原理

2、了解事务的脏读、幻读、不可重复读

3、了解事务的隔离级别以及原理

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


navicat查看某个表的所有字段的详细信息 navicat设计表只能一次查看一个字段的备注信息,那怎么才能做到一次性查询表的信息呢?SELECT COLUMN_NAME,COLUMN_COMMENT,COLUMN_TYPE,COLUMN_KEY FROM information_schema.CO
文章浏览阅读4.3k次。转载请把头部出处链接和尾部二维码一起转载,本文出自逆流的鱼yuiop:http://blog.csdn.net/hejjunlin/article/details/52768613前言:数据库每天的数据不断增多,自动删除机制总体风险太大,想保留更多历史性的数据供查询,于是从小的hbase换到大的hbase上,势在必行。今天记录下这次数据仓库迁移。看下Agenda:彻底卸载MySQL安装MySQL_linux服务器进行数据迁移
文章浏览阅读488次。恢复步骤概要备份frm、ibd文件如果mysql版本发生变化,安装回原本的mysql版本创建和原本库名一致新库,字符集都要保持一样通过frm获取到原先的表结构,通过的得到的表结构创建一个和原先结构一样的空表。使用“ALTER TABLE DISCARD TABLESPACE;”命令卸载掉表空间将原先的ibd拷贝到mysql的仓库下添加用户权限 “chown . .ibd”,如果是操作和mysql的使用权限一致可以跳过通过“ALTER TABLE IMPORT TABLESPACE;”命令恢_alter table discard tablespace
文章浏览阅读225次。当MySQL单表记录数过大时,增删改查性能都会急剧下降,可以参考以下步骤来优化:单表优化除非单表数据未来会一直不断上涨,否则不要一开始就考虑拆分,拆分会带来逻辑、部署、运维的各种复杂度,一般以整型值为主的表在千万级以下,字符串为主的表在五百万以下是没有太大问题的。而事实上很多时候MySQL单表的性能依然有不少优化空间,甚至能正常支撑千万级以上的数据量:字段尽量使用TINYINT、SMALLINT、MEDIUM_INT作为整数类型而非INT,如果非负则加上UNSIGNEDVARCHAR的长度只分配_开发项目 浏览记录表 过大怎么办
文章浏览阅读1.5k次。Mysql创建、删除用户MySql中添加用户,新建数据库,用户授权,删除用户,修改密码(注意每行后边都跟个;表示一个命令语句结束):1.新建用户登录MYSQL:@>mysql -u root -p@>密码创建用户:mysql> insert into mysql.user(Host,User,Password) values("localhost_删除mysql用户组
MySQL是一种开源的关系型数据库管理系统,被广泛应用于各类应用程序的开发中。对于MySQL中的字段,我们需要进行数据类型以及默认值的设置,这对于数据的存储和使用至关重要。其中,有一个非常重要的概念就是MySQL字段默认字符串。 CREATE TABLE `my_...
MySQL是一个流行的开源关系型数据库管理系统,广泛应用于Web应用程序开发、数据存储和管理。在使用MySQL时,正确设置字符集非常重要,以确保数据的正确性和可靠性。 在MySQL中,字符集表示为一系列字符和字母的集合。MySQL支持多种字符集,包括ASCII、UTF...
MySQL存储函数 n以内偶数 MySQL存储函数能够帮助用户简化操作,提高效率,常常被用于计算和处理数据。下面我们就来了解一下如何使用MySQL存储函数计算n以内的偶数。 定义存储函数 首先,我们需要定义一个MySQL存储函数,以计算n以内的偶数。下...
MySQL是一个流行的关系型数据库管理系统,基于客户机-服务器模式,可在各种操作系统上运行。 MySQL支持多种字符集,不同的字符集包括不同的字符,如字母、数字、符号等,并提供不同的排序规则,以满足不同语言环境的需求。 //查看MySQL支持的字符集与校对规...
在MySQL数据库中,我们有时需要对特定的字符串进行截取并进行分组统计。这种操作对于数据分析和报表制作有着重要的应用。下面我们将讲解一些基本的字符串截取和分组统计的方法。 首先,我们可以使用substring函数对字段中的字符串进行截取。假设我们有一张表stude...
MySQL提供了多种字符串的查找函数。下面我们就一一介绍。 1. LIKE函数 SELECT * FROM mytable WHERE mycolumn LIKE 'apple%'; 其中"apple%"表示以apple开头的字符串,%表示任意多个字符...
MySQL 是一种关系型数据库管理系统,广泛应用于各种不同规模和类型的应用程序中。在 MySQL 中,处理字符串数据是很常见的任务。有时候,我们需要在字符串的开头添加一定数量的 0 ,以达到一定的位数。比如,我们可能需要将一个数字转换为 4 位或 5 位的字符串,不足的...
MySQL是一种流行的关系型数据库管理系统,支持多种数据类型。以下是MySQL所支持的数据类型: 1. 数值型数据类型: - TINYINT 保存-128到127范围内的整数 - SMALLINT 保存-32768到32767范围内的整数 - MEDIU...
MySQL中存储Emoji表情字段类型 在现代互联网生态中,表情符号已经成为人们展示情感和思想的重要方式之一,因此将表情符号存储到数据库中是一个经常出现的问题。MySQL作为最流行的开源关系型数据库管理系统之一,也需要能够存储和管理这些表情符号的字段类型。 UT...
MySQL是一种关系型数据库管理系统。在MySQL数据库中,有多种不同的数据类型。而其中,最常见的数据类型之一就是字符串类型。在MySQL中,字符串类型的数据通常会被存储为TEXT或VARCHAR类型。 首先,让我们来看一下VARCHAR类型。VARCHAR是My...
MySQL字符串取整知识详解 MySQL是一种开源的关系型数据库管理系统,广泛应用于各个领域。在使用MySQL过程当中,我们经常需要对数据进行取整操作。本文将介绍如何使用MySQL字符串取整来处理数据取整问题。 什么是MySQL字符串取整? MySQL...
使用MySQL进行数据存储是现代应用程序开发中一个非常重要的组成部分。在MySQL中,数据存储的一个重要特点就是字符长度无限制。在下文中,我们将会详细探讨MySQL字符长度无限制的特征和优势。 什么是MySQL字符长度无限制? MySQL字符长度无限制是指在...
在MySQL中,常常会涉及到字符串和数字之间的比较。然而它们有着不同的排序规则,因此需要注意对它们进行正确的比较。 首先我们来看一下数字比较。 SELECT 1 < 2; -- 返回 1 SELECT 2 > 1; -- 返回 1 SELEC...
MySQL是一种流行的关系型数据库管理系统,可以处理各种不同类型的数据。其中字符串是MySQL中最重要的数据类型之一,因为它可以存储各种不同的数据,例如邮件地址、文本信息、数字等等。在MySQL中,有时候我们需要将字符串按照某个符合进行分隔,例如将一条包含多个数字的字符...
在MySQL中,我们经常需要将字符串与变量拼接起来,以便满足数据操作的需求。可以使用CONCAT函数来进行字符串与变量的拼接,下面是一个使用CONCAT函数的例子: SELECT CONCAT('Hello', ' ', 'world'); 这个例子...