当MySQL的CURD不加控制,会有什么问题?
以买车票为例
CURD满足什么属性,能解决上述问题?
1.买票的过程得是原子的吧
2.买票互相应该不能影响吧
3.买完票应该要永久有效吧
4.买前,和买后都要是确定的状态吧
因此引出了事务的概念!
什么是事务?
说白了我们为完成某个功能使用的每条sql语句都是事务,有的事务是一条sql语句组成,有的事务是多条sql语句组成,
比如我想让A的钱转给B那我们就要执行两条sql语句,一条把A的余额减少,一条把B的余额增加,这两条加起来才是一个完整的事务
为避免上述类似火车票问题,一个完整的事务,绝对不是简单的 sql 集合,还需要满足如下四个属性:
为什么会出现事务
MySQL 中只有使用了 Innodb 数据库引擎的数据库或表才支持事务!MyISAM 不支持。
事务提交方式(1.自动提交 2.手动提交)
查看事务提交方式
mysql> show variables like 'autocommit';+---------------+-------+| Variable_name | Value |+---------------+-------+| autocommit | ON |+---------------+-------+1 row in set (0.41 sec)
用 SET 来改变 MySQL 的自动提交模式
mysql> SET AUTOCOMMIT=0; #SET AUTOCOMMIT=0 禁止自动提交Query OK, 0 rows affected (0.00 sec)mysql> show variables like 'autocommit';+---------------+-------+| Variable_name | Value |+---------------+-------+| autocommit | OFF |+---------------+-------+1 row in set (0.00 sec)
mysql> SET AUTOCOMMIT=1; #SET AUTOCOMMIT=1 开启自动提交Query OK, 0 rows affected (0.00 sec)mysql> show variables like 'autocommit';+---------------+-------+| Variable_name | Value |+---------------+-------+| autocommit | ON |+---------------+-------+1 row in set (0.01 sec)
事务常见操作方式
创建测试表
正常演示 - 证明事务的开始与回滚
mysql> show variables like 'autocommit'; -- 查看事务是否自动提交。我们故意设置成自动提交,看看该选项是否影响begin+---------------+-------+| Variable_name | Value |+---------------+-------+| autocommit | ON |+---------------+-------+1 row in set (0.00 sec)mysql> start transaction; -- 开始一个事务begin也可以,推荐beginQuery OK, 0 rows affected (0.00 sec)mysql> savepoint save1; -- 创建一个保存点save1Query OK, 0 rows affected (0.00 sec)mysql> insert into account values (1, '张三', 100); -- 插入一条记录Query OK, 1 row affected (0.05 sec)mysql> savepoint save2; -- 创建一个保存点save2Query OK, 0 rows affected (0.01 sec)mysql> insert into account values (2, '李四', 10000); -- 在插入一条记录Query OK, 1 row affected (0.00 sec)mysql> select * from account; -- 两条记录都在了+----+--------+----------+| id | name | blance |+----+--------+----------+| 1 | 张三 | 100.00 || 2 | 李四 | 10000.00 |+----+--------+----------+2 rows in set (0.00 sec)mysql> rollback to save2; -- 回滚到保存点save2Query OK, 0 rows affected (0.03 sec)mysql> select * from account; -- 一条记录没有了+----+--------+--------+| id | name | blance |+----+--------+--------+| 1 | 张三 | 100.00 |+----+--------+--------+1 row in set (0.00 sec)mysql> rollback; -- 直接rollback,回滚在最开始Query OK, 0 rows affected (0.00 sec)mysql> select * from account; -- 所有刚刚的记录没有了Empty set (0.00 sec)
非正常演示1 - 证明未commit,客户端崩溃,MySQL自动会回滚(隔离级别设置为读未提交)
--终端 Amysql> show variables like 'autocommit'; -- 依旧自动提交+---------------+-------+| Variable_name | Value |+---------------+-------+| autocommit | ON |+---------------+-------+1 row in set (0.00 sec)mysql> select * from account; -- 当前表内无数据Empty set (0.00 sec)mysql> begin; -- 开启事务Query OK, 0 rows affected (0.00 sec)mysql> insert into account values (1, '张三', 100); -- 插入记录| Variable_name | Value |+---------------+-------+| autocommit | ON |+---------------+-------+1 row in set (0.00 sec)mysql> begin; --开启事务Query OK, 0 rows affected (0.00 sec)mysql> insert into account values (1, '张三', 100); -- 插入记录Query OK, 1 row affected (0.00 sec)mysql> select * from account; --数据已经存在,但没有commit,此时同时查看终端B+----+--------+--------+| id | name | blance |+----+--------+--------+| 1 | 张三 | 100.00 |+----+--------+--------+1 row in set (0.00 sec)mysql> Aborted -- ctrl + \ 异常终止MySQL--终端Bmysql> select * from account; --终端A崩溃前+----+--------+--------+| id | name | blance |+----+--------+--------+| 1 | 张三 | 100.00 |+----+--------+--------+1 row in set (0.00 sec)mysql> select * from account; --数据自动回滚Empty set (0.00 sec)
非正常演示2 - 证明commit了,客户端崩溃,MySQL数据不会再受影响,已经持久化
--终端 Amysql> show variables like 'autocommit'; -- 依旧自动提交+---------------+-------+| Variable_name | Value |+---------------+-------+| autocommit | ON |+---------------+-------+1 row in set (0.00 sec)mysql> select * from account; -- 当前表内无数据Empty set (0.00 sec)mysql> begin; -- 开启事务Query OK, 0 rows affected (0.00 sec)mysql> insert into account values (1, '张三', 100); -- 插入记录Query OK, 1 row affected (0.00 sec)mysql> commit; --提交事务Query OK, 0 rows affected (0.04 sec)mysql> Aborted -- ctrl + \ 异常终止MySQL--终端 Bmysql> select * from account; --数据存在了,所以commit的作用是将数据持久化到MySQL中+----+--------+--------+| id | name | blance |+----+--------+--------+| 1 | 张三 | 100.00 |+----+--------+--------+1 row in set (0.00 sec)
非正常演示3 - 对比试验。证明begin操作会自动更改提交方式,不会受MySQL是否自动提交影响
-- 终端 Amysql> select *from account; --查看历史数据+----+--------+--------+| id | name | blance |+----+--------+--------+| 1 | 张三 | 100.00 |+----+--------+--------+1 row in set (0.00 sec)mysql> show variables like 'autocommit'; --查看事务提交方式+---------------+-------+| Variable_name | Value |+---------------+-------+| autocommit | ON |+---------------+-------+1 row in set (0.00 sec)mysql> set autocommit=0; --关闭自动提交Query OK, 0 rows affected (0.00 sec)mysql> show variables like 'autocommit'; --查看关闭之后结果+---------------+-------+| Variable_name | Value |+---------------+-------+| autocommit | OFF |+---------------+-------+1 row in set (0.00 sec)mysql> begin; --开启事务Query OK, 0 rows affected (0.00 sec)mysql> insert into account values (2, '李四', 10000); --插入记录Query OK, 1 row affected (0.00 sec)mysql> select *from account; --查看插入记录,同时查看终端B+----+--------+----------+| id | name | blance |+----+--------+----------+| 1 | 张三 | 100.00 || 2 | 李四 | 10000.00 |+----+--------+----------+2 rows in set (0.00 sec)mysql> Aborted --再次异常终止-- 终端Bmysql> select * from account; --终端A崩溃前+----+--------+----------+| id | name | blance |+----+--------+----------+| 1 | 张三 | 100.00 || 2 | 李四 | 10000.00 |+----+--------+----------+2 rows in set (0.00 sec)mysql> select * from account; --终端A崩溃后,自动回滚+----+--------+--------+| id | name | blance |+----+--------+--------+| 1 | 张三 | 100.00 |+----+--------+--------+1 row in set (0.00 sec)
非正常演示4 - 证明单条 SQL 与事务的关系
--实验一-- 终端Amysql> select * from account;+----+--------+--------+| id | name | blance |+----+--------+--------+| 1 | 张三 | 100.00 |+----+--------+--------+1 row in set (0.00 sec)mysql> show variables like 'autocommit';+---------------+-------+| Variable_name | Value |+---------------+-------+| autocommit | ON |+---------------+-------+1 row in set (0.00 sec)mysql> set autocommit=0; --关闭自动提交Query OK, 0 rows affected (0.00 sec)mysql> insert into account values (2, '李四', 10000); --插入记录Query OK, 1 row affected (0.00 sec)mysql> select *from account; --查看结果,已经插入。此时可以在查看终端B+----+--------+----------+| id | name | blance |+----+--------+----------+| 1 | 张三 | 100.00 || 2 | 李四 | 10000.00 |+----+--------+----------+2 rows in set (0.00 sec)mysql> ^DBye --ctrl + \ or ctrl + d,终止终端--终端Bmysql> select * from account; --终端A崩溃前+----+--------+----------+| id | name | blance |+----+--------+----------+| 1 | 张三 | 100.00 || 2 | 李四 | 10000.00 |+----+--------+----------+2 rows in set (0.00 sec)mysql> select * from account; --终端A崩溃后+----+--------+--------+| id | name | blance |+----+--------+--------+| 1 | 张三 | 100.00 |+----+--------+--------+1 row in set (0.00 sec)-- 实验二--终端Amysql> show variables like 'autocommit'; --开启默认提交+---------------+-------+| Variable_name | Value |+---------------+-------+| autocommit | ON |+---------------+-------+1 row in set (0.00 sec)mysql> select * from account;+----+--------+--------+| id | name | blance |+----+--------+--------+| 1 | 张三 | 100.00 |+----+--------+--------+1 row in set (0.00 sec)mysql> insert into account values (2, '李四', 10000);Query OK, 1 row affected (0.01 sec)mysql> select *from account; --数据已经插入+----+--------+----------+| id | name | blance |+----+--------+----------+| 1 | 张三 | 100.00 || 2 | 李四 | 10000.00 |+----+--------+----------+2 rows in set (0.00 sec)mysql> Aborted --异常终止--终端Bmysql> select * from account; --终端A崩溃前+----+--------+----------+| id | name | blance |+----+--------+----------+| 1 | 张三 | 100.00 || 2 | 李四 | 10000.00 |+----+--------+----------+2 rows in set (0.00 sec)mysql> select * from account; --终端A崩溃后,并不影响,已经持久化。autocommit起作用+----+--------+----------+| id | name | blance |+----+--------+----------+| 1 | 张三 | 100.00 || 2 | 李四 | 10000.00 |+----+--------+----------+2 rows in set (0.00 sec)
总结上述实验(重要)证明了事务的 原子性、持久性
事务操作注意事项
1.如果没有设置保存点,也可以回滚,只能回滚到事务的开始。直接使用 rollback(前提是事务还没有提交)
2.如果一个事务被提交了(commit),则不可以回退(rollback)
3.可以选择回退到哪个保存点
4.InnoDB 支持事务, MyISAM 不支持事务
5.开始事务可以使 start transaction 或者 begin
事务的隔离性(难点)
隔离级别(重要)
查看与设置隔离性
隔离级别个人总结(很重要!!)
为啥隔离性要有四个级别?
一致性
隔离性的底层( 难点)
数据库并发的场景有三种:
读-读 :不存在任何问题,也不需要并发控制
读-写 :有线程安全问题,可能会造成事务隔离性问题,可能遇到脏读,幻读,不可重复读
写-写 :有线程安全问题,可能会存在更新丢失问题,比如第一类更新丢失,第二类更新丢失
这里面最重要的环节就是读-写因为其他两种很容易控制安全问题,难点在于读-写
读-写
多版本并发控制( MVCC )是一种用来解决 读-写冲突 的无锁并发控制。
为事务分配单向增长的事务ID,为每个修改保存一个版本,版本与事务ID关联,读操作只读该事务开始前的数据库的快照。
所以 MVCC 可以为数据库解决以下问题(重要)
1.在并发读写数据库时,可以做到在读操作时不用阻塞写操作,写操作也不用阻塞读操作,提高了数据库并发读写的性能
2.同时还可以解决脏读,幻读,不可重复读等事务隔离问题
理解 MVCC 需要知道三个前提知识:
3个记录隐藏字段
undo 日志
Read View
3个记录隐藏列字段
undo 日志(说白了就是 MySQL 中的一段内存缓冲区,用来保存日志数据的就行,比如我的CURD记录)
模拟 MVCC
这样,我们就有了一个基于链表记录的历史版本链。所谓的回滚,无非就是用历史数据,覆盖当前数据。
上面的一个一个版本,我们可以称之为一个一个的快照。
当前读与快照读
select读取,是读取最新的版本呢?还是读取历史版本?
1.当前读:读取最新的记录,就是当前读。增删改,都叫做当前读,select也有可能当前读,比如:select lock in sharemode(共享锁)
2.快照读:读取历史版本(一般而言),就叫做快照读。