一、什么是事务

  • 不可分割的操作,假设该操作有ABCD四个步骤组成,则ABCD四个步骤都成功完成,则认为事务成功;ABCD任意一个步骤失败,则事务失败
  • 每条sql语句都是一个事务
  • 事务只对DML语句有效,对DQL语句无效

二、事务的ACID

  • 原子性:指事务包含的所有操作要么全部成功,要么全部失败回滚
  • 一致性:指事务必须使数据库从一个一致性状态换到另一个一致性状态,即事务执行之前和之后都必须处于一致性状态;一个商品出库,仓库减1,对应用户的购物车加1
  • 隔离性:隔离性是当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务干扰,多个并发事务之间要相互隔离
  • 持久性:指一个事务一旦提交了,就不能再回滚了,已经把数据保存到数据库当中了。

三、事务的使用

开启事务:start transaction;
提交事务:commit 所有语句全部执行完毕,没有发生异常,提交事务,更新到数据库当中。
回滚事务:rollback 如果遇到突发情况,撤销执行的 sql 语句。

1.提交事务

建两张表,模拟两个账户:

  1. create table zs_account(
  2. name VARCHAR(50),
  3. money DECIMAL
  4. );
  5. create table ls_account(
  6. name VARCHAR(50),
  7. money DECIMAL
  8. );

mysql事务 - 图1

cmd:
C:\Users\gd>mysql -u root -p
Enter password: ****
mysql> start transaction; --开启一个事务
Query OK, 0 rows affected (0.00 sec)
mysql> update zs_account set money = money - 2000;  --减 2000
Query OK, 1 row affected (0.07 sec)
Rows matched: 1  Changed: 1  Warnings: 0
mysql> update ls_account set money = money + 2000; -- 加 2000
Query OK, 1 row affected (0.01 sec)
Rows matched: 1  Changed: 1  Warnings: 0

此时,查询数据:
mysql事务 - 图2
数据库中数据还没有变化

mysql> commit; -- commit 后,数据库数据才会更改
Query OK, 0 rows affected (0.07 sec)

mysql事务 - 图3
反应了数据的隔离性,即,commit 之前,事务未结束,此时从数据库查询,数据库也没有变化。

2.回滚事务

数据恢复为:
zs_account money = 5000
ls_account money = 1000

mysql> start transaction; --开启一个事务
Query OK, 0 rows affected (0.00 sec)
mysql> update zs_account set money = money - 2000; --减 2000
Query OK, 1 row affected (0.04 sec)
Rows matched: 1  Changed: 1  Warnings: 0
mysql> select * from zs_account;  -- zs 变3000了,只是会话中变了,实际数据库中数据依然未改,还是5000
+------+-------+
| name | money |
+------+-------+
| 张三 |  3000 |
+------+-------+
1 row in set (0.05 sec)
mysql> update ls_account set money = money + 2000;  --加 2000
Query OK, 1 row affected (0.07 sec)
Rows matched: 1  Changed: 1  Warnings: 0
mysql> select * from ls_account; -- ls 变 3000了,但是实际数据库中没变化,事务还未提交
+------+-------+
| name | money |
+------+-------+
| 李四 |  3000 |
+------+-------+
1 row in set (0.00 sec)

然后这个时候断电了
我去,假如断电了,我现在要回滚

mysql> rollback; --回滚
Query OK, 0 rows affected (0.00 sec)

此时再在终端查一下:

mysql> select * from zs_account; --恢复为之前的数据了,当然此时,数据的数据也没有变化
+------+-------+
| name | money |
+------+-------+
| 张三 |  5000 |
+------+-------+
1 row in set (0.00 sec)
mysql> select * from ls_account;
+------+-------+
| name | money |
+------+-------+
| 李四 |  1000 |
+------+-------+
1 row in set (0.00 sec)

再附一张,“蚂蚁小强 ” 大神的图 :
执行流程图
mysql事务 - 图4


四、事务的并发问题

1.脏读

  • 老板要给程序员发工资,程序员的工资是3.6万/月。但是发工资时老板不小心按错了数字,按成3.9万/月,该钱已经打到程序员的户口,但是事务还没有提交,就在这时,程序员去查看自己这个月的工资,发现比往常多了3千元,以为涨工资了非常高业。但是老板及时发现了不对,马上回滚差点就提交了的事务,将数字改成3.6万再提交
  • 实际程序员这个月的工资还是3.6万,但是程序员看到的是3.9万。他看到的是老板还没提交事务时的数据。这就是脏读。
  • 解决办法:Read committed!读提交,能解决脏读问题

2.不可重复读

  • 程序员拿着工资卡(卡里当然是只有3.6万) ,当他买单时(程序员事务开启) ,收费系统事先检测到他的卡里有3.6万,就在这个时候!!程序员的妻子要把钱全部转出充当家用,并提交。当收费系统准备扣款时,再检测卡里的金额,发现已经没钱了。程序员就会很郁闷,明明卡里是有钱的…
  • 一个事务范围内两个相同的查询却返回了不同数据,这就是不可重复读
  • 解决办法: Repeatable read

3.重复读

  • 程序员拿着工资卡(卡里还是有3.6万) ,当他买单时(事务开启,不允许其他事务的 UPDATE修改操作),收费系统事先检测到他的卡里有3.6万,这个时候他的妻子不能转出金额了。接下来收费系统就可以扣款了。

4.幻读

  • 程序员某一天去消费,花了2千元,然后他的妻子去查看他今天的消费记录 (,妻子事务开启) ,看到确实是花了2千元,就在这个时候,程序员花了1万买了一部电脑,即新增INSERT了一条消费记录,并提交。当妻子打印程序员的消费记录清单时(妻子事务提交) ,发现花了1.2万元,似乎出现了幻觉,这就是幻读。
  • 解决办法: Serializable,但是这种事务隔离级别效率低下,比较耗数据库性能,一般不使用。

5.对应关系

附上”蚂蚁”大神的图:
mysql事务 - 图5
mysql 默认使用的是 可重复读(repeatable-read)
另外:
查看隔离级别: select @@global.tx_isolation,@@tx_isolation;
设置全局隔离级别: set global transaction isolation level read committed;
设置当前会话隔离级别: set session transaction isolation level read committed;