事务的概念
事务由单独单元的一个或多个SQL语句组成,在这个单元中,每个MySQL语句是相互依赖的。而整个单独单元作为一个不可分割的整体,如果单元中某条SQL语句一旦执行失败或产生错误,整个单元将会回滚。所有受到影响的数据将返回到事物开始以前的状态;如果单元中的所有SQL语句均执行成功,则事物被顺利执行。
二、MySQL 中的存储引擎以及支持事务,和不支持事务的存储引擎
1、存储引擎的概念:在mysql中的数据用各种不同的技术存储在文件(或内存)中。
这些技术中的每一种技术都使用不同的存储机制,索引 技巧,并且最终提供广泛的不同的功能和能力。可以通过选择不同的技术,可以获得额外的速度或功能,从而改善应用的整体功能。
2、这些不同的技术以及配套的相关功能在mysql中被称为存储引擎(也称为表类型)。
3、通过show engines;来查看mysql支持的存储引擎。
4、 在mysql中用的最多的存储引擎有:innodb,bdb,myisam ,memory 等。其中innodb和bdb支持事务而myisam等不支持事务。
三、事务的四个属性:
1、原子性:事务是由一个或一组相互关联的SQL语句组成,这些语句被认为是一个不可分割的单元。
2、一致性:对于数据库的修改是一致的,即多个用户查的的数据是一样的。一致性主要由mysql的日志机制处理,他记录数据的变化,为事务恢复提供跟踪记录。
3、隔离性(孤立性):每个事务都有自己的空间,和其他发生在系统中的事务隔离开来,而且事务的结果只在他完全被执行时才能看到
4、持久性:但提交了这个事务之后对数据的修改更新就是永久的。当一个事务完成,数据库的日志已经被更新时,持久性即可发挥其特有的 功效,在mysql中,如果系统崩溃或者数据存储介质被破坏,通过日志,系统能够恢复在重启前进行的最后一次成功更新,可以反应系统崩溃时处于执行过程的事物的变化。
四、事务的创建及生存周期
对于支持事务的存储引擎,一个事务的周期:
1、在创建事务的过程中,用户需要创建一个innodb或bdb类型的数据表,其基本命令结构如下:
create table table_name (file defintions) type=innodb/bdb;
2、对表类型进行修改
alert table table-name type =innodb/bdb;
3、事务的整个过程
use databases; //使用某个数据库
start transaction ; //开始事务 、这里也可以使用 begin 、 beginwork
insert into stu1 values('', ); //进行相关的操作
commit //提交事物
rollback //撤销事务(事务回滚)
五:mysql行为
1、我们在使用mysql是如果关闭之后,打开在查询的话就会发现,哪些数据已经保存了,但是我们知道,在这过程中没有进行显示或隐式提交,问什么会这样那?因为我们在mysql中设置了自动提交,但我们也可以改为手动提交。
2、 set autocommit =0; //关闭自动提交
3、 set autocommit =1; //开启自动提交
六、事务的孤立性(隔离性)
1、在多用户的时候使用孤立性级别是很重要的,这样可以保证这些事务互不影响,保证数据库性能不受到影响。
2、mysql中提供的孤立级别有以下四种:
(1)SERIALIZABLE(序列化)
//以序列的形式处理事务,只有事务提交后,用户才能看到,但是该级别的孤立会影响mysql的性能,因为需要占用大量的资源,以保证使大量事务在任意时间不被用户看到。
(2)REPEATABLE READ(可重读)
// 相比序列化该级别在应用的安全性上做了部分妥协。
(3)READ COMMITTED(提交后读)
// 提交后读的安全性比可重读还要低。在这一级的事务,用户可以看到其他事务添加的新记录,在事务处理时,如果存在其他用户同时对事务的相应表进行修改,那么同一事务在不同时间使用select 查询得到的结果集可能不同。
(4)READ UNCOMMITTED(未提交读)
//安全性相比提交后读就更低,同时该孤立及也是事务之间最小的间隔(孤立程度),该孤立级容易产生虚幻读操作。其他用户可以在该孤立级上看到未提交的事务。
3、事务孤立级的查看和修改:
查看:select @@tx_isolation;
修改:set global transaction isolation level 设置的孤立级别;
七、伪事务(锁定)
1、在MySQL中根据不同的需求,提供了很多存储引擎,但是有的存储引擎不支持事务,对于这种情况,可以使用表锁定来代替事务。
2、对于不支持事务的存储引擎MYISAM类型数据表,当用户插入,修改,删除时,这些操作都会立即保存到磁盘中,当多用户同时操作某个表时,可以使用表锁定来避免同一时间有多个用户对数据库中指定表进行操作,这样可以避免在用户操作数据表过程中受到干扰。只有但用户释放表的操作锁定后,其他 用户才可以访问这些修改的数据表。
这里的锁和Java多线程中锁的作用一样,个人觉的可以这样理解。
3、对指定表进行锁操作的过程:
(1)lock table table-name lock type ; //locktype 有read 和write 两种
对多个表进行加锁:
lock table table-name1 lock type, table -name2 lock type ,table -name3 lock type;
(2)在指定的表中进行相应的操作
(3)当用户完成对锁定数据表的操作后,进行解锁。
unlock tables ; //释放了所有加锁表的锁。
事务控制和锁定语句:
LOCK TABLES 可以锁定用于当前线程的表。如果表被其他线程锁定,则当前线程会等待,直到可以获取所有锁定为止。
UNLOCK TABLES 可以释放当前线程获得的任何锁定。当前线程执行另一个 LOCK TABLES 时,或当与服务器的连接被关闭时,所有由当前线程锁定的表被隐含地解锁,具体语法如下:
LOCK TABLES tbl_name [AS alias] {READ [LOCAL] | [LOW_PRIORITY] WRITE} [, tbl_name [AS alias] {READ [LOCAL] | [LOW_PRIORITY] WRITE}] ... |
UNLOCK TABLES
如表 14-1 所示是一个获得表锁和释放表锁的简单例子,演示的是 film_text 表获得 read 锁的情况,其他 session 更新该表记录会等待锁,film_text 表释放锁以后,其他 session 可以进行更新操作。其中 session1 和 session2 表示两个同时打开的 session,表格中的每一行表示同一时刻两个 session 的运行状况,后面的例子也都是同样格式,不再重复说明。
表14-1 一个获得表锁和释放表锁的简单例子
session_1 | session_2 |
获得表film_text的READ锁定 mysql> lock table film_text read; Query OK, 0 rows affected (0.00 sec) |
|
当前session可以查询该表记录 mysql> select film_id,title from film_text where film_id = 1001; +---------+------------------+ | film_id | title | +---------+------------------+ | 1001 | ACADEMY DINOSAUR | +---------+------------------+ 1 row in set (0.00 sec) | 其他session也可以查询该表的记录 mysql> select film_id,title from film_text where film_id = 1001; +---------+------------------+ | film_id | title | +---------+------------------+ | 1001 | ACADEMY DINOSAUR | +---------+------------------+ 1 row in set (0.00 sec) |
| 其他 session 更新锁定表会等待获得锁: mysql> update film_text set title = 'Test' where film_id = 1001; 等待 |
释放锁 mysql> unlock tables; Query OK, 0 rows affected (0.00 sec) | 等待 |
| Session 获得锁,更新操作完成: mysql> update film_text set title = 'Test' where film_id = 1001; Query OK, 1 row affected (1 min 0.71 sec) Rows matched: 1 Changed: 1 Warnings: 0 |
事务定义
Transaction
事务:一个最小的不可再分的工作单元;通常一个事务对应一个完整的业务(例如银行账户转账业务,该业务就是一个最小的工作单元)
一个完整的业务需要批量的DML(insert、update、delete)语句共同联合完成
事务只和DML语句有关,或者说DML语句才有事务。这个和业务逻辑有关,业务逻辑不同,DML语句的个数不同
事务控制
MySQL 通过 SET AUTOCOMMIT、START TRANSACTION、COMMIT 和 ROLLBACK 等语句支持本地事务,具体语法如下。
START TRANSACTION | BEGIN [WORK]
COMMIT [WORK] [AND [NO] CHAIN] [[NO] RELEASE]
ROLLBACK [WORK] [AND [NO] CHAIN] [[NO] RELEASE]
SET AUTOCOMMIT = {0 | 1}
默认情况下,MySQL 是自动提交(Autocommit)的,如果需要通过明确的 Commit 和 Rollback 来提交和回滚事务,那么需要通过明确的事务控制命令来开始事务,这是和 Oracle 的事务管理明显不同的地方。如果应用是从 Oracle 数据库迁移到 MySQL 数据库,则需要确保应用中是否对事务进行了明确的管理。
- START TRANSACTION 或 BEGIN 语句可以开始一项新的事务。
- COMMIT 和 ROLLBACK 用来提交或者回滚事务。
- CHAIN 和 RELEASE 子句分别用来定义在事务提交或者回滚之后的操作,CHAIN 会立即启动一个新事物,并且和刚才的事务具有相同的隔离级别,RELEASE 则会断开和客户端的连接。
- SET AUTOCOMMIT 可以修改当前连接的提交方式,如果设置了 SET AUTOCOMMIT=0,则设置之后的所有事务都需要通过明确的命令进行提交或者回滚。
如果只是对某些语句需要进行事务控制,则使用 START TRANSACTION 语句开始一个事务比较方便,这样事务结束之后可以自动回到自动提交的方式,如果希望所有的事务都不是自动提交的,那么通过修改 AUTOCOMMIT 来控制事务比较方便,这样不用在每个事务开始的时候再执行 START TRANSACTION 语句。
如表 14-2 所示的例子演示了使用 START TRANSACTION 开始的事务在提交后自动回到自动提交的方式;如果在提交的时候使用 COMMIT AND CHAIN,那么会在提交后立即开始一个新的事务。
表14-2 START TRANSACTION和COMMIT AND CHAIN的使用例子
session_1 | session_2 |
从表actor中查询actor_id=201的记录,结果为空: mysql> select * from actor where actor_id = 201; Empty set (0.00 sec) | 从表actor中查询actor_id=201的记录,结果为空: mysql> select * from actor where actor_id = 201; Empty set (0.00 sec) |
用 start transaction 命令启动一个事务,往表 actor中插入一条记录,没有commit: mysql> start transaction; Query OK, 0 rows affected (0.00 sec)
mysql> insert into actor (actor_id,first_name,last_name) values(201,'Lisa','Tom'); Query OK, 1 row affected (0.00 sec) |
|
| 查询表actor,结果仍然为空: mysql> select * from actor where actor_id = 201; Empty set (0.00 sec) |
执行提交: mysql> commit; Query OK, 0 rows affected (0.04 sec) |
|
| 再次查询表actor,可以查询到结果: mysql> select actor_id,last_name from actor where actor_id in (201,202); +----------+-----------+ | actor_id | last_name | +----------+-----------+ | 201 | Tom | +----------+-----------+ 1 row in set (0.00 sec) |
这个事务是按照自动提交执行的: mysql> insert into actor (actor_id,first_name,last_name) values(202,'Lisa','Lan'); Query OK, 1 row affected (0.04 sec) |
|
| 可以从actor表中查询到session1刚刚插入的数据。 mysql> select actor_id,last_name from actor where actor_id in (201,202); +----------+-----------+ | actor_id | last_name | +----------+-----------+ | 201 | Tom | | 202 | Lan | +----------+-----------+ 2 rows in set (0.00 sec) |
重新用start transaction启动一个事务: mysql> start transaction; Query OK, 0 rows affected (0.00 sec)
往表actor中插入一条记录: mysql> insert into actor (actor_id,first_name,last_name) values(203,'Lisa','TT'); Query OK, 1 row affected (0.00 sec) 用commit and chain命令提交: mysql> commit and chain; Query OK, 0 rows affected (0.03 sec)
此时自动开始一个新的事务: mysql> insert into actor (actor_id,first_name,last_name) values(204,'Lisa','Mou'); Query OK, 1 row affected (0.00 sec) |
|
| session1刚插入的记录无法看到: mysql> select actor_id,last_name from actor where first_name = 'Lisa'; +----------+-----------+ | actor_id | last_name | +----------+-----------+ | 178 | MONROE T | | 201 | Tom | | 202 | Lan | | 203 | TT | +----------+-----------+ 4 rows in set (0.00 sec) |
用commit命令提交: mysql> commit; Query OK, 0 rows affected (0.06 sec) |
|
| session1插入的新记录可以看到: mysql> select actor_id,last_name from actor where first_name = 'Lisa'; +----------+-----------+ | actor_id | last_name | +----------+-----------+ | 178 | MONROE T | | 201 | Tom | | 202 | Lan | | 203 | TT | | 204 | Mou | +----------+-----------+ 5 rows in set (0.00 sec) |
如果在锁表期间,用 start transaction 命令开始一个新事务,会造成一个隐含的 unlock
tables 被执行,如表 14-3 所示。
表 14-3 start transaction导致的unlock tables
session_1 | session_2 |
从表actor中查询actor_id=201的记录,结果为空: mysql> select * from actor where actor_id = 201; Empty set (0.00 sec) | 从表actor中查询actor_id=201的记录,结果为空: mysql> select * from actor where actor_id = 201; Empty set (0.00 sec) |
对表actor加写锁: mysql> lock table actor write; Query OK, 0 rows affected (0.00 sec) |
|
| 对表actor的读操作被阻塞: mysql> select actor_id,last_name from actor where actor_id = 201; 等待 |
插入一条记录 mysql> insert into actor (actor_id,first_name,last_name) values(201,'Lisa','Tom'); Query OK, 1 row affected (0.04 sec) | 等待 |
回滚刚才的记录: mysql> rollback; Query OK, 0 rows affected (0.00 sec) | 等待 |
用start transaction命令重新开始一个事务: mysql> start transaction; Query OK, 0 rows affected (0.00 sec) | 等待 |
| session1开始一个事务时,表锁被释放,可以查询: mysql> select actor_id,last_name from actor where actor_id = 201; +----------+-----------+ | actor_id | last_name | +----------+-----------+ | 201 | Tom | +----------+-----------+ 1 row in set (17.78 sec) 对lock方式加的表锁,不能通过rollback进行回滚。 |
因此,在同一个事务中,最好不使用不同存储引擎的表,否则 ROLLBACK 时需要对非事务类型的表进行特别的处理,因为 COMMIT、ROLLBACK 只能对事务类型的表进行提交和回滚。
通常情况下,只对提交的事务记录到二进制的日志中,但是如果一个事务中包含非事务类型的表,那么回滚操作也会被记录到二进制日志中,以确保非事务类型表的更新可以被复制到从(Slave)数据库中。
和 Oracle 的事务管理相同,所有的 DDL 语句是不能回滚的,并且部分的 DDL 语句会造成隐式的提交。
在事务中可以通过定义 SAVEPOINT,指定回滚事务的一个部分,但是不能指定提交事务的一个部分。对于复杂的应用,可以定义多个不同的 SAVEPOINT,满足不同的条件时,回滚不同的 SAVEPOINT。需要注意的是,如果定义了相同名字的 SAVEPOINT,则后面定义的 SAVEPOINT 会覆盖之前的定义。对于不再需要使用的 SAVEPOINT,可以通过 RELEASE SAVEPOINT 命令删除 SAVEPOINT,删除后的 SAVEPOINT,不能再执行 ROLLBACK TO SAVEPOINT 命令。
如表 14-4 所示的例子就是模拟回滚事务的一个部分,通过定义 SAVEPOINT 来指定需要回滚的事务的位置。
表14-4 模拟回滚事务
session_1 | session_2 |
从表actor中查询first_name=’Simon’的记录,结果为空: mysql> select * from actor where first_name = 'Simon'; Empty set (0.00 sec) | 从表actor中查询first_name=’Simon’的记录,结果为空: mysql> select * from actor where first_name = 'Simon'; Empty set (0.00 sec) |
启动一个事务,往表actor中插入一条记录: mysql> start transaction; Query OK, 0 rows affected (0.02 sec) mysql> insert into actor (actor_id,first_name,last_name) values(301,'Simon','Tom'); Query OK, 1 row affected (0.00 sec) |
|
可以查询到刚插入的记录: mysql> select actor_id,last_name from actor where first_name = 'Simon'; +----------+-----------+ | actor_id | last_name | +----------+-----------+ | 301 | Tom | +----------+-----------+ 1 row in set (0.00 sec) | 无法从actor表中查到session1刚插入的记录: mysql> select * from actor where first_name = 'Simon'; Empty set (0.00 sec) |
定义savepoint,名称为test: mysql> savepoint test; Query OK, 0 rows affected (0.00 sec)
继续插入一条记录: mysql> insert into actor (actor_id,first_name,last_name) values(302,'Simon','Cof'); Query OK, 1 row affected (0.00 sec) |
|
可以查询到两条记录: mysql> select actor_id,last_name from actor where first_name = 'Simon'; +----------+-----------+ | actor_id | last_name | +----------+-----------+ | 301 | Tom | | 302 | Cof | +----------+-----------+ 2 rows in set (0.00 sec) | 仍然无法查询到结果: mysql> select * from actor where first_name = 'Simon'; Empty set (0.00 sec) |
回滚到刚才定义的savepoint: mysql> rollback to savepoint test; Query OK, 0 rows affected (0.00 sec) |
|
只能从表actor中查询到第一条记录,因为第二条已经被回滚: mysql> select actor_id,last_name from actor where first_name = 'Simon'; +----------+-----------+ | actor_id | last_name | +----------+-----------+ | 301 | Tom | +----------+-----------+ 1 row in set (0.00 sec) | 仍然无法查询到结果: mysql> select * from actor where first_name = 'Simon'; Empty set (0.00 sec) |
用commit命令提交: mysql> commit; Query OK, 0 rows affected (0.05 sec) |
|
只能从actor表中查询到第一条记录: mysql> select actor_id,last_name from actor where first_name = 'Simon'; +----------+-----------+ | actor_id | last_name | +----------+-----------+ | 301 | Tom | +----------+-----------+ 1 row in set (0.00 sec) | 只能从actor表中查询到session1插入的第一条记录: mysql> select actor_id,last_name from actor where first_name = 'Simon'; +----------+-----------+ | actor_id | last_name | +----------+-----------+ | 301 | Tom | +----------+-----------+ 1 row in set (0.00 sec) |