功能
当多个用户访问同一份数据时,一个用户在更改数据的过程中可能有其他用户同时发起更改请求,为保证数据的更新从一个一致性状态变更为另一个一致性状态,就需要用到事务。
特性
事务有四个特性:原子性、一致性、隔离性、持久性。下面一一介绍。
原子性:事务中所有的操作视为一个原子单元,即对于事务所进行的数据修改操作等操作只能是完全执行或完全回滚。
一致性:事务在完成时,必须使所有的数据从一种一致性状态变更为另外一种一致性状态。
隔离性:一个事务中的操作语句所做的修改与其他事务所做的修改相隔离。
持久性:事务完成之后,对数据的修改时永久的,即使系统故障也不会丢失。
控制语句
BEGIN:也可以使用START TRANSACTION,它用来显式地开启一个事务。
COMMIT:也可以使用COMMIT WORK,二者是等价的。它用来提交事务,对数据库进行永久性的修改。
ROLLBACK:也可以使用ROLLBACK WORK,二者是等价的。它用来结束事务,并撤销正在进行的所有未提交的修改。
SET AUTOCOMMIT = 0 :禁止自动提交
SET AUTOCOMMIT = 1 :开启自动提交
示例
mysql> use test;
Database changed
mysql> CREATE TABLE shiwu( id int(5)) engine=innodb;
Query OK, 0 rows affected
mysql> begin;
Query OK, 0 rows affected
mysql> insert into shiwu(id) values(1);
Query OK, 1 row affected
mysql> insert into shiwu(id) values(2);
Query OK, 1 row affected
mysql> commit;
Query OK, 0 rows affected
mysql> select id from shiwu;
+----+
| id |
+----+
| 1 |
| 2 |
+----+
2 rows in set
mysql> begin;
Query OK, 0 rows affected
mysql> insert into shiwu(id) values(3);
Query OK, 1 row affected
mysql> rollback;
Query OK, 0 rows affected
mysql> select id from shiwu;
+----+
| id |
+----+
| 1 |
| 2 |
+----+
2 rows in set
隔离级别
SQL标准定义了4中隔离级别,指定了事务中哪些数据改变其他事务可见,哪些数据改变其他事务不可见。低隔离级别可以支持更高的并发处理,同时占用的系统资源更少。事务隔离级别可以使用以下语句设置:
#未提交读
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;
READ-UNCOMMITTED
在该隔离级别,所有事务都可以看到其他未提交事务的执行结果。读取未提交的数据会产生脏读(Dirty Read)的问题,比如:事务A将某个值改为1,但并未提交,此时事务B来读取这个值,取到1,事务A发现修改的不对,进行回滚。而事务B获取的是事务A回滚前的数据,这就是脏读。
READ-COMMITTED
在该隔离级别,一个事务从开始到提交前所做的任何改变都是不可见的,其他事务只能看见已经提交事务所做的改变。这样就解决了脏读问题。但存在不可重复读的问题,如:事务A查询某个值后,事务B对这个值进行更新并提交,事务A执行过程中第二次查询这个值时,会得到跟第一次不同的结果。这就是不可重复读。
REPEATTABLE-READ
它是MySQL数据库的默认隔离级别,在该隔离级别,当事务开始读取数据时,不再允许修改。但存在幻读的问题,如:事务A查询某张表的全部数据后,事务B插入了一条数据,当事务A再次查询全部数据时,会发现多了一条数据,这就是幻读。
SERIALIZABLE
这是最高的隔离级别,在该级别下,通过强制事务排序,串行化顺序执行,可以避免脏读、不可重复读与幻读。但这种事务隔离级别效率低下,比较消耗数据库性能,一般不使用。
大多数数据库默认的隔离级别是READ-COMMITTED,比如SQL Server、Oracle。
MySQL的默认隔离级别是REPEATTABLE-READ。
InnoDB锁机制
为解决数据库并发控制问题,如在同一时刻,客户端对于同一个表做更新或者查询操作,为保证数据的一致性,需要对并发操作进行控制。因此产生了锁。同时为实现MySQL的各个隔离级别,锁机制为其提供了保证。
锁的种类主要有:共享锁、排他锁、意向锁。下面一一介绍。
共享锁
共享锁的代号是S,是Share的缩写,共享锁的锁粒度是行或者元组(多个行)。一个事务获取共享锁后,可以对锁定范围内的数据执行读操作。
排他锁
排他锁的代号是X,是eXclusive的缩写,排他锁的粒度与共享锁相同,也是行或者元组。一个事务获取排他锁之后,可以对锁定范围内的数据执行写操作。如果事务A获取了一个元组的共享锁,事务B也可以立即获取这个元组的共享锁,但不能获取这个元组的排他锁,必须等到事务A释放共享锁之后。如果事务A获取了一个元组的排他锁,那么事务B既不能获取这个元组的共享锁,也不能获取这个元组的排他锁。必须等到事务A释放排他锁之后。
意向锁
意向锁是一种表锁,锁定的粒度是整张表,分为意向共享锁(IS)和意向排他锁(IX)两类。意向共享锁表示一个事务有意获取数据的共享锁或者排他锁。“有意”表示事务想执行操作但还没有真正执行。
锁关系
锁和锁之间的关系,要么是相容的,要么是互斥的。关系表如下:
参数 | X | S | IX | IS |
---|---|---|---|---|
X | N | N | N | N |
S | N | Y | N | Y |
IX | N | N | Y | Y |
IS | N | Y | Y | Y |
为了提高数据库的并发量,每次锁定的数据范围越小越好,越小的锁其耗费的系统资源越多,系统性能下降。为在高并发响应和系统性能两方面进行平衡,就产生了“锁粒度(Lock granularity)”的概念。
锁粒度
锁的粒度主要分为表锁和行锁。
表锁管理锁的开销最小,同时允许的并发量也是最小的锁机制。MyISAM存储引擎使用该锁机制,当要写入数据时,把整个表记录加锁,此时其他读、写动作一律等待。同时一些特定的动作,如ALTER TABLE执行时使用的也是表锁。
行锁可以支持最大的并发。InnoDB存储引擎使用该锁机制。如果要支持并发读、写,建议采用InnoDB存储引擎,因为其是采用行级锁,可以获得更好的更新性能。
执行
以下是MySQL中一些语句执行行锁的情况:
SELECT ...... LOCK IN SHARE MODE
此操作会加上一个共享锁。若会话事务中查找的数据已经被其他会话事务加上排他锁的话,共享锁会等待其结束再加,若等待时间过长就会显示事务需要的锁等待超时。
SELECT ...... FOR UPDATE
此操作加上一个排他锁,其他会话事务将无法再加其他锁,必须等待其结束。
INSERT / UPDATE /DELETE
会话事务会对DML语句操作的数据加上一个排他锁,其他会话的事务都将会等待其释放排他锁。
当共享锁或排他锁需要加到一个区间值域时,InnoDB引擎会自动再为其加上间隙锁或称为范围锁,对不存在的数据也锁住,防止出现幻写。注:上述语句描述的情况,与MySQL所设置的事务隔离级别有较大关系。
当开启一个事务时,InnoDB存储引擎会在更新的记录上加行级锁,此时其他事务不可以更新被锁定的记录。
//事务A
mysql> begin;
Query OK, 0 rows affected
mysql> UPDATE student SET sno = 456 WHERE sname = '张三';
Query OK, 0 rows affected
Rows matched: 1 Changed: 0 Warnings: 0
//事务B
mysql> begin;
Query OK, 0 rows affected
mysql> UPDATE student SET sno = 789 WHERE sname = '张三';
//此时事务B会等待,直到事务A执行COMMIT或超时报错(Lock wait timeout exceeded; try restarting transaction)
参考资料:《MySQL数据库从入门到精通》
参考资料:https://blog.csdn.net/qq_33290787/article/details/51924963