一步步走进Sql中的事务及事务隔离级别
1.事务定义
事务是按照逻辑执行的一组任务序列,这一组任务序列要么都执行,要么都不执行。
事务是优先基于会话配置的的(session),其次是基于全局配置的(global)。
2.事务的提交和回滚
提交commited,即一组任务序列,正常执行后,提交给数据库。
回滚rollback,即一组任务序列中,无论哪一个任务环节出现异常,都会把所有的任务序列全部恢复到原始状态。
事务的提交可以分为手动提交和自动提交。
3.查看和设置提交方式
session和global中都有变量autocommit,
autocommit为1的时候自动提交,
autocommit为0的时候手动提交。
select @@session.autocommit;
查看全局配置中的默认提交方式
select @@global.autocommit;
关闭当前会话的自动提交,改为手动提交。
set @@session.autocommit=0;
4.创建数据库,测试提交方式。
设计一个简单的数据库,往数据库中加一个表格。
表格中分别是,id(自增),用户名,用户余额。
USE transdemo; DROP TABLE IF EXISTS users; CREATE TABLE users ( uid INT(11) AUTO_INCREMENT, uname VARCHAR(10) DEFAULT NULL, money INT(11) DEFAULT NULL, PRIMARY KEY (uid) )
插入一条数据
INSERT INTO users VALUES(NULL,'zhanghao',1000)
mysql自增字段(id)插入时,用null即可。
结果:
自动提交:
手动提交:
为了测试手动提交,打开另外一个命令窗口并连接mysql,即开启了另外一个会话窗口B(Windows PowerShell)。
这是原本的会话窗口A(Windows PowerShell),关闭自动提交,改为手动提交。
此时A中会话看似一切正常。
在B中,再次读取users表。
问题出来了,zhanghao用户的money还是900,表格数据并没有被更新。
此时,我们在A会话(原本会话)中,提交commit;
此时,我们在B会话(新开的会话窗口)再次查询,
数据被更新。
总结:自动提交时,无需commit即可自动更新改变数据;手动提交时,必须要commit,才能更新改变数据库中的数据。
5.事务隔离级别
事务中的隔离级别共有四种:
1.读未提交 read uncommited
2.读已提交 read commited
3.可重复读 repeatable read
4.串行化 serializable
首先查看当前会话的事务隔离级别
select @@session.transaction_isolation; -- 8.0以下版本 -- select @@session.tx_isolation;
再看一下全局的
select @@global.transaction_isolation; -- 8.0以下版本 -- select @@global.tx_isolation;
从上面测试可以看出,mysql的事务隔离级别,默认是repeatable-read,即可重复读。
我们可以修改mysql的事务隔离级别,仍然基于当前会话(session)
-- 将当前会话的事务隔离级别设置为 : read uncommitted,即读未提交 set session transaction isolation level read uncommitted; -- 查看当前会话的事务隔离级别 select @@session.transaction_isolation;
此时,事务的隔离级别变为的read uncommitted即读未提交。
5.1 读未提交
两个连接窗口AB(Windows PowerShell),测试读未提交。
按上文方法,将A连接设置为读未提交。B连接不设置隔离级别,采用默认。
首先在B连接中,开始事务,并将zhanghao用户的money减100。
此时,在事务中,可以正常的显示zhanghao的money为900,成功被减去100。
但是在数据库中查看
zhanghao的money仍为1000。
这也不奇怪,因为B连接的事务并没有被提交。
但是,在B连接中的事务没有提交,数据库users表中zhanghao的money为1000的时候,
在A会话窗口中(已被设置为读未提交),开始事务并读取zhanghao的money时,却读到了未提交的900。
这就是读未提交。
导致的问题:脏读。即另外一个事务尚未提交的时候(数据库中的数据没被改变的时候),在A窗口事务中,已经读到了未提交的数据。
5.2 读已提交
仍然开始两个Windows PowerShell窗口分别连接mysql,实现两个会话。
首先在A窗口中,设置事务隔离级别为读已提交。
set session transaction isolation level read committed;
B窗口采用默认的隔离级别,不做任何改动即可。
数据库初始值
在B窗口中,开启事务,并把money减去100,不提交。
然后,在A窗口中,开始事务并查询users表,不提交
然后,在B窗口中,提交。
然后,在A窗口中,再次查询users表。
本次流程A窗口的完整运行结果如下
第一次查询的时候,由于B窗口的update未提交,所以为money1000,
B窗口提交之后,A窗口再次查询的时候,money变为了900。
这就是读已提交。
导致的问题:不可重复读。从上图可以明显看出,在一个事务中,两次读到的结果不相同。
5.3 可重复读
仍然开始两个Windows PowerShell窗口分别连接mysql,实现两个会话。
首先在A窗口中,设置事务隔离级别为可重复读。
B窗口采用默认的隔离级别,不做任何改动即可。
数据库初始值
首先在B窗口中,开启事务。并将zhanghao的money减去100,不提交。
然后,在A窗口中,开启事务并查看users表,不提交。
然后B窗口提交。
然后再次在A窗口中查看users表。
发现,两次查询的结果,是一致的。即使B窗口已经将money改为900并提交,
但是在A窗口的一个事务中,两次查询结果是一致的
这就是可重复读,即在一个事务中,重复读数据时不会变的。
但是这就完美了吗?
此时A窗口仍未提交,B窗口已提交。
在B窗口,再次开启一个事务,并且添加一条用户,lisi,500。
此时,A窗口再次查询users表时,诡异的多了一条lisi的数据,
也就是像魅影一样的出现了,这就是幻读。
mysql8中,已经对幻读增加了防范措施,低版本mysql仍存在幻读问题。
可重复读导致的问题:幻读
5.4 串行化
同上,开启A、B两个Windows PowerShell窗口连接mysql,
在AB窗口中,都设置事务隔离方式为串行化
set session transaction isolation level seriable;
首先在A窗口中开启事务,并查询users表
然后再B窗口中开启事务,并将zhanghao的money减去100,却发现窗口卡住了。
回到A窗口,提交事务
此时B窗口,更新操作顺利完成
这就是串行化,
即当A连接中的事务访问一个表的时候,
B连接中的事务想要修改表中的数据,
B连接就会卡住,等待A连接中的事务提交或回滚后,才继续执行B连接中的修改事务。
即谁先执行的事务,必须等这个事务执行完,才能执行另一个事务。
缺点:性能非常低。