索引

为什么要用B+树

  1. B+树最大的特点:叶子节点存储数据,非叶子节点存索引字段。这样的树会更加矮胖,一般3层的B+树,就能存放千万级的数据量,并且可以把第一层放入内存,也就是只需要两次IO就可以查询到数据,查询效率高;并且查询会更加稳定,因为都需要走到叶子结点取数据(B树非叶子节点也存数据,除了查询不稳定外,在范围查询时会有更多随机IO)。
  2. B+树叶子节点之间还有指针,更适合范围查询。

    B+树一个节点一般多大

    InnoDB中默认一个节点的大小是16K,在InnoDB中被称为页,可以通过命令show variables like 'innodb_page_size';查看。

    每次查询一条数据的时候,都会把这整页16K的数据读入内存。

聚簇索引和非聚簇索引的区别

一张表一般是有一个聚簇索引,可能有多个非聚簇索引,这是因为聚簇索引是存储了我们表中每行的数据。

  • 聚簇索引也叫做索引组织表,也就是说我们存入的数据都是放在这个索引的B+树结构中,并且用主键索引来建立关系,聚簇索引中叶子节点存储的是每行的数据,非叶子节点存储的是主键值;
  • 非聚簇索引一般我们是为了查询效率而建立的,他也是B+树,叶子节点存储的是主键值,非叶子节点存储的是索引字段,当我们用这个索引字段查询时,可以先用这个字段的非聚簇索引查询得到主键,然后在到聚簇索引中根据主键值查询行记录。

    关于主键的建立

  1. 如果有设置主键,那么数据就以主键来做索引组织表;
  2. 如果没有设置主键,会选择表中一个非空唯一的字段来做主键;如果表中没有,InnoDB生成一个row_id来做主键。

    主键用自增id还是手机号?

    用自增id。因为手机号随机,插入时会有频繁的页分裂,性能差。并且手机号一般比自增id长,占用空间更大,树会稍微高一点。

    索引什么时候会失效

  3. like查询的时候以%开头

  4. 数据类型是字符串,但是查询的时候类型是数字
  5. where条件有or或者有函数
  6. 联合索引(a,b),如果where条件是b,那么索引也用不上
  7. 还有些可能mysql自己认为全表扫描要快,那可能他就不走索引了,例如觉得回表比全表扫描慢的时候

    哪些字段不适合做索引

  8. 频繁变更的字段,会导致频繁的页分裂和合并

  9. 区分度低的字段,例如性别这种
  10. 长度很长的字段,text这种,索引值太长,导致树高度增加

    MyISAM和InnoDB的区别

  11. MyLSAM没有行锁

  12. MyLSAM不支持事务
  13. 索引结构不太一样,MyLSAM虽然也是用的B+树,但索引文件和数据文件是分离的,索引文件仅保存数据记录的地址。

    读多写少的场景,用MyLSAM更合适。

char和varchar的区别

定义的varchar(40)的话,如果只存了10个字符,那内存中是10个字符,占用空间更少;char的话定义了40就是40,定长的。因此char访问速度更快点。

执行计划参数的意义

type表示连接类型。常见的有:index表示用了索引;all全表扫描;const;唯一索引等值条件是个常量,例如id=1;eq_ref唯一索引等值条件是另一个表的外键。
key就表示索引用的是哪个。
rows就是执行计划估计的需要扫描多少行数据。
extra表示额外的执行计划。常见的有:使用了排序;使用了临时表;Using index覆盖索引;Using index condition需要回表。

联合索引

我们以(a,b)两个字段建立索引,这个就是联合索引,这个索引的特点是:会以a有序为先,在a相等的时候,再按照b有序排列。
所以如果查询是select * from t where a = 1这样的查询,就会用到这个联合索引,而如果是select * from t where b=1那么就不会用到联合索引。

(a,b,c)的联合索引,在b用范围查询时,之后的c就不会走索引了。

什么是覆盖索引

select id from t where a=1这个查询在a的索引树上,id可以直接获得,不用再走回表,这样的索引查询就是覆盖索引。

事务

原子性

操作要么都成功,要么都失败回滚。实现依靠回滚日志(undo log)。

持久性

事务提交之后,不会因为宕机等情况而影响。实现依靠重做日志(redo log)。

隔离性

隔离性说的是两个事务之间都影响,两个事务如果都是读的话,那不会有什么问题,但如果有一个是写,就有可能出现问题。

读写的情况

如果一个事务写,一个事务读,那么会有3中异常读的情况: 1 脏读。读到另一个事务未提交的数据 2 不可重复读。一次事务中,读同一个数据,两次结果不一样 3 幻读。一个事务中读到的结果行数不一样

SQL标准

针对这3种读写异常的情况,有4种隔离标准: 1 读未提交 2 读已提交 3 可重复读 4 串行化

InnoDB隔离标准的实现

InnoDB默认的隔离级别是可重复读,实现是依靠mvcc。

mvcc实现原理

每行数据除了业务字段外,还有两个隐藏字段:事务id、回滚日志的指针。 可重复读隔离级别下,事务开启的时候会生成一个数组,记录当前数据库中活跃的事务id,然后当我们读一个数据的时候,会用这行数据的事务id,和事务开启时候创建的数组的事务id做对比,如果需要读以前版本的数据,根据指针去找,通过这种方式就可以确定我们读到的数据的版本

读已提交和可重复读的区别

都是通过mvcc实现的,读已提交是每次查询都会创建一个最新的事务数组,可重复读在事务开启的时候创建一个,后面都用这个。

InnoDB可重复读怎么解决幻读的

通过间隙锁和行锁来实现,这样就不能插入数据了。

一致性

一致性是数据保障一致,这其实是事务的目的,不仅需要原子性、持久性、隔离性的保障,还需要业务上的保障,不然转账扣钱了,那边钱不加上,数据就不是一致的了。

乐观锁和悲观锁

可以参考这个博客:https://www.hollischuang.com/archives/934

悲观锁

概念:当我们对数据修改时,为了避免并发修改,数据修改前进行加锁,修改完成后释放锁。也就是认为写大于读。
实现方式:用数据库的排他锁 for update,需要知道的是: for update 在事务完成之前,数据可以读,不能写。

  1. update t set num=num-1 where id=#{id} for update;

乐观锁

概念:是一种无锁操作,当我们修改数据时,不加锁,只有当出现冲突的时候才做控制。适合读大于写的场景。
实现方式:MySQL中乐观锁可以用版本号来实现,例如下面这个对表t减库存(num)的操作:

  1. update t set num=num-1 where id=#{id} and version=#{version};