MySQL 索引

MySQL update

Posted by cslqm on October 9, 2020

事务隔离

MyISAM 引擎不支持隔离,InnoDB 支持隔离。

事务 ACID (Atomicity、Consistency、Isolation、Durability,即 原子性、一致性、隔离性、持久性)

隔离性

为何需要隔离性? 当数据库上有多个事务同时执行的时候,就可能出现脏读(dirty read)、不可重复读 (non-repeatable read)、幻读(phantom read)的问题,为了解决这些问题,就有 了“隔离级别”的概念。

隔离性的四个等级

隔离级别:读未提交(read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(serializable )。

读未提交,一个事务还没有提交时,它的变更能被别的事务看到。 读提交,一个事务提交之后,它做的变更才会被其他事务看到。 可重复读,一个事务执行过程中看到的数据,总是跟这个事务在启动时看到的数据是一致的。在可重复读隔离级别下,未提交变更对其他事务也是不可见的。 串行化,顾名思义是对于同一行记录,“写”会加“写锁”,“读”会加“读锁”。当出现读写锁冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行。

隔离的实现

通过描述“可重复读”来说明隔离的实现。

在 MySQL 中,实际上每条记录在更新的时候都会同时记录一条回滚操作。总能看到事务启动时的数据,就是将当前的数据通过“回滚”,得到的。

回滚日志总不能一直保留吧,什么时候删除呢?答案是,在不需要的时候才删除。也就是说,系统会判断,当没有事务再需要用到这些回滚日志时,回滚日志会被删除。

什么时候才不需要了呢?就是当系统里没有比这个回滚日志更早的 read-view 的时候。

所以就不建议用长事务。

长事务意味着系统里面会存在很老的事务视图。由于这些事务随时可能访问数据库里面的任何数据,所以这个事务提交之前,数据库里面它可能用到的回滚记录都必须保留,这就会导致大量占用存储空间。

事务的启动方式

MySQL 的事务启动方式:

  1. 显式启动事务语句, begin 或 start transaction。配套的提交语句是 commit,回滚语 句是 rollback。
  2. set autocommit=0,这个命令会将这个线程的自动提交关掉。意味着如果你只执行一 个 select 语句,这个事务就启动了,而且并不会自动提交。这个事务持续存在直到你主 动执行 commit 或 rollback 语句,或者断开连接。

有些客户端连接框架会默认连接成功后先执行一个 set autocommit=0 的命令。这就导致 接下来的查询都在事务中,如果是长连接,就导致了意外的长事务。建议你总是使用 set autocommit=1, 通过显式语句的方式来启动事务。

在 autocommit 为 1 的情况下,用 begin 显式启动的事务,如果执行 commit 则提交事务。如果执行 commit work and chain,则是提交事务并自动启动下一个事务,这样也省去了再次执行 begin 语句的开销。同时带来的好处是从程序开发的角度明确地知道每个语句是否处于事务中。

索引的常见模型 基础数据结构

哈希表这种结构适用于只有等值查询的场景,比如 Memcached 及其他一些 NoSQL 引擎。

有序数组在等值查询和范围查询场景中的性能就都非常优秀, 有序数组索引只适用于静态存储引擎,比如你要保存的是 2017 年某个城市的所有 人口信息,这类不会再修改的数据。

InnoDB 的索引模型

在 InnoDB 中,表都是根据主键顺序以索引的形式存放的。这种存储的表叫索引组织表。 每个索引在 InnoDB 里对应一棵 B+ 树。 由于表和索引都是 B+ 树存储,所以 InnoDB 中,数据都是存储在 B+ 树中的。

比如:

1
2
3
4
5
create table T(
id int primary key,
k int not null,
name varchar(16), 
index (k))engine=InnoDB;

就有两棵树

如果在这个表内加入 R1~R5 的 (ID,k) 值分别为 (100,1)、(200,2)、(300,3)、(500,5) 和 (600,6)

索引

根据叶子节点的内容,索引类型分为主键索引和非主键索引:

  • 主键索引的叶子节点存的是整行数据。在 InnoDB 里,主键索引也被称为聚簇索引 (clustered index)。
  • 非主键索引的叶子节点内容是主键的值。在 InnoDB 里,非主键索引也被称为二级索引 (secondary index)。

基于主键索引和普通索引的查询有什么区别?

  • 如果语句是 select * from T where ID=500,即主键查询方式,则只需要搜索 ID 这棵 B+ 树;
  • 如果语句是 select * from T where k=5,即普通索引查询方式,则需要先搜索 k 索引 树,得到 ID 的值为 500,再到 ID 索引树搜索一次。这个过程称为回表。

也就是说,基于非主键索引的查询需要多扫描一棵索引树。因此,我们在应用中应该尽量 使用主键查询

索引维护

B+ 树为了维护索引的有序性,在插入新值的时候需要做必要的维护。

以上面这个图为例, 如果插入新的行 ID 值为 700,则只需要在 R5 的记录后面插入一个新记录。如果新插入的 ID 值为 400,就相对麻烦了,需要逻辑上挪动后面的数据,空出位置。 而更糟的情况是,如果 R5 所在的数据页已经满了,根据 B+ 树的算法,这时候需要申请 一个新的数据页,然后挪动部分数据过去。这个过程称为页分裂。 在这种情况下,性能自然会受影响。 除了性能外,页分裂操作还影响数据页的利用率。原本放在一个页的数据,现在分到两个 页中,整体空间利用率降低大约 50%。 当然有分裂就有合并。当相邻两个页由于删除了数据,利用率很低之后,会将数据页做合 并。合并的过程,可以认为是分裂过程的逆过程。

自增主键是指自增列上定义的主键,在建表语句中一般是这么定义的: NOT NULL PRIMARY KEY AUTO_INCREMENT。

自增主键,插入新记录的时候可以不指定 ID 的值,系统会获取当前 ID 最大值加 1 作为下一条记录的 ID 值。

主键长度越小,普通索引的叶子节点就越小,普通索引占用的空间也就越小。 所以,从性能和存储空间方面考量,自增主键往往是更合理的选择。