MySQL中的事务是一组数据库操作,这些操作被视为单个逻辑单元并且被当做原子操作执行,这意味着它们要么全部成功,要么全部失败,没有中间状态。事务通常用于确保数据库中的数据完整性和一致性。
在MySQL中,事务可以使用以下命令启动和结束:
BEGIN或START TRANSACTION:用于开始一个新的事务。
COMMIT:用于提交事务,即将所做的更改保存到数据库中。
ROLLBACK:用于撤销事务,即回滚到事务开始之前的状态。
复制代码
MySQL数据库本身是自带事务,MySQL在执行SQL语句的时候是【自动提交事务】,保证SQL语句正常执行并且更改数据库中数据,MySQL中事务主要就是保证数据库中数据安全性,通过事务的特征(ACID)已到达SQL语句操作数据的安全性
MySQL的事务其实就是一组逻辑性操作,这个操作的要求要么都成功,要么都失败
因为MySQL是自动事务,所以对于某些操作需要手动开启事务才可以达到效果
数据的准备
# 准备一些事务数据
# 创建一张账户表
create table account(
id int PRIMARY KEY AUTO_INCREMENT,
name varchar(20) NOT NULL,
money double
);
# 插入数据
INSERT INTO account(name,money) values
('zs',10000),('ls',10000);
select * from account;
复制代码
MySQL中事务就是保证数据库内表中数据被SQL语句修改之后数据安全问题,使用就是事务提供特性(ACID),模拟一个场景:利用account账户进行转账和收款的过程
提供一组操作 zs 转账1000【money - 1000】 ls 进账 1000 【money+1000】
MySQL使用事务原则特性就是保证 SQL语句的执行要么都成功,要么都失败
提供一组操作成功 zs 余额 9000 ls 余额 11000
提供一组操作失败 zs 余额 10000 ls 余额 10000
提供一组操作是不会出现 zs 余额 9000 ls余额10000 或 zs 余额10000 ls余额 11000
正常转账版本
# 假设 zs 和 ls 谈成了一笔大面买 交易金额是1000元
# 需要从 zs 账户中将1000元扣除 ls 账户中需要加入这1000
# zs 需要转账 1000
UPDATE account SET money = money-1000 WHERE name = 'zs'; -- 模拟转账
# ls 需要进账 1000
UPDATE account SET money = money+1000 WHERE name = 'ls'; -- 模拟收款
# 账户查询
SELECT * FROM account;
复制代码
2021-12-17_084859
提供一个异常转账的操作
# 准备一些事务数据
# 创建一张账户表
create table account(
id int PRIMARY KEY AUTO_INCREMENT,
name varchar(20) NOT NULL,
money double
);
# 插入数据
INSERT INTO account(name,money) values
('zs',10000),('ls',10000);
select * from account;
# 假设 zs 和 ls 谈成了一笔大面买 交易金额是1000元
# 需要从 zs 账户中将1000元扣除 ls 账户中需要加入这1000
# zs 需要转账 1000
UPDATE account SET money = money-1000 WHERE name = 'zs'; -- 模拟转账
/*
模拟 【数据库因为zs用户转账之后数据出现问题】
zs 转账之后出现问题 ---》 突然间出现【断电、火灾、数据库崩溃、各种操作异常】
导致 ls的收款语句【没有执行】,程序直接退出 ls账户没有任何金额进入
MySQL是自动事务,提交到MySQL中数据库操作一旦执行成功就“永久生效”
此时面临这样场景,MySQL中自动事务就无法满足需求
事务最主要特点就是:【要么都成功,要么都失败】
而现在就违背了这个原则: zs 转账之后扣款1000 但是出现问题,这1000元不应该扣款成功,而是返回给zs账户。
像这样对于数据库中数据修改的操作 建议将自动事务修改为手动事务
*/
# ls 需要进账 1000
UPDATE account SET money = money+1000 WHERE name = 'ls'; --
模拟收款
# 账户查询
SELECT * FROM account;
复制代码
2021-12-17_090408
可以对MySQL进行手动事务的开启
# 准备一些事务数据
# 创建一张账户表
create table account(
id int PRIMARY KEY AUTO_INCREMENT,
name varchar(20) NOT NULL,
money double
);
# 插入数据
INSERT INTO account(name,money) values
('zs',10000),('ls',10000);
select * from account;
#手动开启事务
start TRANSACTION; -- 这个语句作用就是开启“手动”事务
/*
当上述语句执行完毕之后,后续语句执行都是“手动事务”操作
手动事务中提供两个核心操作
commit 提交 【在确认操作无误的前提下,使用commit可以将SQL语句提交给数据库执行并永久生效】
rollback 回滚 【当执行SQL语句出现问题的时候,就可以使用rollbacl执行回滚操作,将SQL语句操作回滚到之前一个状态】
*/
# 假设 zs 和 ls 谈成了一笔大面买 交易金额是1000元
# 需要从 zs 账户中将1000元扣除 ls 账户中需要加入这1000
# zs 需要转账 1000
UPDATE account SET money = money-1000 WHERE name = 'zs'; -- 模拟转账
/*
模拟 【数据库因为zs用户转账之后数据出现问题】
zs 转账之后出现问题 ---》 突然间出现【断电、火灾、数据库崩溃、各种操作异常】
导致 ls的收款语句【没有执行】,程序直接退出 ls账户没有任何金额进入
MySQL是自动事务,提交到MySQL中数据库操作一旦执行成功就“永久生效”
此时面临这样场景,MySQL中自动事务就无法满足需求
事务最主要特点就是:【要么都成功,要么都失败】
而现在就违背了这个原则: zs 转账之后扣款1000 但是出现问题,这1000元不应该扣款成功,而是返回给zs账户。
像这样对于数据库中数据修改的操作 建议将自动事务修改为手动事务
*/
ROLLBACK; -- 回滚
# ls 需要进账 1000
UPDATE account SET money = money+1000 WHERE name = 'ls'; -- 模拟收款
#COMMIT; -- 提交
# 账户查询
SELECT * FROM account;
复制代码
2021-12-17_091929
开启手动事务之后如果转账成功
# 准备一些事务数据
# 创建一张账户表
create table account(
id int PRIMARY KEY AUTO_INCREMENT,
name varchar(20) NOT NULL,
money double
);
# 插入数据
INSERT INTO account(name,money) values
('zs',10000),('ls',10000);
select * from account;
#手动开启事务
start TRANSACTION; -- 这个语句作用就是开启“手动”事务
/*
当上述语句执行完毕之后,后续语句执行都是“手动事务”操作
手动事务中提供两个核心操作
commit 提交 【在确认操作无误的前提下,使用commit可以将SQL语句提交给数据库执行并永久生效】
rollback 回滚 【当执行SQL语句出现问题的时候,就可以使用rollbacl执行回滚操作,将SQL语句操作回滚到之前一个状态】
*/
# 假设 zs 和 ls 谈成了一笔大面买 交易金额是1000元
# 需要从 zs 账户中将1000元扣除 ls 账户中需要加入这1000
# zs 需要转账 1000
UPDATE account SET money = money-1000 WHERE name = 'zs'; -- 模拟转账
/*
模拟 【数据库因为zs用户转账之后数据出现问题】
zs 转账之后出现问题 ---》 突然间出现【断电、火灾、数据库崩溃、各种操作异常】
导致 ls的收款语句【没有执行】,程序直接退出 ls账户没有任何金额进入
MySQL是自动事务,提交到MySQL中数据库操作一旦执行成功就“永久生效”
此时面临这样场景,MySQL中自动事务就无法满足需求
事务最主要特点就是:【要么都成功,要么都失败】
而现在就违背了这个原则: zs 转账之后扣款1000 但是出现问题,这1000元不应该扣款成功,而是返回给zs账户。
像这样对于数据库中数据修改的操作 建议将自动事务修改为手动事务
*/
#ROLLBACK; -- 回滚
# ls 需要进账 1000
UPDATE account SET money = money+1000 WHERE name = 'ls'; -- 模拟收款
COMMIT; -- 提交
# 账户查询
SELECT * FROM account;
复制代码
2021-12-17_092522
事务的原则(特征)
原子性(Atomicity)是指事务是一个不可分割的单位,事务中操作要么都发生(成功),要么不发生(失败)
例如: 转账案例
操作成功 zs money = 9000(-1000) ls money = 11000(+1000)
操作失败 zs money = 10000 ls money = 10000
不可能发生 zs money = 9000(-1000) ls money = 10000
或 zs money = 10000 ls money =11000
一致性(Consistency)是指事务前后数据完成性必须保持一致
例如: 无论发生什么(成功/失败)数据的总和使用保持20000
不可能出现 zs money = 9000(-1000) ls money = 10000 (+10000)总和 19000
不可能出现 zs money = 10000(-1000) ls money = 11000 (+10000)总和 21000
隔离性(Isolation)是指事务在多个用户并发操作数据库时,一个用户的事务是不能其他用户所干扰,多并发事务之间的数据是相互隔离的
持久性(Durability)是指一个事务一旦被提交,它对数据库中数据修改永久的,就算接下来数据库发生故障也不会对数据造成影响
事务操作和隔离性问题
事务操作之设置回滚点
当插入大量数据的时候(1亿条数据),事务的原则要么都成功,要么都是失败
如果你保持这个事务原则,假设插入到7000W条数据的时候,程序崩溃了,事务的回滚执行会将之前所有插入都回滚,这样一来就你需要从第一条插入,这种无脑回滚对于操作是有影响,所以为了解决这样问题,可以在SQL语句或操作代码中设置【回滚点】,一旦出现错误,不是回滚所有数据,而是回滚到这个回滚点设置的位置
设置回滚点 -----》 saveponit 回滚点的名字
回滚数据 -----》 rollback to 回滚点的名字
#手动开启事务
start TRANSACTION; -- 这个语句作用就是开启“手动”事务
# zs 需要转账 1000
UPDATE account SET money = money-1000 WHERE name = 'zs'; -- 模拟转账
UPDATE account SET money = money-1000 WHERE name = 'zs';
UPDATE account SET money = money-1000 WHERE name = 'zs';
UPDATE account SET money = money-1000 WHERE name = 'zs';
#设置一个回滚点
SAVEPOINT zrollback;
#操作都失败了
UPDATE account SET money = money-1000 WHERE name = 'zs'; -- 模拟转账
UPDATE account SET money = money-1000 WHERE name = 'zs';
UPDATE account SET money = money-1000 WHERE name = 'zs';
UPDATE account SET money = money-1000 WHERE name = 'zs';
# 回滚到指定的回滚点之前
ROLLBACK TO zrollback;
# 账户查询
SELECT * FROM account;
复制代码
隔离性问题
MySQL中事务一共有四种隔离性【级别是从低到高】
级别 | 名字 | 隔离级别(操作英文) | 脏读 | 不可重复读 | 幻读 | 数据库默认隔离和级别 |
---|---|---|---|---|---|---|
1 | 读未提交 | read uncommitted | 是 | 是 | 是 | |
2 | 读已提交 | read committed | 否 | 是 | 是 | |
3 | 可重复读 | repeatable read | 否 | 否 | 是 | MySQL |
4 | 串行化 | serializable | 否 | 否 | 否 |
如果在不考虑事务的隔离性的前提下就会出现【脏读、不可重复读、幻读】,事务的隔离性的理想状态,就是在操作事务时出现互不干扰的结果
如果需使用MySQL数据库避免这三个问题【脏读、不可重复读、幻读】,只能开启最高隔离级别“串行化”,如果一旦开启串行化,所有MySQL客户端的操作都是“串行”效果只要一个客户端在操作数据库,没有执行commit之前,剩余客户端的操作都要等待
隔离级别越高,性能越差,特别是“串行化”,正常开发中MySQL的默认隔离级别就可以了
#查询和修改数据库隔离级别
#查询
select @@tx_isolation; --》 mysql5.7 但是 mysql8.0失效
select @@transaction_isolation; --》 mysql8.0查询
#修改隔离级别
read uncommitted(读未提交) read committed(读已提交)
repeatable read(可重复读) serializable(串行化)
复制代码
以下的演示中都需要开启两个创建A窗口和B窗口
演示脏读
一个事务中读取到另外一个事务没有提交数据就叫做脏读
PS:演示中需要开启 read uncommitted(读未提交) 隔离级别
数据准备
create table T(
ID int,
name varchar(255)
);
INSERT INTO T(id,name) values(1,'牛A'),(1,'牛B'),(1,'牛C');
复制代码
文末扫码领取福利!
- 先在A、B窗口中确认隔离级别
#查询隔离级别
select @@transaction_isolation;
复制代码
- 设置A窗口的隔离级别为read uncommitted(读未提交)
set session transaction isolation level read uncommitted;
复制代码
- A、B窗口同时开启手动事务
start transaction;
复制代码
- 在B窗口中插入数据
INSERT INTO T values(4,'牛D');
复制代码
- 在A窗口中进行查询
select * from T;
复制代码
这时如果没有事务之间隔离性,那么B窗口中事务结果就会出现在A窗口中,因为现在是手动事务,在B窗口中执行的操作并没有做任何提交,但是在A窗口中可以查询出来这个就是“脏读(dirty read)”
演示不可重复读
数据准备
create table T1(
ID int,
name varchar(255)
);
INSERT INTO T1(id,name) values(1,'牛A'),(1,'牛B'),(1,'牛C');
复制代码
不可重复读:在一个事务里面,同一条语句,两次查询的结果不一致
- 先在A、B窗口中确认隔离级别
#查询隔离级别
select @@transaction_isolation;
复制代码
- 设置A、B窗口的隔离级别为read committed(读已提交)
set session transaction isolation level read committed;
复制代码
- A、B窗口同时开启手动事务
start transaction;
```·
4. 在B窗口中执行更新操作
````sql
update t1 set name = '不牛' where ID = 1;
复制代码
- 在A窗口中执行操作操作【此时得到是结果(牛A、牛B、牛C)此时避免了脏读】
select * from t1;
复制代码
- 在B窗口中执行了提交操作
COMMIT
复制代码
- 在A窗口中执行操作操作【此时得到结果是(不牛、不牛、不牛)】
select * from t1;
复制代码
执行了两次selec得到结果是不一样【在同一个事务中】,这个效果就是不可重复读
演示幻读
数据准备
create table T2(
ID int PRIMARY KEY, -- 这个列设置为主键
name varchar(255)
);
INSERT INTO t2(id,name) values(1,'牛A'),(2,'牛B'),(3,'牛C');
复制代码
幻读的【错误】理解:幻读事务A执行两次select操作得到不同结果集,即select1得到10条记录,select2得到11条记录,这个操作就是幻读
这里其实并不是幻读,这是不可重复读取的一种,只要在 【读未提交、读已提交】隔离级别下都会出现,而在MySQL默认隔离级别下是不会出现的
幻读的正确理解:并不是说读取获取结果集不同,幻读侧重的方面是某一次select操作得到结果所表现的数据状态无法支撑后续业务操作。更具体一些:select查询之后某记录是否存在,不存在,准备插入数据时,无法执行insert插入提示数据已存在,这效果才是幻读
- 先在A、B窗口中确认隔离级别
#查询隔离级别
select @@transaction_isolation;
复制代码
- 设置A、B窗口的隔离级级别repeatable read、(可重复读)
set session transaction isolation level repeatable read;
复制代码
- A、B窗口同时开启手动事务
start transaction;
复制代码
- 在A窗口先执行一次查询
select * from t2;
复制代码
- 在B窗口中出入一条数据并提交
insert into t2 values(4,'牛D');
COMMIT
复制代码
- 在A执行查询操作【上一次的查询要进行提交 读取到都是插入之前的结果】
select * from t2;
复制代码
- 在A窗口中在插入数据【数据插入是失败的,因为数据已经存在】
insert into t2 values(4,'牛D');
复制代码
演示串行化【就是所有问题都不出现,但是效率极低】
数据准备
create table T3(
ID int PRIMARY KEY, -- 这个列设置为主键
name varchar(255)
);
INSERT INTO t3(id,name) values(1,'牛A'),(2,'牛B'),(3,'牛C');
复制代码
- 先在A、B窗口中确认隔离级别
#查询隔离级别
select @@transaction_isolation;
复制代码
- 设置A、B窗口的隔离级级别serializable (串行化)
set session transaction isolation level serializable ;
复制代码
- A、B窗口同时开启手动事务
start transaction;
复制代码
- 在B窗口中插入数据
insert into t3 values(4,'牛D');
复制代码
- 在A窗口中查询
select * from t3;
复制代码
此时A窗口中是不会出现数据的,只有B窗口提供commit,A窗口才会执行查询出数据
千锋大数据Hadoop全新增强版-先导片