目录
什么是事务?
事务的特性
为什么要有事务
事务的版本支持
事务的提交方式
事务的基本操作
启动事务
创建回滚点
回滚
正常提交
autocommit 和 事务的关系
自动提交
手动提交
单条 sql 与 事务的区别
事务总结
事务的注意事项
在谈事务之前,我们先谈一下事务是什么:
事务实际上就是一组 sql 语句,而这一组 sql 语句可能有逻辑关系,在执行中这一组 sql 要么去做全部执行成功,要么失败,这一组 sql 是一个整体。mysql 提供一种机制,保证我们达到这种效果。事务还规定不同的客户端看到的数据是不同的。
上面是对事务的一个简单的介绍,由于事务是由一组 sql 组成的,所以如果执行到中间失败了,那么就会回滚到最开始,所以事务绝对不仅仅是一组 sql 组成的, 事务还需要满足下面的特性:
原子性:一个事务中的所有操作,要么全部执行完成,要么一个都不执行,如果执行到中间失败,那么就会回滚到最开始的状态,像从未发生一样,而这就是原子性。
一致性:在事务开始之前和结束之后,数据库的完整性没有被破坏,并且写入的数据完全复合预设的结果。
隔离性:数据库允许多个事务同时并发的读写和修改数据,隔离性可以防止多个事务并发执行,由于交叉执行而导致数据不一致。隔离性也是分等级的包括,读未提交(read uncommited)、读提交(read commit)、可重复读(repeatable read)和 串行化(serializable)。
持久性:事务处理结束后,对数据的修改是永久的,即使系统故障也不会有问题。
上面就是事务的特性,但是这里先简单了解一下,后面会讲到。
上面既然介绍完了什么是事务,那么现在了解一下为什么要有事务。
假设现在在买票,现在就剩下最后一张票了,由于 mysql 是多线程的,并且 mysql 也支持多用户访问,所以在买最后一张票时可能会发生将一张票出售两次的问题,所以为了解决这种问题,mysql 就有了事务,而事务也并不是mysql 一开始就有的,而是事务可以帮助用户可以在编写程序的时候忽略掉这些问题。
在 mysql 中并不是都支持事务,而是只有 innodb 引擎支持事务,下面可以看一下:
mysql> show engines\G
*************************** 1. row ***************************
Engine: InnoDB
Support: DEFAULT
Comment: Supports transactions, row-level locking, and foreign keys
Transactions: YES
XA: YES
Savepoints: YES
*************************** 5. row ***************************
Engine: MyISAM
Support: YES
Comment: MyISAM storage engine
Transactions: NO
XA: NO
Savepoints: NO
这里只看一下这两个,这两个时常用的,而这里看到 innodb 在 transaction 那么显示 yes 表示支持事务,而 myisam 不支持事务。
事务的提交方式通常由两种:
自动提交
手动提交
查看事务提交方式
mysql> show variables like 'autocommit';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit | ON |
+---------------+-------+
1 row in set (0.01 sec)
这里看到默认是自动提交。
设置事务提交方式
设置 autocommit 为 0 表示手动提交。
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.01 sec)
设置 autocommit 为 1 表示自动提交。
mysql> 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> select @@tx_isolation;
+-----------------+
| @@tx_isolation |
+-----------------+
| REPEATABLE-READ |
+-----------------+
1 row in set, 1 warning (0.00 sec)
当前的隔离级别是可重复读,下面修改事务隔离级别然后重新启动mysql
mysql> set global transaction isolation level read uncommitted;
Query OK, 0 rows affected (0.00 sec)
mysql> select @@tx_isolation;
+------------------+
| @@tx_isolation |
+------------------+
| READ-UNCOMMITTED |
+------------------+
1 row in set, 1 warning (0.00 sec)
这里设置成功了,这里设置方法后面也会介绍。
将前面的准备工作做好后,我们开始试验
下面创建一个 account 表
mysql> create table account(
-> id int primary key,
-> name varchar(12),
-> balance decimal(10,2)
-> );
Query OK, 0 rows affected (0.01 sec)
mysql> desc account;
+---------+---------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+---------+---------------+------+-----+---------+-------+
| id | int(11) | NO | PRI | NULL | |
| name | varchar(12) | YES | | NULL | |
| balance | decimal(10,2) | YES | | NULL | |
+---------+---------------+------+-----+---------+-------+
3 rows in set (0.01 sec)
创建好后,开始测试事务
下面首先开始一个事务,开启一个事务由两种方法
begin
start transaction
下面打开两个终端演示一下
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
我们下面的试验是在一个终端插入,另一个终端查询。
首先在没插入数据之前创建一个回滚点
savepoint point_name;
mysql> savepoint save1;
Query OK, 0 rows affected (0.00 sec)
下面插入数据
mysql> insert into account values(1, '张三', 1234.99);
Query OK, 1 row affected (0.00 sec)
插入数据后,我们在另一个启动的 mysql 事务中查询
mysql> select * from account;
+----+--------+---------+
| id | name | balance |
+----+--------+---------+
| 1 | 张三 | 1234.99 |
+----+--------+---------+
1 row in set (0.00 sec)
查询到了刚才的数据,下面我们继续创建一个回滚点
mysql> savepoint save2;
Query OK, 0 rows affected (0.00 sec)
创建好后继续插入数据
mysql> insert into account values(2, '李四', 77526.1);
Query OK, 1 row affected (0.00 sec)
继续查询
mysql> select * from account;
+----+--------+----------+
| id | name | balance |
+----+--------+----------+
| 1 | 张三 | 1234.99 |
| 2 | 李四 | 77526.10 |
+----+--------+----------+
2 rows in set (0.00 sec)
我们继续插入数据
mysql> insert into account values(3, '王五', 7191.1);
Query OK, 1 row affected (0.00 sec)
查询
mysql> select * from account;
+----+--------+----------+
| id | name | balance |
+----+--------+----------+
| 1 | 张三 | 1234.99 |
| 2 | 李四 | 77526.10 |
| 3 | 王五 | 7191.10 |
+----+--------+----------+
3 rows in set (0.00 sec)
roolback to point_name;
下面回滚到 save2
mysql> rollback to save2;
Query OK, 0 rows affected (0.00 sec)
回滚后查看另一个启动的事务
mysql> select * from account;
+----+--------+---------+
| id | name | balance |
+----+--------+---------+
| 1 | 张三 | 1234.99 |
+----+--------+---------+
1 row in set (0.00 sec)
下面继续回滚到 save1
mysql> rollback to save1;
Query OK, 0 rows affected (0.01 sec)
查询
mysql> select * from account;
Empty set (0.00 sec)
在事务里面既可以 mysql 自己出错回滚,也可以手动回滚,下面我们将刚才的那一批数据插入进去
mysql> insert into account values(1, '张三', 1234.99);
Query OK, 1 row affected (0.00 sec)
mysql> insert into account values(2, '李四', 77526.10);
Query OK, 1 row affected (0.00 sec)
mysql> insert into account values(3, '王五', 7191.10);
Query OK, 1 row affected (0.00 sec)
mysql> select * from account;
+----+--------+----------+
| id | name | balance |
+----+--------+----------+
| 1 | 张三 | 1234.99 |
| 2 | 李四 | 77526.10 |
| 3 | 王五 | 7191.10 |
+----+--------+----------+
3 rows in set (0.00 sec)
实际上回滚还可以直接回滚,也可以不设置回滚点,直接回滚会回滚到最开始
mysql> rollback;
Query OK, 0 rows affected (0.00 sec)
查询
mysql> select * from account;
Empty set (0.00 sec)
实际上在实际中,我们一般并不会在命令行上这样操作,一般 mysql 的的服务端奔溃,或者由于异常退出, mysql 可以帮我们保证原子性,下面我们让 mysql 退出等查看mysql 的回滚
插入数据
mysql> insert into account values(1, '张三', 1234.99);
Query OK, 1 row affected (0.00 sec)
查询
mysql> select * from account;
+----+--------+---------+
| id | name | balance |
+----+--------+---------+
| 1 | 张三 | 1234.99 |
+----+--------+---------+
1 row in set (0.00 sec)
ctrl + \ 让 mysql 奔溃
mysql> Aborted
查询
mysql> select * from account;
Empty set (0.00 sec)
这里看到,当 mysql 异常奔溃后,查询不到数据了,实际上异常退出也会这样。
经过上面测试我们一句测试完了mysql的事务的回滚,那么如果我们正常提交呢?
commit
当我们将一组 sql 写完后,我们想要正常提交我们可以直接 commit 那么这一组 sql 就被包装成一个事务被提交了。
下面测试一些正常提交
插入数据
mysql> insert into account values(1, '张三', 1234.99);
Query OK, 1 row affected (0.00 sec)
查询
mysql> select * from account;
+----+--------+---------+
| id | name | balance |
+----+--------+---------+
| 1 | 张三 | 1234.99 |
+----+--------+---------+
1 row in set (0.00 sec)
然后我们正常提交
mysql> commit;
Query OK, 0 rows affected (0.00 sec)
起始提交后我们回滚或者使其奔溃等都不会是数据丢失等,使数据发生错误
mysql> rollback;
Query OK, 0 rows affected (0.00 sec)
回滚后查询
mysql> select * from account;
+----+--------+---------+
| id | name | balance |
+----+--------+---------+
| 1 | 张三 | 1234.99 |
+----+--------+---------+
1 row in set (0.00 sec)
下面我们继续将事务开启,然后插入数据提交后使其奔溃
mysql> insert into account values(2, '李四', 1111.99);
Query OK, 1 row affected (0.00 sec)
mysql> commit;
Query OK, 0 rows affected (0.00 sec)
mysql> Aborted
查询
mysql> select * from account;
+----+--------+---------+
| id | name | balance |
+----+--------+---------+
| 1 | 张三 | 1234.99 |
| 2 | 李四 | 1111.99 |
+----+--------+---------+
2 rows in set (0.00 sec)
前面说了设置 autocommit 也就是自动提交和手动提交,那么自动提交和手动提交和事务的关系是什么?
下面分别测试自动提交和手动提交下面,mysql 事务插入数据和奔溃后的区别
下面启动一个事务,然后插入数据,让其奔溃
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> show variables like 'autocommit';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit | ON |
+---------------+-------+
1 row in set (0.00 sec)
mysql> insert into account values(1, '张三', 1234.99);
Query OK, 1 row affected (0.00 sec)
查询
mysql> select * from account;
+----+--------+---------+
| id | name | balance |
+----+--------+---------+
| 1 | 张三 | 1234.99 |
+----+--------+---------+
1 row in set (0.00 sec)
让其奔溃
mysql> Aborted
查询
mysql> select * from account;
Empty 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> insert into account values(1, '张三', 1234.99);
Query OK, 1 row affected (0.00 sec)
查询数据
mysql> select * from account;
+----+--------+---------+
| id | name | balance |
+----+--------+---------+
| 1 | 张三 | 1234.99 |
+----+--------+---------+
1 row in set (0.00 sec)
让其奔溃
mysql> Aborted
查询
mysql> select * from account;
+----+--------+---------+
| id | name | balance |
+----+--------+---------+
| 1 | 张三 | 1234.99 |
+----+--------+---------+
1 row in set (0.00 sec)
经过上面的测试我们并未发现自动提交与手动提交对事务由影响,实际上只要我们手动启动一个事务,那么autocommit 就和事务没有任何关系了,只要事务启动了,那么就是手动提交。
在上面我们知道了事务的特点,那么我们前面设置的 autocommit 是什么呢?它与单条 sql 由什么关系?
下面我们测试一下在手动提交和自动提交下,单条 sql 正常commit 和让其奔溃后的结果
mysql> show variables like 'autocommit';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit | ON |
+---------------+-------+
1 row in set (0.00 sec)
当前是自动提交,下面插入数据后,先让其奔溃
mysql> insert into account values(3, '王五', 22222.99);
Query OK, 1 row affected (0.00 sec)
查询
mysql> select * from account;
+----+--------+----------+
| id | name | balance |
+----+--------+----------+
| 1 | 张三 | 1234.99 |
| 2 | 李四 | 1111.99 |
| 3 | 王五 | 22222.99 |
+----+--------+----------+
3 rows in set (0.00 sec)
这里查询出了结果,下面让其奔溃后继续查询
mysql> select * from account;
+----+--------+----------+
| id | name | balance |
+----+--------+----------+
| 1 | 张三 | 1234.99 |
| 2 | 李四 | 1111.99 |
| 3 | 王五 | 22222.99 |
+----+--------+----------+
3 rows in set (0.00 sec)
这里即使奔溃后,结果还是被保留了,下面我们在插入数据后 commit
mysql> insert into account values(4, '赵六', 16732.99);
Query OK, 1 row affected (0.00 sec)
mysql> commit;
Query OK, 0 rows affected (0.00 sec)
查询
mysql> select * from account;
+----+--------+----------+
| id | name | balance |
+----+--------+----------+
| 1 | 张三 | 1234.99 |
| 2 | 李四 | 1111.99 |
| 3 | 王五 | 22222.99 |
| 4 | 赵六 | 16732.99 |
+----+--------+----------+
4 rows 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.01 sec)
上面设置成功了手动提交,下面测试
插入数据
mysql> insert into account values(5, '田七', 120032.99);
Query OK, 1 row affected (0.00 sec)
查询
mysql> select * from account;
+----+--------+-----------+
| id | name | balance |
+----+--------+-----------+
| 1 | 张三 | 1234.99 |
| 2 | 李四 | 1111.99 |
| 3 | 王五 | 22222.99 |
| 4 | 赵六 | 16732.99 |
| 5 | 田七 | 120032.99 |
+----+--------+-----------+
5 rows in set (0.00 sec)
使其奔溃
mysql> Aborted
查询
mysql> select * from account;
+----+--------+----------+
| id | name | balance |
+----+--------+----------+
| 1 | 张三 | 1234.99 |
| 2 | 李四 | 1111.99 |
| 3 | 王五 | 22222.99 |
| 4 | 赵六 | 16732.99 |
+----+--------+----------+
4 rows in set (0.00 sec)
奔溃后我们继续查询这里插入后的数据就不见了,下面 commit 后在查询
mysql> insert into account values(5, '田七', 120032.99);
Query OK, 1 row affected (0.01 sec)
查询
mysql> select * from account;
+----+--------+-----------+
| id | name | balance |
+----+--------+-----------+
| 1 | 张三 | 1234.99 |
| 2 | 李四 | 1111.99 |
| 3 | 王五 | 22222.99 |
| 4 | 赵六 | 16732.99 |
| 5 | 田七 | 120032.99 |
+----+--------+-----------+
5 rows in set (0.00 sec)
下面提交后在使其奔溃
mysql> commit;
Query OK, 0 rows affected (0.00 sec)
mysql> Aborted
查询
mysql> select * from account;
+----+--------+-----------+
| id | name | balance |
+----+--------+-----------+
| 1 | 张三 | 1234.99 |
| 2 | 李四 | 1111.99 |
| 3 | 王五 | 22222.99 |
| 4 | 赵六 | 16732.99 |
| 5 | 田七 | 120032.99 |
+----+--------+-----------+
5 rows in set (0.00 sec)
这里我们提交后在查询数据就得以保留
经过我们上面的测试,实际上我们也就知道了,实际上自动提交和手动提交是为单条 sql 设置的,如果是自动提交,那么单条 sql 就被被包装成事务,然后由 mysql 进行处理,如果是手动提交,那么就必须我们自己 commit 否则就不会被提交。
所以在mysql中,每一条sql都是被包装成事务后被执行的。
事务只要输入 begin 或者是 start transaction 后就必须通过 commit 才能持久化,与设置了 autocommit 无关。
事务可以手动回滚,同时如果操作异常,mysql也可以自动回滚。
对于 innodb 来说,每一条 sql 都会默认封装成事务,然后提交(但是 select 有特殊情况,后面会说到)。
从我们上面的测试,我们也就看到了事务的原子性(回滚),同时也看到了持久性。
如果设置了回滚点,那么就可以 rollback to ... 到该回滚点,否则只能 rollback 到最开始。
如果一个事务已经 commit(提交),那么是无法 rollback (回滚)的。
只有 innodb 支持事务,myisam 并不支持。
开始事务可以使用 start transaction 或者 begin。