从以下模块展开论述:
- 存储(文件系统):机械硬盘/SSD固态硬盘
- 程序实例
- 存储管理:为了减少磁盘I/O的次数,一次读取多行数据,通常是读入一个数据块或页;
- 缓存机制:LRU等
- SQL解析:将部分SQL结果缓存
- 日志管理:主从同步、灾难恢复
- 权限划分:支持多用户
- 容灾机制
- 索引管理:优化数据查询效率
- 锁管理:支持并发操作
为了避免全表检索的低效,借鉴目录的思想,快速检索。
- 主键、唯一键以及普通键等
为了使得查询高效:
- 生成索引,建立二叉查找树进行二分查找
- 生成索引,建立B树进行查找
- 生成索引,建立B+树进行查找
- 生成索引,建立Hash结构进行查找
B+树更适合用来用存储索引
- B+树的磁盘读写代价更低,因为非叶子结点只存储索引,其内部存储空间更小,这样一次性读入内存的数据就更多,相对来说磁盘的I/O次数就减少了。
- B+树的查询效率更加稳定,由于每一条查询都是从根结点到叶子结点,所有关键字的查询路径相同。
- B+树更有利于对数据库的扫描,由于B+树的叶子结点由链表连接,这样使得一次查询就能够读取到一定范围的数据,更加有利于数据库的范围查询。
Hash索引的缺点
- 仅仅能够满足“=”,“IN”,不能使用范围查询;
- 索引无法用来数据的排序操作;
- 不能利用部分索引键查询,即组合索引;
- 不能避免表扫描;
- 遇到大量Hash冲突的情况效率会大大降低
不同之处在于,叶子结点存放的是否是一整行的信息。
密集索引
也称作聚集索引,叶子结点保存的不仅仅是键值,还保存了位于同一行记录中其他列的信息。由于密集索引决定了表的物理排列顺序,一个表只能有一个物理排列顺序,所以一个表只能创建一个密集索引。
- 文件中的每个搜索码值都对应一个索引值
稀疏索引
也称作非聚集索引,叶子结点仅仅保存了键位信息以及该行的地址,有的稀疏索引是仅仅保存了键位信息及其主键。定位到叶子结点后,仍然需要地址或主键进一步定位信息。稀疏索引的存在并不会影响密集索引的组织,因此一张表上可以有多个稀疏索引。
- 只为索引码的某些值建立索引项
MyISAM:
所有索引都是稀疏索引,其中索引和数据是分开存储的。
InnoDB:
行数据和主键索引是保存在同一个文件中的,有且仅有一个密集索引,这个密集索引的选取规则:
- 若一个主键被定义,该主键则作为密集索引;
- 若没有主键被定义,该表的第一个唯一非空索引则作为密集索引;
- 若不满足以上条件,InnoDB内部会生成一个隐藏主键(密集索引),这个隐藏主键是一个6字节的列,会随着数据的插入而递增;
- 非主键索引,就是稀疏索引的叶子结点并不存储行数据的物理地址,而是存储相关键位和其对应的主键值,包含两次查找,一次是查找次级索引自身,再查找主键
衍生出来的问题,以MySQL为例
具体场景具体分析,只提出大致思路:
- 1. 根据慢日志定位慢查询SQL
- 2. 使用EXPLAIN等工具分析SQL语句
EXPLAIN关键字段
- type
性能由高到低,index和all表示本次查询是全表扫描。
- extra
extra中出现以下两项意味着MySQL根本不能使用索引,效率会受到重大影响。应尽可能对此进行优化:
extra项 | 说明 |
---|---|
Using filesort | 表示MySQL会对结果使用一个外部索引排序,而不是从表里按索引次序读到相关内容,可能在内存或磁盘上进行排序。MySQL中无法利用索引完成的排序操作成为“文件排序”。 |
Using temporary | 表示MySQL在对查询结果排序时使用临时表。常见于排序order by和分组查询group by。 |
- 3. 修改SQL或尽量让SQL走索引
- 最左前缀匹配原则,MySQL会一直向右匹配直到遇到范围查询(> 、<、between、like)就停止匹配;
- =和in可以乱序,因为MySQL的查询优化器可以帮助优化成索引可以识别的形式;
MySQL在创建复合索引的规则是,首先对第一个的索引字段进行排序,在此基础上再对第二个索引字段进行排序,实现了类似于order by 字段1 再order by 字段2。所以第一个字段是绝对有序的,而第二个字段就是无序的。因此直接使用第二个字段进行条件查询就无法使用复合索引。
- 数据量小的表不需要建立索引,建立会增加额外的索引开销;
- 数据变更需要维护索引,因此更多的索引意味着更多的维护成本;
- 更多的索引意味着也需要更多的空间;
- MyISAM默认用的是表级锁,不支持行级锁;
- InnoDB默认用的是行级锁,也支持表级锁。当没有使用到索引的时候,使用的是表级锁。
适合的场景
MyISAM:
- 频繁执行全表count语句
- 对数据进行增删改的频率不高,查询非常频繁
- 没有事务
InnoDB:
- 数据增删改都相当频繁
- 可靠性要求比较高,要求支持事务
- 按照锁的粒度划分:可分为表级锁、行级锁、页级锁
- 按照锁的级别划分,可分为共享锁、排它锁
- 按照加锁方式划分,可分为自动锁、显示锁
- 按照操作划分,可分为DML锁、DDL锁
- 按照使用方式划分,可分为乐观锁、悲观锁
- 原子性
- 一致性
- 隔离性
- 持久性
- 更新丢失,即一个事务的更新覆盖了另一个事务的更新。——MySQL所有事务隔离级别在数据库层面上均可避免该问题。
- 脏读,即一个事务读到另一个事务未提交的数据。——已提交读(READ-COMMITTED)事务隔离级别以上可避免。
- 不可重复读,事务A多次读取同一数据,在这期间事务B修改了该部分数据并提交,而导致事务A多次读取的数据不一致。——可重复读(REPEATABLE-READ)事务隔离级别以上可避免
- 幻读,事务A读取与搜索条件的若干行,事务B以插入或删除方式来修改事务A的结果集,导致事务A像出现了幻觉一样。
- 表象:快照读(非阻塞读)--伪MVCC
- 内在:next-key锁(行锁+gap锁)
当前读和快照读
- 当前读:select...lock in share mode,s elect ... for update
- 当前读:update, delete, insert
- 快照读:不加锁的非阻塞读,select
当前读即加了锁的增删改查语句,因为读取的是当前的最新版本,并且保持其他事务不能修改
next-key锁(行锁+gap锁)
- 行锁
- Gap锁:Gap即索引树中插入新纪录的空隙,Gap锁(间隙锁),即锁定一个范围,但不包括记录本身。Gap锁的目的是为了防止同一事务的两次当前读而出现幻读的现象。Gap锁是在RR以上的隔离级别才有的。
对主键索引或唯一索引会使用Gap锁吗?
- 如果where条件全部命中(精确查询),则不会用Gap锁,只会加记录锁;
先在id上加上排它锁,再到name上加排它锁。
- 如果where条件部分命中或者全都不命中,则会加Gap锁
Gap锁会在非唯一索引或者不走索引的当前读中
- 非唯一索引
Gap都是左闭右开的区间
- 不走索引
- 数据行里的DB_TRX_ID、DB_ROLL_PTR、DB_ROW_ID字段
DB_TRX_ID: 最近一次做修改的事务的标识符
DB_ROLL_PTR: 回滚指针,指写入回滚端的日志(undo)记录
DB_ROW_ID: 行号,包含一个随着新行插入而单调递增的行ID,当有InnoDB产生聚集索引时,聚集索引会包含这个行ID的值,否则这个行ID不会出现在这个索引中。这就是InnoDB默认创建的主键字段。
- undo日志: 当对记录做了变更操作的时候就会产生undo日志,undo中存储着是老版数据,当一个旧的事务需要读取数据时,为了能读取到老版本的数据,需要顺着undo链找到其满足老版本的数据。主要分为两种:insert undo log,只在insert事务中需要,当发生回滚后立即丢弃;update undo log:事务对数据进行update和delete操作时产生的undo log,不仅在事务回滚的时候需要,还在快照读的时候需要,所以不能随便删除,只有当数据库中所使用的快照中不涉及该日志记录,对应的回滚日志文件才会被线程删除。
- read view:主要是用来做可见性判断的,即我们在执行快照读select的时候,会针对查询的数据创建一个read view来决定当前事务所能看到的是哪个版本的数据,有可能是当前最近版本的数据,也有可能是undo log中某个版本的数据。read view遵循一个可见性算法,即将一个将要修改数据DB_TRX_ID取出来与系统其它活跃事务ID做比较,若大于等于,则通过DB_ROLL_PTR取出上一层的DB_TRX_ID,直到小于当前修改数据DB_TRX_ID
RR级别下,在开启事务后的第一条快照读操作会创建一个快照,即read view。将当前系统中活跃的其他事务记录起来,此后再调用快照读的时候还是用的同一个read view。如果首次使用快照读,是在别的事务在对数据做出增删改并提交之前的,此后即便别的事务对数据进行了增删改并提交还是读不到数据变更的原因。对于RR来说,首次事务select的时机是相当重要的。
而在RC级别下,事务中每条select语句,即每次调用快照读的时候,都会创建一个新的快照。这就是在RC下能用快照读看到别的事务提交的对表记录的增删改操作。
- 满足“SELECT子句中的列名必须为分组列或列函数”,即如果用group by,那么select语句中选出的列要么是group by里用到的列,要么就是带有sum、min等列函数的列,这个仅仅针对于同一张表。
- 列函数对于group by 子句定义的每个组各返回一个结果;
- 通常与GROUP BY子句一起使用
- where是过滤行,而having是过滤组的。如果省略了group by子句,having的功能就和where相同。
- 出现在同一个SQL的顺序:WHERE>GROUP BY>HAVING