事务管理是针对数据库的一组操作。由一条或多条SQL语句组成,这些语句在逻辑上具有强烈的相关性,如果其中一条语句无法执行,那么所有的语句都不会执行。
原子性 |
指一个事务必须被视为一个不可分割的最小单元。只有事务中所有的数据操作都执行成功,才算整个事务都执行成功。 |
一致性 |
事务执行前后,数据库的状态(所有的约束条件、完整性规则和触发器)必须保持一致。 |
隔离性 |
每个事务的执行都应该与其他事务相互隔离,使得每个事务感觉不到其他事务的存在。 |
持久性 |
一旦事务提交,其所做的修改将会永久保存在数据库中。即使系统故障或重启后也不会丢失。 |
表 事务的四个特性
事务有以下的优点:
START TRANSACTION; -- 开启事务
SAVEPOINT label1; -- 保存回滚节点
ROLLBACK TO label1; -- 回滚到具体的节点
ROLLBACK; -- 回滚整个事务
COMMIT; -- 提交事务
START TRANSACTION;
INSERT teacher(id,name) VALUES(4,"刘老师");
SAVEPOINT label1;
INSERT teacher(id,name) VALUES(5,"张老师");
ROLLBACK TO label1;
COMMIT; -- 最终,只有第一条信息会被插入id=4
1)脏读。一个事务处理过程中读取了另一个未提交的事务中的数据。
当数据库的事务隔离级别为读未提交(RU,READ-UNCOMMITTED)时,会出现这个问题。
-- 事务隔离级别为“读未提交”
START TRANSACTION;
UPDATE teacher SET `name`=CONCAT(`name`,"(未提交事务)") WHERE id = 4;
ROLLBACK -- 先不执行,先在其他事务查询这条数据,发现name="刘老师(未提交事务)",然后再执行回滚
将数据库隔离级别设置为其他的级别(比如读已提交),则不会有这个问题。
2)不可重复读。在一个事务中,多次读取同一个数据时,读取的数据不一致。(数据被更新了update)。
-- 隔离级别为“读已提交”
START TRANSACTION;
SELECT `name` FROM teacher WHERE id = 4;
SELECT SLEEP(30); -- 休眠30s,在这个时间在另一个事务中执行update操作,来跟新id=4的数据
SELECT `name` FROM teacher WHERE id = 4;
COMMIT;
将隔离级别设置为可重复读或可串行化则不会出现这个问题。
3)幻读。读取某一范围的数据时,在一个事务中多次读,结果不一致。(发生在插入或删除数据时。)
-- 隔离级别为 "读已提交",注意mysql 的“可重复读”不会出现这个问题。
START TRANSACTION;
SELECT * FROM teacher WHERE name = '刘老师';
SELECT SLEEP(20); -- 休眠20s,在这段时间在另一个事务执行insert操作,来插入新刘老师的数据
SELECT * FROM teacher WHERE name = '刘老师';
COMMIT;
mysql 将隔离级别设置为可重复读或可串行化则不会出现这个问题。
读未提交 RU READ UNCOMMITTED |
可以读取到事务未提交的数据,隔离性差。 |
读已提交 RC READ COMMITTED |
读取事务已提交的数据,隔离性一般。 |
可重复读 RR REPEATABLE READ |
默认。在一个事务中多次读取同一个数据时,能够保证读取到的数据一致(即使其他事务修改了该数据)。 |
可串行化 SR SERIALIZABLE |
最高隔离级别。保证所有事务之间的执行顺序按照某个顺序执行,避免了所有并发问题。事务并发性最差。 |
表 事务的四种隔离级别
读未提交 |
读已提交 |
可重复读 |
可串行化 |
|
脏读 |
x |
√ |
√ |
√ |
不可重复读 |
x |
x |
√ |
√ |
幻读 |
x |
x |
√ |
√ |
表 不同隔离级别下所解决的问题
注意:MySQL 隔离级别为可重复读解决了“幻读”的问题。
SHOW VARIABLES LIKE "transaction_isolation"; -- 查看数据库的隔离级别
SET GLOBAL TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -- 设置全局隔离级别为“读未提交”。需要重新连接数据库才会生效。
当前读,读取最新提交的数据。(隔离级别为可串行化)。
快照读(一致性读),读取某一时间点的数据。(隔离级别为读提交或可重复读)。
在读提交隔离级别下,每次SELECT都会建立新的快照。
在可重复读隔离级别下,建立快照的时机为:
-- 隔离级别为 "可重复读"
START TRANSACTION;
SELECT * FROM teacher WHERE name LIKE '%刘老师%'; -- 建立快照
SELECT SLEEP(20); -- 休眠20s,在这段时间在另一个事务执行insert操作,来插入新刘老师的数据
INSERT INTO teacher(`name`) VALUES('刘老师'); -- 数据插入或删除会建立新的快照
UPDATE teacher SET `name` = '刘老师(旧数据修改)' WHERE id = 2; -- 旧数据修改,会建立新的快照
SELECT * FROM teacher WHERE name LIKE '%刘老师%';
COMMIT;
注意,上面查询的结果虽然不一致,这是因为在同一个事务中进行修改或插入操作。因此不是幻读。 而如果在休眠的20s中在其他事务中执行了插入操作,依旧不会影响查询结果。因为快照读是基于MVCC(多版本并发控制)实现的。