MySQL流水账(一)

1、MySQL常见的三种存储引擎:InnoDB、MyISAM、MEMORY

2、MySQL基本架构示意图

MySQL流水账(一)_第1张图片

 3、MySQL 里面最重要的两个日志,即物理日志 redo log (重做日志)和逻辑日志 binlog(归档日志)。redo log 用于保证 crash-safe 能力。

innodb_flush_log_at_trx_commit 这个参数设置成 1 的时候,表示每次事务的 redo log 都直接持久化到磁盘。这个参数我建议你设置成 1,这样可以保证 MySQL 异常重启之后数据不丢失。

sync_binlog 这个参数设置成 1 的时候,表示每次事务的 binlog 都持久化到磁盘。这个参数我也建议你设置成 1,这样可以保证 MySQL 异常重启之后 binlog 不丢失。

4、redo log示意图;write pos是当前记录的位置,checkpoint是当前要擦除的位置

MySQL流水账(一)_第2张图片

5、update语句执行流程;图中浅色框表示是在InnoDB内部执行的,深色框表示是在执行器中执行的

MySQL流水账(一)_第3张图片

6、SQL 标准的事务隔离级别包括:读未提交(read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(serializable )。下面我逐一为你解释:

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

读未提交:别人改数据的事务尚未提交,我在我的事务中也能读到。
读已提交:别人改数据的事务已经提交,我在我的事务中才能读到。
可重复读:别人改数据的事务已经提交,我在我的事务中也不去读。
串行:我的事务尚未提交,别人就别想改数据。
这4种隔离级别,并行性能依次降低,安全性依次提高。

7、常见的索引模型:哈希表、有序数组、二叉搜索树

哈希表:键-值(key-value)存储数据,多key值相同的情况下采用链表解决,哈希表适用于只有等值查询的场景,示意图如下:

MySQL流水账(一)_第4张图片

 有序数组:在等值查询和范围查询场景中的性能都非常优秀;更新数据效率低;查询采用二分法,时间复杂度为O(log(N));只适用于静态存储引擎,示意图如下:

MySQL流水账(一)_第5张图片

二叉搜索树:每个节点的左儿子小于父节点,父节点又小于右儿子;查询和更新复杂度:O(log(N));由于树高问题需要多次读取磁盘降低查询速度,所以索引模型多采用N叉树而非二叉树,示意图入下:

MySQL流水账(一)_第6张图片

 8、InnoDB的索引模型是B+树,示意图如下:

MySQL流水账(一)_第7张图片

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

如果语句是 select * from T where ID=500,即主键查询方式,则只需要搜索 ID 这棵 B+ 树;

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

显然,主键长度越小,普通索引的叶子节点就越小,普通索引占用的空间也就越小。

10、索引维护

一个数据页满了,按照B+Tree算法,新增加一个数据页,叫做页分裂,会导致性能下降。空间利用率降低大概50%。当相邻的两个数据页利用率很低的时候会做数据页合并,合并的过程是分裂过程的逆过程。

从性能和存储空间方面考量,自增主键往往是更合理的选择。

11、覆盖索引

某索引已经覆盖了查询需求,称为覆盖索引。由于覆盖索引可以减少树的搜索次数,显著提升查询性能,所以使用覆盖索引是一个常用的性能优化手段。

12、最左前缀原则

B+Tree这种索引结构,可以利用索引的"最左前缀"来定位记录;最左前缀可以是联合索引的最左N个字段,也可以是字符串索引的最左M个字符;只要满足最左前缀,就可以利用索引来加速检索。

在建立联合索引的时候,如何安排索引内的字段顺序。

第一原则是,如果通过调整顺序,可以少维护一个索引,那么这个顺序往往就是需要优先考虑采用的;其次要考虑的原则就是空间(用字段小的建立单字段索引)。

13、索引下推

在MySQL5.6之前,只能从根据最左前缀查询到ID开始一个个回表;到主键索引上找出数据行,再对比字段值。MySQL5.6引入的索引下推优化,可以在索引遍历过程中,对索引中包含的字段先做判断,直接过滤掉不满足条件的记录,减少回表次数。

mysql> select * from tuser where name like '张 %' and age=10 and ismale=1;

MySQL流水账(一)_第8张图片 无索引下推执行流程 

 

MySQL流水账(一)_第9张图片 索引下推执行流程

14、MySQL锁 

根据加锁的范围,MySQL里面的锁大致可以分成全局锁、表级锁和行锁三类

15、全局锁

顾名思义,全局锁就是对整个数据库实例加锁。MySQL 提供了一个加全局读锁的方法,命令是 Flush tables with read lock (FTWRL)。当你需要让整个库处于只读状态的时候,可以使用这个命令,之后其他线程的以下语句会被阻塞:数据更新语句(数据的增删改)、数据定义语句(包括建表、修改表结构等)和更新类事务的提交语句。

全局的典型使用场景,做全库逻辑备份;

风险:
1.如果在主库备份,在备份期间不能更新,业务停摆
2.如果在从库备份,备份期间不能执行主库同步的binlog,导致主从延迟
官方自带的逻辑备份工具mysqldump,当mysqldump使用参数--single-transaction的时候,会启动一个事务,确保拿到一致性视图,而由于MVCC的支持,这个过程中数据是可以正常更新的。一致性读是好,但是前提是引擎要支持这个隔离级别。所以,single-transaction方法只适用于所有的表使用事务引擎的库。

如果要全库只读,为什么不使用set global readonly=true的方式?
1.在有些系统中,readonly的值会被用来做其他逻辑,比如判断主备库。所以修改global变量的方式影响太大。
2.在异常处理机制上有差异。如果执行FTWRL命令之后由于客户端发生异常断开,那么MySQL会自动释放这个全局锁,整个库回到可以正常更新的状态。而将整个库设置为readonly之后,如果客户端发生异常,则数据库就会一直保持readonly状态,这样会导致整个库长时间处于不可写状态,风险较高。

16、表级锁

MySQL里面表级锁有两种,一种是表锁,一种是元数据锁(meta data lock,MDL)
表锁的语法是:lock tables ... read/write,可以用unlock tables主动释放锁,也可以在客户端断开的时候自动释放。lock tables语法除了会限制别的线程的读写外,也限定了本线程接下来的操作对象。
对于InnoDB这种支持行锁的引擎,一般不使用lock tables命令来控制并发,毕竟锁住整个表的影响面还是太大。
MDL:不需要显式使用,在访问一个表的时候会被自动加上。
MDL的作用:保证读写的正确性。
在对一个表做增删改查操作的时候,加MDL读锁;当要对表做结构变更操作的时候,加MDL写锁。
读锁之间不互斥。读写锁之间,写锁之间是互斥的,用来保证变更表结构操作的安全性。
MDL 会直到事务提交才会释放,在做表结构变更的时候,一定要小心不要导致锁住线上查询和更新。
如何安全地给小表加字段?比较理想的机制是,在 alter table 语句里面设定等待时间,如果在这个指定的等待时间里面能够拿到 MDL 写锁最好,拿不到也不要阻塞后面的业务语句,先放弃。之后开发人员或者 DBA 再通过重试命令重复这个过程。示例如下:
ALTER TABLE tbl_name NOWAIT add column ...
ALTER TABLE tbl_name WAIT N add column ...

17、行级锁

两阶段锁:在 InnoDB 事务中,行锁是在需要的时候才加上的,但并不是不需要了就立刻释放, 而是要等到事务结束时才释放。这个就是两阶段锁协议。
建议:如果你的事务中需要锁多个行,要把最可能造成锁冲突、最可能影响并发度的锁尽量往后放。
死锁:当并发系统中不同线程出现循环资源依赖,涉及的线程都在等待别的线程释放资源时,就会导致这几个线程都进入无限等待的状态。
解决方案:
1、通过参数 innodb_lock_wait_timeout 根据实际业务场景来设置超时时间,InnoDB引擎默认值是50s。
2、发起死锁检测,发现死锁后,主动回滚死锁链条中的某一个事务,让其他事务得以继续执行。将参数 innodb_deadlock_detect 设置为 on,表示开启这个逻辑(默认是开启状态)。
如何解决热点行更新导致的性能问题?
1、如果你能确保这个业务一定不会出现死锁,可以临时把死锁检测关闭掉。一般不建议采用
2、控制并发度,对应相同行的更新,在进入引擎之前排队。这样在InnoDB内部就不会有大量的死锁检测工作了。
3、将热更新的行数据拆分成逻辑上的多行来减少锁冲突,但是业务复杂度可能会大大提高。

innodb行级锁是通过锁索引记录实现的,如果更新的列没建索引是会锁住整个表的。

18、InnoDB 的行数据有多个版本,每个数据版本有自己的 row trx_id,每个事务或者语句有自己的一致性视图。普通查询语句是一致性读,一致性读会根据 row trx_id 和一致性视图确定数据版本的可见性。

  • 对于可重复读,查询只承认在事务启动前就已经提交完成的数据;
  • 对于读提交,查询只承认在语句启动前就已经提交完成的数据;

InnoDB 利用了“所有数据都有多个版本”的这个特性,实现了“秒级创建快照”的能力。

19、读提交的逻辑和可重复读的逻辑类似,它们最主要的区别是:

  • 在可重复读隔离级别下,只需要在事务开始的时候创建一致性视图,之后事务里的其他查询都共用这个一致性视图;
  • 在读提交隔离级别下,每一个语句执行前都会重新算出一个新的视图。

20、更新数据都是先读后写的,而这个读,只能读当前的值,称为“当前读”(current read)。

21、普通索引和唯一索引应该怎么选择。其实,这两类索引在查询能力上是没差别的,主要考虑的是对更新性能的影响。所以,我建议你尽量选择普通索引。

22、redo log 主要节省的是随机写磁盘的 IO 消耗(转成顺序写),而 change buffer 主要节省的则是随机读磁盘的 IO 消耗。

你可能感兴趣的:(MySQL)