mysql 事务

目录

什么是事务?

事务的特性

为什么要有事务

事务的版本支持

事务的提交方式

事务的基本操作

启动事务

创建回滚点

回滚

正常提交

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 和 事务的关系

前面说了设置 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 就和事务没有任何关系了,只要事务启动了,那么就是手动提交。

单条 sql 与 事务的区别

在上面我们知道了事务的特点,那么我们前面设置的 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。

你可能感兴趣的:(android)