事务 是一组操作的集合,它是一个不可分割的工作单位,事务会把所有的操作作为一个整体一起向系统提交或撤销操作请求,即这些操作要么同时成功,要么同时失败。
最常见的一个例子就是 用户A 给 用户B 转 1000元,那么 用户A 的余额就要少 1000,而 用户B 的余额就要多1000,这完整的一件事必须都执行成功,即用户A 的钱确实少了1000,用户B 的钱确实多了1000,不能因为其它异常情况而导致 A 的少了1000,而 B 却没有多1000,要么 A 的就不少 1000,B的也不多 1000。
我们先创建一张表:
create table account (
id int auto_increment primary key comment ' 主键ID',
name varchar(10) comment '姓名',
money int comment '余额'
) comment '账户表';
-- 插入数据
insert into account(id, name, money) VALUES (null, '张三', 2000), (null, '李四', 2000);
表的数据如下图所示:
现在我们开始模拟转账的过程(张三给李四转账1000):
转账后两人余额情况:
那现在假设语句书写错误:
张三和李四两个人的总金额就发生了变化。
在 MySQL 命令行的默认设置下,事务都是自动提交的,即执行 SQL 语句后就会马上执行 COMMIT 操作。
为了避免这种情况,我们可以设置事务提交方式,把自动提交改成手动提交。
现在我们在尝试操作转账这个过程:
这里看似转账成功了,但是当我们重新打开一个 mysql 客户端就可以发现并没有转账成功:
现在我们在最开始的客户端提交事务:
手动提交后,在重新开启的客户端就可以看到表里的金额发生了变化:
如果我们在执行 SQL语句发生错误时,我们就不再提交事务,而是选择 回滚 rollback;,这样就可以恢复到上次提交时状态,这里就不做演示了。
上面的事务操作是修改默认的事务提交方式,使用手动提交事务完成的。
现在我们不修改事务的提交方式,来完成上面的效果:
针对以上的事务并发问题,我们可以设置具体的隔离来解决:
隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
Read uncommitted | √ | √ | √ |
Read committed | × | √ | √ |
Repeatable Read(默认) | × | × | √ |
Serializable | × | × | × |
(注意:√ 表示会出现,× 表示不会出现,例如,在使用 Read committed 隔离后,会出现不可重复读和幻读)
隔离级别越高,性能就越低,性能越高,数据安全性就越低。
查看事务隔离级别
SELECT @@TRANSACTION_ISOLATION;
设置事务隔离级别
SET [SESSION | GLOBAL] TRANSACTION ISOLATION LEVEL {READ UNCOMMITTED | READ COMMITTED | REPEATABLE READ | SERIALIZABLE}
使用 Read uncommitted 就会出现脏读的情况:
A 读取到了 B 还没有提交后的数据就是脏读。
Read committed 虽然解决了脏读,但是并不能解决 不可重复读这个问题:
将隔离级别修改成 默认的 Repeatable Read,就可以解决不可重复读这个问题:
使用 Serializable 隔离可以解决幻读这个现象:
只有事务A 提交后,事务B 才会执行刚刚一直在等待执行的语句:
索引 (index) 是帮助 MysQL 高效获取数据的数据结构(有序)。说直白点,索引就是为了提高查询的效率。但索引的存在也占用了额外的空间,也降低了更新表 (insert,update等) 的速度。
索引的存在是为了提高查询速度,那么我们可以想到哈希表和二叉搜索树都是查询速度比较快的结构,但是在 mysql 中,我们也常使用 范围查询(between …),而哈希表只适用等值查询,对于二叉搜索树结构,那树的深度会随着表中的数据增多而变深,每次查找或插入也并不理想。
所以针对以上情况,mysql 使用的是 B+ 树,在了解 B+ 树之前,先看一下 B 树:
B 树,也叫多路平衡查找树,以一棵最大度为 5 的 B 树为例(每个节点最多存储4个key,5个指针):
将一段数据分为多个区间存储,比二叉树的深度减少了很多。
MySQL 索引数据结构对经典的 B+Tree 进行了优化。在原 B+Tree 的基础上,增加一个指向相邻叶子节点的链表指针,就形成了带有顺序指针的 B+Tree,提高区间访问的性能。
综上可以总结出使用 B+ 树的原因: