MySQL

存储引擎

MySQL5.5引入InnoDB作为默认引擎,支持事务和行级锁。

并发事务的问题

  • 脏读:一个事务正在访问数据并进行修改,而这个修改还没提交,但另一个事务访问到了修改后的数据(dirty data)。
  • 丢失修改:两个事务同时修改一个数据,前者的修改内容被丢失。
  • 不可重复读:在一个事务内多次访问同一数据,由于其他事务的修改,导致数据前后不一致。
  • 幻读:类似不可重复读,但现象为后续的访问比之前的访问多了一些原本不存在的数据,或者原先的部分数据消失了(好像发生幻觉)。

事务隔离级别

  • READ-UNCOMMITTED(读取未提交):允许读取尚未提交的数据变更。可能导致脏读、不可能重复读、幻读。
  • READ-COMMITTED(读取已提交):允许读取并发事务已提交的数据。可避免脏读,但不可能重复读和幻读仍会发生。
  • REPEATABLE-READ(可重复读):保证事务内对同一字段的多次读取结果一致,除非是数据被本事务修改。可避免脏读和不可重复读,但幻读仍会发生。
  • SERIALIZABLE(可串行化):所有事务串行执行,完全服从ACID。一般在分布式事务的情况会用到。

InnoDB锁算法

  • Record Lock:记录锁,对单个行记录上锁
    行锁通过索引项实现的,这也意味着只有通过索引检索数据时,InnoDB才会使用行锁,否则将使用表锁! 对于普通索引,先锁普通索引,再锁主键索引。

  • Gap Lock:间隙锁,锁定一个范围,不包括记录本身

  • Next-key Lock:临键锁,锁定一个范围,包括记录本身

Q:行锁会产生死锁吗?如何解决?
A:会,如两个事物反序修改两行就会造成死锁。解决办法有两种:一是设一个锁超时时间,到期自动释放。InnoDB的innodb_lock_wait_timeout默认50秒;二是进行死锁检测,发现死锁后主动回滚链条中的一个事务,需消耗CPU资源。

Q:InnoDB默认隔离级别是可重复读,怎么实现的?
A:使用MVCC(多版本并发控制)实现的。每行记录后面都有隐藏字段:DB_TRX_ID(记录插入或更新该行的事务的ID)、DB_ROLL_PTR(指向undo log 记录的回滚指针)。每开启一个事务时,系统给该事务分配一个递增的事务ID,在事务执行第一条语句时生成一个事务快照ReadView,包含如下信息:
trx_ids:当前系统中还未提交的事务ID列表。
up_limit_id:trx_ids 中最小事务ID。
low_limit_id:trx_ids 中最大事务ID。
creator_trx_id:本事务 Id。
于是,可根据如下步骤判断记录是否对本事务可见:
1.若DB_TRX_ID=creator_trx_id,说明是本事务修改的记录,可见;
2.若DB_TRX_ID<up_limit_id,说明是本事务快照前已提交的记录,可见;
3.若DB_TRX_ID>low_limit_id,说明是本事务快照时尚未提交的记录,不可见;
4.若DB_TRX_ID在[up_limit_id,low_limit_id]间,再判断DB_TRX_ID是否在trx_ids列表中:若在,说明是本事务快照时尚未提交的记录,不可见;若不在,说明是本事务快照前已提交的记录,可见。
对于不可见的记录,则通过DB_ROLL_PTR指针找到其上一个版本,重新进行比较,直到找到一个对当前事务可见的版本为止。
针对删除这一特殊的更新操作,设置了一个额外的标记位delete_bit来标识记录是否被删除。进行上述可见性判断时,若delete_bit被标记,则直接跳过该版本,通过DB_ROLL_PTR找上一个版本重新判断。

Q:那MVCC能解决幻读吗?
A:首先了解两个概念:
快照读:生成一个事务快照ReadView,之后都基于快照获取数据。普通的SELECT语句就是快照读。
当前读:读取记录的最新版本。UPDATE/INSERT/DELETE以及SELECT…FOR UPDATE都是当前读。
对于快照读,基于ReadView必然不会看到新插入的记录,所以幻读问题不存在;而对于当前读,MVVC无法解决幻读。但InnoDB默认开启间隙锁,来避免这一问题。
如事务A包含语句select * from user where id < 10 for update,而事务B插入了一条id=2的数据。当开启Gap Lock时,事务A会锁住id<10的整个范围,因此事务B无法插入数据,从而防止了幻读。

索引

Hash

hash表是键值对的集合,通过key计算对应的index,从而快速获取value。

	index = hash(key) % array_size

hash冲突问题一般通过链地址法来解决,优秀的哈希算法应保证数据尽量均分在整个哈希数组中。

B Tree

  • B树
    多路平衡查找树,n路代表每个节点最多有n个子节点。每个节点存储①key ②指向下一层节点的指针 ③key数据的地址。
  • B+树
    B树的变种,区别:①只有叶节点才存key数据的地址 ②叶节点存有指向相邻叶节点的指针
    在这里插入图片描述
    InnoDB中,主键索引的叶节点存放key和key对应的数据行,辅助索引存放的是key和key对应的主键值。因此使用辅助索引时通常会查两次。

Q:MySQL为什么不使用hash、B树、红黑树作为索引的数据结构?
A:
①只查单条记录时,hash索引效率很高,但hash索引不支持范围查询。
②如果在内存中,红黑树的查找效率比B树更高;但由于红黑树是二叉树,数据量大时树的层数很高,而从树的根节点向下寻找的过程中,每读一个节点都相当于一次IO操作,性能影响较大。
③B树在进行范围查询时,需要做局部的中序遍历,可能涉及跨层访问,需要额外的IO操作。

主键索引

数据表主键列使用的索引,一张表只能有一个主键,且不能为null。
InnoDB中,若没有显示指定表的主键,则选择有唯一索引的字段作为主键;若也没有,则创建一个隐藏的自增字段row_id作为主键。

聚簇索引

索引和数据行存在一起,即找到索引就找到了数据。
优点是查询快,缺点是更新代价大。

回表查询

非聚簇索引查到key对应的主键值,还需再按主键索引查一遍。

覆盖索引

当索引上包含了查询语句中的所有列时,我们无需进行回表查询就能拿到所有的请求数据,因此速度会很快。

联合索引

底层同样是一颗B+树,首先按照第一个索引排序,在第一个索引相同的情况下,再按第二个索引排序,依次类推。

日志

redo log

InnoDB通过重做日志在崩溃后恢复数据,保证数据的持久性。
MySQL交互数据以页为单位,加载出来的数据页放到Buffer Pool中。
redo log
参数innodb_flush_log_at_trx_commit支持三种策略:
0:表示每次事务提交时不进行刷盘操作
1:表示每次事务提交时都将进行刷盘操作(默认值)
2:表示每次事务提交时都只把 redo log buffer 内容写入 page cache
刷盘
Q:每次修改后把数据页刷盘不行吗,为什么要刷redo log?
A:数据页大小16KB,而每次修改通常涉及几Byte数据;并且数据页刷盘是随机写,而redo log是顺序写。因此,使用redo log记录修改内容,性能远超刷数据页的方式,数据库的并发能力更强。

binlog

redo log是物理日志,记录内容是“在某个数据页上做了什么修改”;而binlog是逻辑日志,记录内容是所有涉及更新数据的逻辑操作,如“给id=2这一行的x字段加1”。
binlog

undo log

所有事务的修改都会先记录到回滚日志中,并优先于数据持久化到磁盘。若事务执行过程发生异常,则可利用回滚日志将数据回滚到修改前的样子。

主从同步

原理

异步复制

  1. 从库创建一个I/O线程,请求主库的binlog,并写入relay log
  2. 主库创建一个dump线程,把binlog发送给从库
  3. 从库创建一个SQL线程,读取relay log执行更新语句

优点

  1. 从库作为数据的热备,一旦主库出现问题可进行替换,保证系统的高可用
  2. 随着业务量越来越大,单机无法满足,多库存储可降低磁盘访问频率
  3. 读写分离,使数据库能支撑更大的并发

问题

Q:异步复制时主库宕机数据丢失怎么办?
A:使用半同步复制。主库将更新语句写入binlog后,先不返回客户端,而是将binlog传给从库,待从库写入relay log并返回确认后,再通知客户端成功。
全同步复制:待所有从库都返回确认才通知客户端。

Q:主库写压力大,从库复制出现延迟怎么办?
A:使用并行复制,从库多个SQL线程执行relay log。基于组提交保证事务间没有冲突。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值