MySQL的事务详解

博客主页:系列专栏: MySQL
一句短话: 难在坚持,贵在坚持,成在坚持!

一. 事务的业务场景

在数据库中 事务(transaction) 可以把多个SQL给打包到一起,即将多个SQL语句变成一个整体,也就是说一个事务中的所有操作要么全部成功执行,要么完全不执行.

通过实际场景来理解事务:

实际生活中我们经常涉及转帐操作,张三给李四转账2000元,涉及到两个操作

  1. 给张三的账户余额减去2000元
  2. 给李四的账户余额增加2000元

这里就要考虑到这两个操作的完整性,也就是不能出现张三的账户余额减少了2000元,但李四的账户余额未发生变化,这就要求上面的两个操作要么全部执行完成功转账,要么一个都不执行双方都没有损失,不会出现中途发生一些问题导致数据不一致的情况.

这样的一次完整操作叫做 事务(transaction),一个事务中的所有操作要么全部成功执行,要么完全不执行.

二. 事务的使用

事务是如何保证操作的完整性的呢?

其实事务执行中间出错了,只需要让事务中的这些操作恢复成之前的样子即可,这里涉及到的一个操作,回滚(rollback).

事务处理是一种对必须整批执行的 MySQL 操作的管理机制,在事务过程中,除非整批操作全部正确执行,否则中间的任何一个操作出错,都会回滚 (rollback)到最初的安全状态以确保不会对系统数据造成错误的改动.

相关语法:

-- 开启事务
start transaction;

-- 若干条执行sql

-- 提交/回滚事务
commit/rollback;

注意:

在开启事务之后,执行sql不会立即去执行,只有等到commit操作后才会统一执行(保证原子性).

示例:
首先创建一个账户表并初始化数据

-- 创建一个账户表
create table account(
     id int primary key auto_increment,
	 name varchar(20),
	 money double(10,2)
);
-- 初始化账户信息
insert into account(name, money) values ('张三', 10000), ('李四', 10000);

首先看正常情况下的转账操作

-- 张三账户 -2000
mysql> update account set money = money - 2000 where name = '张三';
Query OK, 1 row affected (0.01 sec)
Rows matched: 1  Changed: 1  Warnings: 0
-- 李四账户 +2000
mysql> update account set money = money + 2000 where name = '李四';
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0
-- 转账成功
mysql> select * from account;
+----+--------+----------+
| id | name   | money    |
+----+--------+----------+
|  1 | 张三   |  8000.00 |
|  2 | 李四   | 12000.00 |
+----+--------+----------+
2 rows in set (0.00 sec)

如果操作中出现异常情况,比如sql语句中所写的注释格式错误导致sql执行中断.

-- 先将张三和李四的账户余额恢复为10000元
update account set money = 10000 where name = '张三';
update account set money = 10000 where name = '李四';
-- 张三 -2000
mysql> update account set money = money - 2000 where name = '张三';
Query OK, 1 row affected (0.01 sec)
Rows matched: 1  Changed: 1  Warnings: 0
-- 李四 +2000
mysql> 没加--的注释
    -> update account set money = money + 2000 where name = '李四';
ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '没加--的注释
update account set money = money + 2000 where name = '李四'' at line 1
-- 出现异常
mysql> select * from account;
+----+--------+----------+
| id | name   | money    |
+----+--------+----------+
|  1 | 张三   |  8000.00 |
|  2 | 李四   | 10000.00 |
+----+--------+----------+
2 rows in set (0.00 sec)

观察结果发现了张三的账户少了2000元,但李四的账户余额并没有增加,在实际操作中这种涉及钱的操作发生这种失误可能会造成很大的损失.

为了防止这种失误的出现我们就可以使用事务来打包这些操作.

-- 先将张的账户余额恢复为10000元
update account set money = 10000 where name = '张三';
-- 开启事务
start transaction;
-- 张三 -2000
mysql> update account set money = money - 2000 where name = '张三';
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0
-- 李四 -2000
mysql> 没加--的注释
    -> update account set money = money + 2000 where name = '李四';
ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '没加--的注释
update account set money = money + 2000 where name = '李四'' at line 1
-- 预期结果
mysql> select * from account;
+----+--------+----------+
| id | name   | money    |
+----+--------+----------+
|  1 | 张三   |  8000.00 |
|  2 | 李四   | 10000.00 |
+----+--------+----------+
2 rows in set (0.00 sec)

观察这里的结果发现在当前的数据库用户查询到的account表中的账户余额发生了变化,但开启了事务之后在commit之前只是临时的预操作并不会真的去修改表中的数据;

可以退出数据库再打开重新查询表中数据或者切换用户去查询去验证表中数据是否发生改变,这里就不作演示了.

发现操作结果异常之后,当前用户需要恢复到事务之前的状态,即进行回滚操作.

-- 回滚事务
mysql> rollback;
Query OK, 0 rows affected (0.03 sec)
-- 验证回滚后的状态
mysql> select * from account;
+----+--------+----------+
| id | name   | money    |
+----+--------+----------+
|  1 | 张三   | 10000.00 |
|  2 | 李四   | 10000.00 |
+----+--------+----------+
2 rows in set (0.00 sec)

如果开启事务之后发现预操作的结果是预期的效果,此时我们就可以提交事务,当我们提交完事务之后,数据就是真的修改了,也就是硬盘中存储的数据真的改变了.

-- 开启事务
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
-- 张三 -2000
mysql> update account set money = money - 2000 where name = '张三';
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0
-- 李四 +2000
mysql> update account set money = money + 2000 where name = '李四';
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0
-- 提交事务
mysql> commit;
Query OK, 0 rows affected (0.03 sec)

mysql> select * from account;
+----+--------+----------+
| id | name   | money    |
+----+--------+----------+
|  1 | 张三   |  8000.00 |
|  2 | 李四   | 12000.00 |
+----+--------+----------+
2 rows in set (0.00 sec)

要注意事务也不是万能的,不能保证你删表删库之后可以完全恢复,只是在适量的数据和操作下使用事务可以避免一些问题.

回滚(rollback)操作,实际上是我们把事务中的操作再进行逆操作,前面是插入,回滚就是删除…

这些操作是有很大开销的,可以保存,但不能够无限保存,最多是将正再执行的事务保存下来,额外的内容就不好再保存了; 数据库要是有几十亿条数据,占据了几百G硬盘空间,不可能去花费几个T甚至更多的空间用来记录这些数据是如何来的.

三. 事务的特性(ACID)

1. 原子性(Atomicity)

一个事务是一个不可分割的最小单位,事务中的所有操作要么全部成功,要么全部失败,没有中间状态.

原子性主要是通过事务日志中的回滚日志(undo log)来实现的,当事务对数据库进行修改时,InnoDB 会根据操作生成相反操作的 undo log,比如说对 insert 操作,会生成 delete 记录,如果事务执行失败或者调用了 rollback,就会根据 undo log 的内容恢复到执行之前的状态.

事务的原子性,也是事务的核心特性,是事务的初心.

2. 一致性(Consistency)

事务执行之前和执行之后数据都是合法的一致性状态,即使发生了异常,也不会因为异常引而破坏数据库的完整性约束,比如唯一性约束等.

事务执行前/执行后,都得是数据合法的状态; 比如像上面的转账,不能说转的过程出错了,导致出现钱转丢了的情况.

3. 持久性(Durability)

事务提交之后对数据的修改是持久性的,即使数据库宕机也不会丢失,通过事务日志中的重做日志(redo log)来保证; 事务修改之前,会先把变更信息预写到 redo log 中,如果数据库宕机,恢复后会读取 redo log 中的记录来恢复数据(回滚).

事务产生的修改,都是会写入硬盘的,程序重启/主机重启/掉电,事务都可以正常工作,保证修改是生效的.

4. 隔离性(Isolation)

这里的隔离性是指一个数据库服务器,同时执行多个事务的时候,事务之间的相互影响程度.

一个服务器,可以同时给多个客户端提供服务,这多个客户端是并发执行的关系,多个客户端就会有多个事务,多个事务同时去操作一个表的时候,特别容易出现互相影响的问题.

如果隔离性越高,就意味着事务之间的并发程度越低,执行效率越慢,但是数据准确性越高.

如果隔离性越低,就意味着事务之间的并发程度越高,执行效率越快,但是数据准确性越低.

隔离性通过事务的隔离级别来定义,并用锁机制来保证写操作的隔离性,用 MVCC 来保证读操作的隔离性.

四. 事务并发异常

在实际生产环境下,可能会出现大规模并发请求的情况,如果没有妥善的设置事务的隔离级别,就可能导致一些异常情况的出现,最常见的几种异常为脏读(Dirty Read), 幻读(Phantom Read)不可重复读(Unrepeatable Read).

1. 脏读

一个事务读取到了另外一个事务没有提交的数据(读写的是同一份数据).

说详细点就是当一个事务正在访问数据并且对数据进行了修改,而这种修改还没有提交到数据库中,与此同时时另外一个事务也访问这个数据,然后使用了这个数据; 因为这个数据是还没有提交的数据,那么另外一个事务读到的这个数据就是脏数据,依据脏数据所做的操作可能是不正确的.

用一个场景例子来理解,张三正在写代码,李四趴在屏幕前看张三写代码,等张三走掉之后,李四就把他刚刚写的这段代码删掉了,此时李四看到的这段代码就可能是一个错误的代码.

在这个场景下,张三和李四就可以理解为两个事务,这两个事务是完全并发没有任何限制的,此时就会出现脏读问题.

解决脏读问题的办法,就是降低并发性,提高隔离性,具体来说就是给这里的 “写操作” 加锁,张三在写代码的时候,李四不能看,张三和李四约定张三代码写完后会提交到githup上,李四去githup上去看.

当进行了写加锁的时候,张三写的时候,李四就不能同时去读了; 相当于降低了并发程度,提高了隔离性. 降低了一定的效率,但是提高了准确性.

2. 不可重复读

在同一事务中,连续两次读取同一数据,得到的结果不一致.

还是基于上面的场景进行理解,上面已经约定了写加锁(张三写代码过程中,李四不要读,等到张三提交之后,李四再去读).

此时张三在写代码,张三和李四有约定,所以此时李四在等张三把代码提交到githup上再去看代码.

过了一会儿,张三写完了,并将代码提交到了githup上,李四开始读代码.

当李四正在读这个代码的时候,张三觉得自己的代码还有不足,于是张三动手修改,重新提交了个版本; 导致李四读代码读了一半,突然代码自动就变了.

这种情况就是不可重复读问题了,解决办法是给读操作也加锁,张三在读代码的时候,李四不能修改.

此时这两个事务之间的并发程度进一步降低了,隔离性又进一步提高了,运行速度又进一步变慢了,数据的准确性又进—步提高了.

3. 幻读

同一事务中,用同样的操作读取两次,得到的记录数不相同.

幻读是指当事务不是独立执行时发生的一种现象,例如第一个事务对一个表中的数据进行了修改,这种修改涉及到表中的全部数据行; 同时,第二个事务也修改这个表中的数据,这种修改是向表中插入一行新数据; 那么,以后就会发生操作第一个事务的用户发现表中还有没有修改的数据行,就好象发生了幻觉一样.

再基于2中的场景进行理解,当前已经约定了写加锁和读加锁,即张三写代码的时候,李四不能读; 李四读代码的时候,张三也不能写.

此时李四在读代码,张三虽然不能去修改李四现在正在读的这个文件,但是李四又去新增/删除一个其他的文件,此时,虽然李四读的代码内容没变,但他发现,文件的数量变了; 这就是幻读问题了.

解决幻读问题的办法是 串行化,也就是彻底的舍弃并发,此时只要李四在读代码,张三就不能进行任何操作.

四. MySQL的四个隔离级别

MySQL中有 4 种事务隔离级别,由低到高依次为 读未提交 Read Uncommitted,读已提交 Read Committed,可重复读 Repeatable Read ,串行化 Serializable.

串行化的事务处理方式是最安全的,但不能说用这个就一定好,应该是根据实际需求去选择合适的隔离级别,比如银行等涉及钱的场景,就需要确保准确性,速度慢一点也没什么; 而比如抖音,B站,快手等上面的点赞数,收藏数就没必要那么精确了,这个场景下速度提高一点体验会更好一些.

脏读 不可重复读 幻读
读未提交 read uncommited
读已提交 read commited
可重复读 repeatable read
串行化 serializable
  1. read uncommited

不做任何限制,事务之间都是随意并发执行的; 并发程度最高,隔离性最差.

会产生脏读 + 不可重复读 + 幻读问题.

  1. read commited

对写操作加锁,并发程度降低,隔离性提高.

解决了脏读问题, 仍然存在不可重复读 + 幻读问题.

  1. repeatable read

写加锁,读加锁,隔离性再次提高,并发程度再次降低.

解决了脏读 + 不可重复读问题,仍然存在幻读问题.

这个隔离级别也是MySQL的默认隔离级别,如果需要改的话,可以通过MySQL的配置文件来进行调整.

  1. serializable

严格执行串行化,并发程度最低,隔离性最高,执行速度最慢.

解决了 脏读 + 不可重复读 + 幻读问题.

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

相关推荐


在正式开始之前,我们先来看下 MySQL 服务器的配置和版本号信息,如下图所示: “兵马未动粮草先行”,看完了相关的配置之后,我们先来创建一张测试表和一些测试数据。 -- 如果存在 person 表先删除 DROP TABLE IF EXISTS person; -- 创建 person 表,其中
> [合辑地址:MySQL全面瓦解](https://www.cnblogs.com/wzh2010/category/1859594.html "合辑地址:MySQL全面瓦解") # 1 为什么需要数据库备份 - 灾难恢复:当发生数据灾难的时候,需要对损坏的数据进行恢复和
物理服务机的CPU、内存、存储设备、连接数等资源有限,某个时段大量连接同时执行操作,会导致数据库在处理上遇到性能瓶颈。为了解决这个问题,行业先驱门充分发扬了分而治之的思想,对大库表进行分割,
然后实施更好的控制和管理,同时使用多台机器的CPU、内存、存储,提供更好的性能。而分治有两种实现方式:垂直拆
1 回顾 上一节我们详细讲解了如何对数据库进行分区操作,包括了 垂直拆分(Scale Up 纵向扩展)和 水平拆分(Scale Out 横向扩展) ,同时简要整理了水平分区的几种策略,现在来回顾一下。 2 水平分区的5种策略 2.1 Hash(哈希) 这种策略是通过对表的一个或多个列的Ha
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...