[MySQL基础5] 事务


事务是一组操作的集合,是不可分割的工作单位。事务会把所有的操作作为一个整体像系统提交或撤销操作请求,即这些请求要么同时成功,要么同时失败。


比如说,银行的转账,A给B转账1k,要经历查询A余额——A账户余额减小1k——B账户余额增加1k三步。这三步要么同时成功,要么同时失败,如果在中间的过程业务程序抛异常,那么要回滚到之前的状态。

因此,我们需要手动指定事务的开始、结束和回滚。

1. 事务的基本操作

我们以前面的转账为例子,先建表:

CREATE TABLE account (
    id int PRIMARY KEY AUTO_INCREMENT, 
    name VARCHAR(10) NOT NULL, 
    money int NOT NULL
);

INSERT INTO account VALUES (NULL, 'abc', 2000), (NULL, 'bcd', 2000);

+----+------+-------+
| id | name | money |
+----+------+-------+
|  1 | abc  |  2000 |
|  2 | bcd  |  2000 |
+----+------+-------+

正常来讲,转账会有以下三步:

-- 查询abc账户余额
SELECT money FROM account WHERE name='abc';

-- abc余额减1000
UPDATE account SET money = money - 1000 WHERE name='abc';

-- bcd余额加1000
UPDATE account SET money = money + 1000 WHERE name='bcd';

但如果中间抛出异常,就不会正确完成。在MySQL中,事务都是自动提交的,即上述的每个对数据库增删改的语句执行完毕后都会自动提交,为此,我们需要手动设置事务。

基础操作:

-- 查看 设置事务提交方式
SELECT @@autocommit;

+--------------+
| @@autocommit |
+--------------+
|            1 |
+--------------+


-- 为1说明现在的状态是自动提交,我们可以手动设置为0
SET @@autocommit = 0;  -- 只对当前窗口的命令有效

-- 手动提交事务:
COMMIT;

-- 手动回滚事务:
ROLLBACK;

完整的用法为:

方式一:

-- 设定手动提交
SET @@autocommit = 0;

-- 查询abc账户余额
SELECT money FROM account WHERE name='abc';

-- abc余额减1000
UPDATE account SET money = money - 1000 WHERE name='abc';

-- 故意加一个报错
INSERT INTO account VALUES (123, 123);

-- bcd余额加1000
UPDATE account SET money = money + 1000 WHERE name='bcd';

-- 如果成功则提交
COMMIT;

-- 不成功则回滚, 注意 commit 和 rollback二者运行其一!
ROLLBACK;

方式二:

也可以不设定autocommit,开启事务用start transaction或者是begin:

USE test;

-- 开启事务
START TRANSACTION;

-- abc余额减1000
UPDATE account SET money = money - 1000 WHERE name='abc';

-- 故意加一个报错
INSERT INTO account VALUES (123, 123);

-- bcd余额加1000
UPDATE account SET money = money + 1000 WHERE name='bcd';

-- 如果成功则提交
COMMIT;

-- 不成功则回滚
ROLLBACK;
  • **特别注意:**手动启用提交回滚,只是多了一个反悔的机会,比如上面的例子,在报错之前,实际上数据库abc的余额已经减了1000,而且由于报错,bcd的余额并没有加1000,只不过,报错之后,我们可以手动rollback使得数据不受影响

2. 事务的四大特性ACID

  • 原子性(Atomicity):事务是不可分割的最小操作单元,要么全部成功,要么全部失败。
  • 一致性(Consistency):事务完成时,必须使所有的数据都保持一致状态。
  • 隔离性(lsolation):数据库系统提供的隔离机制,保证事务在不受外部并发操作影响的独立环境下运行。
  • 持久性(Durability):事务一旦提交或回滚,它对数据库中的数据的改变就是永久的。

3. 并发事务问题

如果有多个事务同时操作数据库,可能会出现并发问题:

问题描述
脏读一个事务读到另一个事务还没提交的数据
不可重复读一个事务先后读取同一条记录,但两次读取的数据不同
幻读一个事务按照条件查询数据时,没有对应的数据行,但是在插入数据时,又发现这行数据已经存在,好像出现了幻影。

下面简单说明一下:

  • 脏读

    比如说,事务A对id=1的数据执行了update,但还没有commit,这时事务B对id=1的数据进行查询,查询到的实际上就是已经更改的数据,但这个数据在理论上还不应该被commit

  • 不可重复读

    比如说,事务A对id=1的数据进行了一个查询操作,随后,事务B对id=1的数据进行了更改,**并进行了commit,**随后,事务A再次对id=1的数据查询,这两次查询的数据不一样,就是不可重复读

  • 幻读

    比如说,事务A对id=1的数据进行了一个查询操作,发现没有,随后事务B insert了id=1的数据,事务A发现没有id=1后,会insert id=1的数据,这时会报错,因为id重复了。**如果不可重复读的问题解决,则事务A再去查询id=1时,仍然是没有这个数据的,**但又不能成功插入,就是幻读。

4. 事务的隔离级别

几个隔离级别如下:

隔离级别脏读不可重复读幻读
read uncommitted会发生会发生会发生
read committed不会发生会发生会发生
repeatable read(mysql默认)不会发生不会发生会发生
serializable不会发生不会发生不会发生

**记忆:**看英文字面意思:读未提交的(就会有脏读),读提交的(就不会有脏读),repeatable read(可重复读),serializable(序列化,所以不会有冲突)

语法如下:

-- 查看事务隔离级别
SELECT @@TRANSACTION_ISOLATION;

+-------------------------+
| @@TRANSACTION_ISOLATION |
+-------------------------+
| REPEATABLE-READ         |
+-------------------------+

-- 设置事务隔离级别
SET {SESSION 或 GLOBAL} TRANSACTION ISOLATION LEVEL {READ UNCOMMITED 或 READ COMMITED 或 REPEATABLE READ 或 SERIALIZABLE};

其中,SESSION是在当前对话下设置隔离级别,GLOBAL是设置全局的

例子:

SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;

验证一下隔离级别是否会出现并发事务问题

  • 脏读:

    在事务A, 首先设定隔离级别为read uncommited,随后执行查询:

    mysql> SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
    Query OK, 0 rows affected (0.00 sec)
    
    mysql> select * from account;
    +----+------+-------+
    | id | name | money |
    +----+------+-------+
    |  1 | abc  |  2000 |
    |  2 | bcd  |  2000 |
    +----+------+-------+
    2 rows in set (0.00 sec)
    

    此时,在事务B,我们修改id=1的数据,但是不commit:

    mysql> start transaction;
    Query OK, 0 rows affected (0.00 sec)
    
    mysql> update account set money=1000 where id=1;
    Query OK, 1 row affected (0.00 sec)
    Rows matched: 1  Changed: 1  Warnings: 0
    

    这时,在事务A,我们再次查询,就出现了事务B修改但未commit的数据:

    mysql> select * from account;
    +----+------+-------+
    | id | name | money |
    +----+------+-------+
    |  1 | abc  |  1000 |
    |  2 | bcd  |  2000 |
    +----+------+-------+
    2 rows in set (0.00 sec)
    

    如果我们把事务A的隔离级别调成read committed,再进行上述的更改:

    -- 事务B修改不提交
    mysql> update account set money=3000 where id=1;
    Query OK, 1 row affected (0.00 sec)
    Rows matched: 1  Changed: 1  Warnings: 0
    
    -- 事务A查询
    mysql> select * from account;
    +----+------+-------+
    | id | name | money |
    +----+------+-------+
    |  1 | abc  |  2000 |
    |  2 | bcd  |  2000 |
    +----+------+-------+
    
    -- 事务B提交
    mysql> commit;
    Query OK, 0 rows affected (0.01 sec)
    
    -- 事务A查询
    mysql> select * from account;
    +----+------+-------+
    | id | name | money |
    +----+------+-------+
    |  1 | abc  |  3000 |
    |  2 | bcd  |  2000 |
    +----+------+-------+
    2 rows in set (0.00 sec)
    

    当然,上面的例子也验证了read commited不能解决不可重复读

  • 不可重复读

    -- 事务A
    
    mysql> SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ; 
    Query OK, 0 rows affected (0.00 sec)
    
    mysql> start transaction;
    Query OK, 0 rows affected (0.00 sec)
    
    mysql> select * from account;
    +----+------+-------+
    | id | name | money |
    +----+------+-------+
    |  1 | abc  |  3000 |
    |  2 | bcd  |  2000 |
    +----+------+-------+
    2 rows in set (0.00 sec)
    
    -- 事务B
    mysql> update account set money=4000 where id=1;
    Query OK, 1 row affected (0.01 sec)
    Rows matched: 1  Changed: 1  Warnings: 0
    
    -- 事务A
    mysql> select * from account;
    +----+------+-------+
    | id | name | money |
    +----+------+-------+
    |  1 | abc  |  3000 |
    |  2 | bcd  |  2000 |
    +----+------+-------+
    2 rows in set (0.00 sec)
    

    可以发现,A没有出现不可重复读的问题

  • 幻读

    -- 紧接上面的例子,如果A查询一个id=5的数据
    mysql> select * from account where id=5;
    Empty set (0.00 sec)  -- 查询不到
    
    -- 事务B 插入
    mysql> insert into account values (null, 'ert', 2000);
    Query OK, 1 row affected (0.01 sec)
    
    mysql> select * from account;
    +----+------+-------+
    | id | name | money |
    +----+------+-------+
    |  1 | abc  |  4000 |
    |  2 | bcd  |  2000 |
    |  3 | qwe  |  2000 |
    |  5 | ert  |  2000 |
    +----+------+-------+
    
    -- 事务A 插入
    mysql> insert into account values (5, '123123', 1000);
    ERROR 1062 (23000): Duplicate entry '5' for key 'account.PRIMARY'
    
    -- 不能插入 但是也查询不到 就像幻影
    mysql> select * from account where id=5;
    Empty set (0.00 sec)
    

    我们再试一下最高等级serializable:

    -- 事务A 设定level 目前查询不到id=6的数据
    mysql> set session transaction isolation level serializable;
    Query OK, 0 rows affected (0.00 sec)
    
    mysql> start transaction;
    Query OK, 0 rows affected (0.00 sec)
    
    mysql> select * from account where id=6;
    Empty set (0.00 sec)
    
    -- 事务B
    insert into account values (null, 'asdt', 8000);
    -- 直接卡主了!说明serializable直接把整个表锁住了,不让任何其他的事务更改,根本不会有冲突
    
    -- 事务A 结束
    mysql> rollback;
    Query OK, 0 rows affected (0.00 sec)
    

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值