文章目录
1.事务是什么
在MySQL中,事务就是一组SQL语句
,是一个操作单元,事务里的操作是不可分割的。
说人话就是,事务里的SQL语句要么都执行,要么都不执行,这些操作是一个整体。
比如:
START TRANSACTION; #开启事务
SELECT * FROM student;
UPDATE student SET name = '李华' WHERE id = 3;
COMMIT; #提交事务
其中SELECT * FROM student
和UPDATE student SET name = '李华' WHERE id = 3
两条语句就是事务了。
START TRANSACTION
是开始事务的语句,TRANSACTION(transaction)
是事务的意思。
COMMIT
是提交事务的语句,提交了事务,服务器就把数据写入到磁盘中了,数据就永久更改了。
而且服务器要么执行完这些语句,要么一条语句都不执行。
那事务的作用体现在哪里呢?
银行转账就是事务的一个应用场景:
要保证两个人在转账中,一个人扣了费,一个人收了钱。
就得使用事务来处理,将一个人中的余额扣掉并且将另一个人的余额增加,
这两个操作是一个整体,是一个事务。
当这个事务执行到一半时,如果服务器宕机,也不会造影响。
因为服务器有日志信息,日志信息存在服务器硬盘里,
下次通电后,会根据日志信息选择回滚到事务之前的状态还是继续执行事务。
如果不用事务的话,还是以上两个操作。
服务器先执行扣除一个人余额操作,如果执行完了之后,突然断电!!!
这时候,增加余额的操作没有执行到,另一个人的账户是没有变化的。
这时候问题就出现了,就算通了电,一个人扣了费,另一个人账户余额还是没增加。
因为这两个操作不是一个事务,日志里没信息,没法回到之前的状态。
2.使用事务
2.1开始和提交事务
开始事务语句:begin
或者 start transaction
提交事务语句:commit
现有user表:
id 用户id
name 用户名字
age 用户年龄
balance 用户余额
+---------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+---------+-------------+------+-----+---------+----------------+
| id | int | NO | PRI | NULL | auto_increment |
| name | varchar(20) | YES | | NULL | |
| age | int | YES | | NULL | |
| balance | int | YES | | NULL | |
+---------+-------------+------+-----+---------+----------------+
下面是数据:
+----+------+-----+---------+
| id | name | age | balance |
+----+------+-----+---------+
| 1 | 小明 | 20 | 1500 |
| 2 | 小红 | 21 | 2000 |
| 3 | 小蓝 | 22 | 1800 |
+----+------+-----+---------+
现在模拟银行转账的情况:
将小明的账户余额减少500块,将小红的余额增减500块。
START TRANSACTION;
UPDATE user SET balance = balance - 500 WHERE name = '小明';
UPDATE user SET balance = balance + 500 WHERE name = '小红';
COMMIT;
事务执行后的结果:
+----+------+-----+---------+
| id | name | age | balance |
+----+------+-----+---------+
| 1 | 小明 | 20 | 1000 |
| 2 | 小红 | 21 | 2500 |
| 3 | 小蓝 | 22 | 1800 |
+----+------+-----+---------+
2.2rollback回滚
使用rollback
关键字可以回到事务开始之前的状态。
如果你发现事务里逻辑有问题,可以回滚到事务之前的状态。
这可能比较抽象,看下面的例子,就会懂怎么用事务了。
当前表数据:
+----+------+-----+---------+
| id | name | age | balance |
+----+------+-----+---------+
| 1 | 小明 | 20 | 1000 |
| 2 | 小红 | 21 | 2500 |
| 3 | 小蓝 | 22 | 1800 |
+----+------+-----+---------+
现将小蓝的余额扣除300,小红的余额增加300。
但中途发现只需要将小蓝的余额扣除200就够了,
而且事务都执行了一半了,事务里的数据都改了,怎么办呢?
这时回滚(rollback)
就派上用场了。
START TRANSACTION;
#操作
UPDATE user SET balance = balance - 300 WHERE name = '小蓝';
UPDATE user SET balance = balance + 300 WHERE name = '小红';
#查表
SELECT * FROM user;
#发现问题,回滚
ROLLBACK;
#再次查表
SELECT * FROM user;
结果:
回滚之前
+----+------+-----+---------+
| id | name | age | balance |
+----+------+-----+---------+
| 1 | 小明 | 20 | 1000 |
| 2 | 小红 | 21 | 2800 |
| 3 | 小蓝 | 22 | 1500 |
+----+------+-----+---------+
回滚之后
+----+------+-----+---------+
| id | name | age | balance |
+----+------+-----+---------+
| 1 | 小明 | 20 | 1000 |
| 2 | 小红 | 21 | 2500 |
| 3 | 小蓝 | 22 | 1800 |
+----+------+-----+---------+
在回滚之后,数据又恢复原样了,之后可以再开启事务进行操作。
2.3savepoint保存点
其实,如果在事务里发现了逻辑错误的话,但是其中有一些逻辑是正确的呀,
直接回滚那不是麻烦了吗,又要重新开启一个事务,重新写一遍代码???😂
当然了,我们亲爱的MySQL设计者肯定不会这样做。
所以,在MySQL中,可以回到保存点时的状态而不用回滚事务。
MySQL中有savelpoint
关键字来定义保存点。
语法如下:
savepoint name;
比如定义一个名为point1的保存点:
savepoint point1;
回滚到保存点的语法还不仅仅是直接rollback
这么简单
下面时回滚到保存点的语法:
rollback to savepoint 保存点名
比如:
rollback to savepoint point1;
注意:保存点只在事务内使用
光说不练假把式,下面就是一个案例:
#开启事务
START TRANSACTION;
#修改数据
UPDATE user SET balance = balance - 300 WHERE name = '小蓝';
UPDATE user SET balance = balance + 300 WHERE name = '小红';
#设置保存点
SAVEPOINT my_point;
#删除小蓝的数据
DELETE FROM user WHERE name = '小蓝';
#查表
SELECT * FROM user;
#回滚到保存点时的状态
ROLLBACK TO SAVEPOINT my_point;
#提交事务
COMMIT;
#再次查表
SELECT * FROM user;
执行结果:
执行事务前
+----+------+-----+---------+
| id | name | age | balance |
+----+------+-----+---------+
| 1 | 小明 | 20 | 1000 |
| 2 | 小红 | 21 | 2500 |
| 3 | 小蓝 | 22 | 1800 |
+----+------+-----+---------+
执行事务后
+----+------+-----+---------+
| id | name | age | balance |
+----+------+-----+---------+
| 1 | 小明 | 20 | 1000 |
| 2 | 小红 | 21 | 2800 |
| 3 | 小蓝 | 22 | 1500 |
+----+------+-----+---------+
可以看到小蓝的数据并没有被删除,因为回滚到了删除之前的状态。
总结:
在事务中设置保存点是个好习惯,可以让你的程序容错率更高。
特别是在进行删除数据这种危险的操作时,设置保存点是非常有必要的。
3.事务的四大特性
当你把前面的内容都看过了,下面这几种性质就不难理解了。
事务的四大特性还是要掌握的,面试中也是经常问的。
3.1原子性(Atomicity)
事务中的全部操作是一个整体,这些操作要么都执行,要么都不执行。
这些操作是一个单元,就像原子一样不可分割,这就是所谓的原子性(Atomicity)。
3.2一致性(Consistency)
一致性(Consistency)讲的就是,最终表里的数据要么跟执行事务之前的数据保持一致,
要么跟执行事务之后的数据保持一致。
比如,张三有100元,李四有200元。
现在要转账,其实也就是一个事务,张三转给李四100块,李四收到一百块。
如果转账成功,即事务执行完毕,结果为张三剩0元,李四剩300元。
如果转账失败,即事务没执行完毕,结果为张三剩100元,李四剩200元。
不会出现张三剩0元,李四剩200元的情况,这就是事务的一致性。
3.3持久性(Durability)
持久性(Durability)就更好理解了,提交事务后,服务器就把数据写入到磁盘中了,对数据的更改是持久性的。
3.4隔离性(Isolation)
隔离性(Isolation)是针对数据资源的并发访问,当有多个事务操作同一个表时,不能被其他事务所干扰
这就是隔离性。
4.事务的四个隔离级别
在说事务的四个隔离级别之前,必须先说说脏读
,不可重复读
,幻读
-
脏读
有事务A和事务B,事务A对数据进行了修改,然后事务B读取了事务A里未提交的数据
但事务A进行了回滚,事务B读取到的数据就是无效的,这就是脏读。
-
不可重复读
事务A和事务B,事务B先读取了一个表中的数据,然后事务A对这个表的数据进行了修改
事务B之后读取到的数据跟先前读取的数据不一样,这就是不可重复读。
比如:在一个事务中刚开始读一个表时,有一项数据为30,可能由于有其他事务提交了,后来又变成了40。 -
幻读
幻读是针对插入操作来说的,事务B两次读取相同范围内的数据,但事务A插入了一些数据,导致事务B读取数据
的时候,又多出来一些数据。
比如:
在事务B中,你想在学生表中查18岁到20岁的学生,然后进行了一次查询。
事务A在表中添加了几个18岁到20岁的学生,然后事务B再次查询,发现多了几条记录
而且这几条记录还可能跟之前的纪律一样,就很魔幻,这就是幻读。
注意:不可重复读针对的是数据的修改,而幻读针对的是数据的增加。
4.1读未提交
读未提交
(read uncommited
),指在一个事务中可以读取到另一个未提交事务的数据。
比如:
事务A修改数据修改数据。
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
事务B读取事务A中未提交的数据
读未提交会导致脏读,不可重复读和脏读。
4.2读已提交
读已提交
(read commited
)
指的是一个事务中,不能读取还未提交事务的数据,只能读取已提交事务的数据。
事务A先查询未修改的数据
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
事务B修改数据后,提交事务。
在查询一次已修改的数据。
读已提交解决了脏读的问题,但是会导致不可重复读和幻读。
4.3可重复读
可重复读
(repeatable read
),在一个事务中,读取一个范围的数据时,这些数据不会因为其他事务的提交而发生改变。
这也是MySQL的默认隔离级别。
事务A在事务B提交之前先查询了数据,
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
事务B修改了数据,并把事务提交了。
事务A在事务B提交之后又查询了数据。
可重复读不会导致脏读和不可重复读,但是幻读的问题仍然未解决。
4.4序列化
序列化解决了脏读,幻读,和不可重复读,相当于一个事务一个事务地执行。
这里不在做演示。
各个隔离级别会导致的问题:
脏读 | 可重复读 | 幻读 | |
---|---|---|---|
read uncommited | √ | √ | √ |
read commited | × | √ | √ |
repeatable read | × | × | √ |
serializable | × | × | × |