mysql的事务

当多个用户访问同一份数据时候,一个用户在更改数据的过程中可能有其他用户同时发起更改请求,为保证数据的更改从一个一致性状态更改为另外一个一致性状态,从而引入事务。

MySQL提供了多种存储引擎支持事务,支持事务的存储引擎有InnoDB和BDB。InnoDB存储引擎事务主要通过UNDO日志和REDO日志实现。MyISAM和MEMORY存储引擎则不支持事务。

1. 事务概述事务的四个特性:

a. 原子性(Atomicity):事务中所有的操作视为一个原子单元,即对于事务所进行的数据修改等操作只能是完全提交或者完全回滚。

b. 一致性(Consistency):事务在完成时,必须使所有的数据从一种一致性状态变更为另外一种一致性状态,所有的变更都必须应用于事务的修改,以确保数据的完整性。

c. 隔离性(Isolation):一个事务中的操作语句所做的修改必须与其他事物所做的修改相隔离。在进行事务查看数据时数据所处的状态,要么是被另一并发事务修改之前的状态,要么是被另一并发事务修改之后的状态,即当前事务不会查看由另一个并发事务正在修改的数据,这种特性通过锁机制实现。

d. 持久性(Durability):事务完成之后,所做的修改对数据的影响是永久的,即使系统重启,或者出现系统故障数据仍可以恢复。

MySQL提供了多种事务型存储引擎支持,如InnoDB和BDB等,而MyISAM不支持事务,InnoDB支持ACID事务,行级锁和高并发。为支持事务,InnoDB存储引擎引入了与事务相关的UNDO日志和REDO日志,同时事务依赖于MySQL提供的锁机制。

1.1 REDO日志

事务执行时需要将执行的事务日志写入到日志文件里,对应的日志文件为REDO日志。当每条SQL进行数据库更新操作时,首先将REDO日志写入到日志缓冲区。当客户端执行COMMIT命令提交时,日志缓冲区的内容将被刷新到磁盘,日志缓冲区的刷新方式或者时间间隔可以通过参数innidb_flush_log_at_trx_commit控制。

REDO日志对应于磁盘上的ib_logfileN文件,该文件默认为5MB,建议设置成512MB以便容纳较大的事务。在MySQL崩溃恢复时会重新执行REDO日志中的记录。

1.2 UNDO日志

UNDO日志与 REDO日志相反,UNDO日志主要用于事务异常时的数据回滚,具体内容就是复制事务前的数据库内容到UNDO缓冲区,然后在合适的时间将内容刷新到磁盘。

与REDO日志不同的是,磁盘上不存在单独的UNDO日志文件,所有的UNDO日志均存放在表空间对应的.ibd数据文件中,即使MySQL服务启用了独立表空间,依然如此。UNDO日志又称为回滚段。

2. MySQL事务控制语句

MySQL中使用事务BEGIN事务,事务OMMIT结束事务,中间可以使用ROLLBACK回滚事务。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中的事务是默认提交的。如需对某些语句进行事务控制则使用START TRANSACTION或者BEGIN开始一个事务比较方便。这样事务结束之后可以自动回到自动提交的方式。

下面示例演示一个简单的事务,功能为更新表中的一条记录,为保证数据从一个一致性状态更新到另外一种一致性状态,因此采用事务完成更新过程,如果更新失败或者其他原因可以使用回滚。此实例执行时对应的MySQL默认隔离级别为REPEATABLE-READ。

#查看MySQL隔离级别
mysql> show variables like 'tx_isolation';
+---------------+-----------------+
| Variable_name | Value           |
+---------------+-----------------+
| tx_isolation  | REPEATABLE-READ |
+---------------+-----------------+
1 row in set (0.33 sec)


#创建测试需要的表,主要存储引擎为InnoB
mysql> use company;
Database changed
mysql> create table test_1(id int,username varchar(20)) engine=InnoDB;
Query OK, 0 rows affected (0.67 sec)

mysql> insert into test_1 values(1,'petter'),(2,'bob'),(3,'allen'),(4,'aron');
Query OK, 4 rows affected (0.17 sec)
Records: 4  Duplicates: 0  Warnings: 0

mysql> select * from test_1;
+------+----------+
| id   | username |
+------+----------+
|    1 | petter   |
|    2 | bob      |
|    3 | allen    |
|    4 | aron     |
+------+----------+
4 rows in set (0.00 sec)

#开启一个事务
mysql> begin;
Query OK, 0 rows affected (0.02 sec)

#更新一条记录
mysql> update test_1 set username='test' where id = 1;
Query OK, 1 row affected (0.06 sec)
Rows matched: 1  Changed: 1  Warnings: 0

#提交事务
mysql> commit;
Query OK, 0 rows affected (0.06 sec)

#发现记录已经更改生效
mysql> select * from test_1;
+------+----------+
| id   | username |
+------+----------+
|    1 | test     |
|    2 | bob      |
|    3 | allen    |
|    4 | aron     |
+------+----------+
4 rows in set (0.00 sec)

#开启另外一个事务
mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> update test_1 set username='petter' where id = 1;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> select * from test_1;
+------+----------+
| id   | username |
+------+----------+
|    1 | petter   |
|    2 | bob      |
|    3 | allen    |
|    4 | aron     |
+------+----------+
4 rows in set (0.00 sec)

#回滚事务
mysql> rollback;
Query OK, 0 rows affected (0.13 sec)

#此时发现数据已经回滚
mysql> select * from test_1;
+------+----------+
| id   | username |
+------+----------+
|    1 | test     |
|    2 | bob      |
|    3 | allen    |
|    4 | aron     |
+------+----------+
4 rows in set (0.00 sec)
View Code

3. MySQL事务隔离级别

SQL标准定义了四种隔离级别,指定了事务中哪些数据改变其他事务可见,哪些数据改变其他事务不可见,低级别的隔离级别可以支持更高的并发处理,同时占用的系统资源更少。

查看MySQL隔离级别:

SHOW VARIABLES LIKE 'tx_isolation';

事务隔离级别可以使用以下语句设置:

#未提交读
SET GLOBAL TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
#提交读
SET GLOBAL TRANSACTION ISOLATION LEVEL READ COMMITTED;
#可重复读
SET GLOBAL TRANSACTION ISOLATION LEVEL  REPEATABLE READ;  
#可串行化
SET GLOBAL TRANSACTION ISOLATION LEVEL SERIALIZABLE;

3.1 READ-UNCOMMITTED(读取未提交内容)

在该隔离级别,所有事务都可以看到其他未提交事务的执行结果。因为其性能也不比其他级别高很多,因此此隔离级别实际应用中很少使用,读取未提交的数据被称为脏读(Dirty Read)。

脏读问题示例如下:

READ-UNCOMMITTED级别造成的脏读问题演示
A事务 B事务
mysql> show variables like 'tx_isol
+---------------+------------------
| Variable_name | Value
+---------------+------------------
| tx_isolation  | READ-UNCOMMITTED
+---------------+------------------
1 row in set (0.00 sec)

mysql> begin;
Query OK, 0 rows affected (0.00 sec

mysql> select * from test_1;
ERROR 1046 (3D000): No database sel
mysql> use company;
Database changed
mysql> show variables like 'tx_isol
+---------------+------------------
| Variable_name | Value
+---------------+------------------
| tx_isolation  | READ-UNCOMMITTED
+---------------+------------------
1 row in set (0.00 sec)

mysql> begin;
Query OK, 0 rows affected (0.00 sec

mysql> use company;
Database changed
mysql> select * from test_1;
+------+----------+
| id   | username |
+------+----------+
|    1 | test     |
|    2 | bob      |
|    3 | allen    |
|    4 | aron     |
+------+----------+
4 rows in set (0.00 sec)

mysql> select * from test_1;
+------+----------+
| id   | username |
+------+----------+
|    1 | test     |
|    2 | leslie   |
|    3 | allen    |
|    4 | aron     |
+------+----------+
4 rows in set (0.00 sec)

mysql> select * from test_1;
+------+----------+
| id   | username |
+------+----------+
|    1 | test     |
|    2 | bob      |
|    3 | allen    |
|    4 | aron     |
+------+----------+
4 rows in set (0.00 sec)
View Code
mysql> SHOW VARIABLES LIKE 'tx_isolation';
+---------------+------------------+
| Variable_name | Value            |
+---------------+------------------+
| tx_isolation  | READ-UNCOMMITTED |
+---------------+------------------+
1 row in set (0.00 sec)

mysql> BEGIN;
Query OK, 0 rows affected (0.00 sec)

mysql> UPDATE test_1 SET username='leslie' WHERE username='bob';
Query OK, 1 row affected (0.01 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> ROLLBACK;
Query OK, 0 rows affected (0.04 sec)
View Code

MySQL的隔离级别为READ-UNCOMMITTED,首先开启A和B两个事务,在B事务更新但未提交之前,A事务读取到了更新后的数据,但由于B事务回滚,A事务出现了脏读的现象。

3.2 READ-COMMITTED(读取提交内容)

这是大多数数据库系统的默认隔离级别,但并不是MySQL默认的隔离级别。其满足了隔离的简单定义,一个事务从开始到提交前所做的任何改变都是不可见的,事务只能看见已经提交事务所做的改变。这种隔离级别也支持所谓的不可重复读(Nonrepeatable Read),因为同一事务的其他实例在该实例处理期间可能会有新的数据提交导致数据改变,所以同一查询可能返回不同的结果。此级别导致的不可重复读示例如下所示:

 
A事务 B事务
mysql> show variables like 'tx_isolation';
+---------------+----------------+
| Variable_name | Value          |
+---------------+----------------+
| tx_isolation  | READ-COMMITTED |
+---------------+----------------+
1 row in set (0.00 sec)

mysql> use company;
Database changed

mysql> select * from test_1;
+------+----------+
| id   | username |
+------+----------+
|    1 | test     |
|    2 | bob      |
|    3 | allen    |
|    4 | aron     |
+------+----------+
4 rows in set (0.00 sec)

mysql> select * from test_1;
+------+----------+
| id   | username |
+------+----------+
|    1 | test     |
|    2 | leslie   |
|    3 | allen    |
|    4 | aron     |
+------+----------+
4 rows in set (0.00 sec)
View Code
mysql> SHOW VARIABLES LIKE 'tx_isolation';
+---------------+----------------+
| Variable_name | Value          |
+---------------+----------------+
| tx_isolation  | READ-COMMITTED |
+---------------+----------------+
1 row in set (0.00 sec)

mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> UPDATE test_1 SET username='leslie' WHERE username='bob';
Query OK, 0 rows affected (0.00 sec)
Rows matched: 0  Changed: 0  Warnings: 0

mysql> COMMIT;
Query OK, 0 rows affected (0.00 sec)
View Code

MySQL的隔离级别为READ-COMMITTED,首先开启A和B两个事务,在B事务更新并提交之前,A事务读取到了更新后的数据,此时处于同一A事务中的查询出现了不同的查询结果,即不可重复读现象。

3.3 REPEATABLE -READ(可重读)

这是MySQL的默认事务隔离级别,能确保同一事务的多个实例在并发读取数据时,会看到同样的数据行。理论上会导致幻读(Phantom Read)。例如第一个事务对一个表中 的数据进行了修改,这种修改涉及表中的全部数据行。同时第二个事务也修改这个表中的数据,这种修改是向表中插入一行新数据。那么,以后就会发生操作第一个事务的用户发现表中还有没有修改的数据行。InnoDB和Falcon存储引擎通过多版本并发控制(Multi_Version Concurrency Control,MVCC)机制解决了该问题。

InnoDB存储引擎MVCC机制:InnoDB通过为每个数据行增加两个隐含值得方式来实现。这两个隐含值记录了行的创建时间,以及过期时间。每一行存储事件发生的时的系统版本号。每一次开始一个新事务时版本号会自动加1,每个事务都会保存开始时的版本号,每个查询根据事务的版本号来查询结果。

此级别示例如下所示:

REPEATABLE -READ级别操作演示
A事务 B事务
mysql> show variables like 'tx_isolation
+---------------+-----------------+
| Variable_name | Value           |
+---------------+-----------------+
| tx_isolation  | REPEATABLE-READ |
+---------------+-----------------+
1 row in set (0.00 sec)

mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> use company;
Database changed
mysql> select * from test_1;
+------+----------+
| id   | username |
+------+----------+
|    1 | test     |
|    2 | leslie   |
|    3 | allen    |
|    4 | aron     |
+------+----------+
4 rows in set (0.00 sec)

mysql> select * from test_1;
+------+----------+
| id   | username |
+------+----------+
|    1 | test     |
|    2 | leslie   |
|    3 | allen    |
|    4 | aron     |
+------+----------+
4 rows in set (0.03 sec)

mysql> commit;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from test_1;
+------+----------+
| id   | username |
+------+----------+
|    1 | 龙       |
|    2 | 龙       |
|    3 | 龙       |
|    4 | 龙       |
+------+----------+
4 rows in set (0.00 sec)
View Code
mysql> SHOW VARIABLES LIKE 'tx_isolation';
+---------------+-----------------+
| Variable_name | Value           |
+---------------+-----------------+
| tx_isolation  | REPEATABLE-READ |
+---------------+-----------------+
1 row in set (0.00 sec)

mysql> BEGIN;
Query OK, 0 rows affected (0.00 sec)

mysql> UPDATE test_1 SET username='';
Query OK, 0 rows affected (0.03 sec)
Rows matched: 4  Changed: 0  Warnings: 0

mysql> COMMIT;
Query OK, 0 rows affected (0.03 sec)
View Code

MySQL的隔离级别为REPEATABLE-READ,首先开启A和B两事务,在B事务更新并提交后,A事务读取到的仍然是之前的数据,保证了在同一事务中读取到的数据都是同样的。在同一事务中,不推荐使用不同存储引擎的表,COMMIT,ROLLBACK只能对事务类型的表进行提交和回滚。

MySQL中所有的DDL语句是不能回滚的,并且部分的DDL会造成隐式的提交。比如,ALTER TABLE,TRUNCATE TABLE和DROP TABLE等。

隐式提交语句造成的事务自动提交示例如下所示:

 
A事务 B事务
mysql> show variables like 'tx_isolation';
+---------------+-----------------+
| Variable_name | Value           |
+---------------+-----------------+
| tx_isolation  | REPEATABLE-READ |
+---------------+-----------------+
1 row in set (0.00 sec)

mysql> select * from test_1;
+------+----------+
| id   | username |
+------+----------+
|    1 | pettry   |
|    2 | bob      |
|    3 | carry    |
|    4 | tom      |
|    5 | pandy    |
+------+----------+
5 rows in set (0.00 sec)

mysql> select * from test_1;
+------+----------+
| id   | username |
+------+----------+
|    1 | pettry   |
|    2 | bob      |
|    3 | carry    |
|    4 | tom      |
|    5 | pandy    |
+------+----------+
5 rows in set (0.00 sec)

mysql> select * from test_1;
+------+----------+
| id   | username |
+------+----------+
|    1 ||
|    2 ||
|    3 ||
|    4 ||
|    5 ||
+------+----------+
5 rows in set (0.00 sec)
View Code
mysql> SHOW VARIABLES LIKE 'tx_isolation';
+---------------+-----------------+
| Variable_name | Value           |
+---------------+-----------------+
| tx_isolation  | REPEATABLE-READ |
+---------------+-----------------+
1 row in set (0.00 sec)

mysql> UPDATE test_1 SET username='';
Query OK, 0 rows affected (0.08 sec)
Rows matched: 5  Changed: 0  Warnings: 0

mysql> ALTER TABLE test_1 ADD INDEX idx_1(id);
Query OK, 0 rows affected (0.36 sec)
Records: 0  Duplicates: 0  Warnings: 0
View Code

以上实例开启了一个事务,当B事务更新完指定的记录时,此时其他事务并不能看到更改额结果。当执行ALTER语句时,造成事务隐式提交,此时事务A看到了更改后的记录。

3.4 SERIALIZABLE(可串行化)

这是最高的隔离级别,通过强制事务排序,使之不可能相互冲突,从而解决幻读问题。简言之,是在每个读的数据行上加上共享锁实现。在这个级别,可能会导致大量的超时现象和锁竞争,一般不推荐使用。

4. 四种事务可能出现的问题

 上面的四种隔离级别采取不同的锁类型来实现,若读取的是同一个数据的话,就容易发生问题。例如:

a. 脏读(Dirty Read),某个事务已经更新了一份数据,另一个事务在此时读取了同一份数据,由于某些原因,前一个rollback了操作,则后一个事务读取的数据就会是不正确的。

b. 不可重复读(Non-repeatable read),在一个事务的两个查询之中数据不一致,这可能是两次查询过程中间插入了一个事务更新的原有的数据。

c.幻读(Phanton read), 在一个事务的两次查询中数据笔数不一致,例如有一个事务查询了几列(Row)数据,而另一个事务却在此时插入了新的几列数据,先前的事务在接下来的查询中,就会发现有几列数据是它之前所没有的。

5. InnoDB锁机制

为了解决数据库并发控制问题,如在同一时刻,客户端对于同一个表做更新或者查询操作,为保证数据的一致性,需要对并发操作进行控制,因此产生了锁。同时锁机制为实现MySQL的各个隔离级别提供了保证。

1. 锁的类型

锁的种类主要有以下几种:

a. 共享锁

共享锁的代号是S,是Share的缩写,共享锁的锁粒度是行或者元组(多个行)。一个事务获取了共享锁之后,可以对锁定范围内的数据执行读操作。

b. 排他锁

排他锁的代号是X,是eXclusive的缩写,排他锁的粒度与共享锁相同,也是行或者元组。一个事务获取了排他锁之后,可以对锁定范围内的数据进行写操作。

如有两个事务A和B,如果事务A获取了一个元组的共享锁,事务B还可以立即获取这个元组的共享锁,当不能立即获取这个元组的排他锁,必须等到事务A释放共享锁之后。

如果事务A获取了一个元组的排他锁,事务B不能立即获取这个元组的排他共享锁,也不能立即获取这个元组的排他锁,必须等到A释放排他锁之后。

c. 意向锁

意向锁是一种表锁,锁定的粒度是整张表,分为意向共享锁(IS)和意向排他锁(IX)两类。意向共享锁表示一个事务有意对数据上共享锁或者排他锁。"有意"表示事务想执行操作但还没有正真正执行。锁和锁之间的关系,要么是相容的,要么是互斥的。

锁a和锁b相容是指:操作同样一组数据时,如果事务t1获取了锁a,另一个事务t2还可以获取锁b;

锁a和锁b互斥是指:操作同样一组数据时,如果事务t1获取了锁a,另一个事务t2在t1释放锁a之前无法获获取锁b;

共享锁,排他锁,意向共享锁,意向排他锁相互之间的相容/互斥关系如下表所示:

其中Y表示相容,N表示互斥。

MySQL锁兼容情况说明
参数 X S IX IS
X N N N N
S N Y N Y
IX N N Y Y
IS N Y Y Y

2. 锁额粒度

锁的粒度主要分为表锁和行锁。

表锁管理表的开销最小,同时允许的并发量也是最小的锁机制。MyISAM存储引擎使用该锁机制。当要写入数据时,把整个表记录锁定,此时其他读,写动作一律等待。同时一些特定的动作,如ALTER TABLE执行时使用的也是表锁。

行锁可以支持最大的并发。InnoDB存储引擎使用该锁机制。如果要支持并发读/写,建议采用InnoDB存储引擎。因为其是采用行级锁,可以获得更多的更新性能。

以下是MySQL中一些语句执行时锁的情况:

SELECT ... LOCK IN SHARE MODE

此操作会加上一个共享锁。若会话事务中查找额数据已经被其他会话事务加上排他锁的话,共享锁会等待其结束再加,若等待时间过长就会显示事务需要的锁等待超时。

SELECT ... FOR UPDATE

此操作会加上一个排他锁,其他会话事务将无法再加其他锁,必须等待其结束。

INSERT,UPDATE.DELETE

会话事务会对DML语句操作的数据加上一个排他锁,其他会话的事务都将会等待其释放排他锁。

InnoDB引擎会自动给会话事务中的共享锁,更新锁以及排他锁,需要加到一个区间值域时,再加上个间隙锁或称为范围锁,对不存在的数据也锁住,防止出现幻写。

当开启一个事务时,InnoDB存储引擎会在更新的记录上加行级锁,此时其他事务是不可以更新被锁定的记录。示例如下:

InnoDB行级锁级别操作演示
A事务 B事务
mysql> SHOW VARIABLES LIKE 'tx_isolation';
+---------------+-----------------+
| Variable_name | Value           |
+---------------+-----------------+
| tx_isolation  | REPEATABLE-READ |
+---------------+-----------------+
1 row in set (0.00 sec)

mysql> update test_1 set username='111_new' where id=1;
Query OK, 1 row affected (0.09 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> commit;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from test_1;
+------+----------+
| id   | username |
+------+----------+
|    1 | 111_new  |
|    2 ||
|    3 ||
|    4 ||
|    5 ||
+------+----------+
5 rows in set (0.00 sec)
View Code
mysql>  SHOW VARIABLES LIKE 'tx_isolation';
+---------------+-----------------+
| Variable_name | Value           |
+---------------+-----------------+
| tx_isolation  | REPEATABLE-READ |
+---------------+-----------------+
1 row in set (0.00 sec)

mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql>  UPDATE test_1 SET username='222_new' WHERE id=1;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
View Code

如以上实例所示,当有不同的事务同时更新同一条记录时,另外一个事务需要等待另外一个事务把锁释放。

InnoDB锁为行级锁,同时最小范围的锁定更新记录涉及的范围,注意此时where条件指定的列要有主键或者索引,如下实例演示了InnoDB间隙锁的实现机制,示例如下:

 
A事务 B事务
mysql> show variables like 'tx_isolation';
+---------------+-----------------+
| Variable_name | Value           |
+---------------+-----------------+
| tx_isolation  | REPEATABLE-READ |
+---------------+-----------------+
1 row in set (0.00 sec)

mysql> use company;
Database changed
mysql> alter table test_1 add unique key idx_id(id);
Query OK, 0 rows affected (0.19 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> update test_1 set username='111' where id=1;
Query OK, 1 row affected (0.06 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> commit;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from test_1;
+------+----------+
| id   | username |
+------+----------+
|    1 | 111      |
|    2 ||
|    3 ||
|    4 ||
|    5 ||
+------+----------+
5 rows in set (0.00 sec)
View Code
mysql> show variables like 'tx_isolation';
+---------------+-----------------+
| Variable_name | Value           |
+---------------+-----------------+
| tx_isolation  | REPEATABLE-READ |
+---------------+-----------------+
1 row in set (0.00 sec)

mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> update test_1 set username='222' where id=2;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> commit;
Query OK, 0 rows affected (0.14 sec)

mysql> select * from test_1;
+------+----------+
| id   | username |
+------+----------+
|    1 | 111      |
|    2 | 222      |
|    3 ||
|    4 ||
|    5 ||
+------+----------+
5 rows in set (0.00 sec)
View Code

在上述实例中,由于InniDB行级锁为间隙锁,加锁只是锁定需要的记录,因此事务B可以更新其他记录,两个事务之间互互不影响。

原文地址:https://www.cnblogs.com/longshiyVip/p/4657342.html