请简要说明Mysql 中MyISAM 和InnoDB 引擎的区别
MyISAM 和InnoDB 都是Mysql 里面的两个存储引擎。
MyISAM 引擎的数据是通过二进制的方式存储在磁盘上,它在磁盘上体现为两个文件
一个是.MYD 文件,D 代表Data,是MyISAM 的数据文件,存放数据记录,
一个是.MYI 文件,I 代表Index,是MyISAM 的索引文件,存放索引
因为索引和数据是分离的,所以在进行查找的时候,先从索引文件中找到数据的磁盘位置,再到数据文件中找到索引对应的数据内容。
InnoDB 存储引擎中,数据同样存储在磁盘上,它在磁盘上只有一个ibd 文件,里面包含索引和数据。它的整体结构B+树,叶子节点里面存储了索引对应的数据,在通过索引进行检索的时候,命中叶子节点,就可以直接从叶子节点中取出行数据。
MyISAm 和InnoDB 的区别有4 个,
第一个,数据存储的方式不同,MyISAM 中的数据和索引是分开存储的,而InnoDB 是把索引和数据存储在同一个文件里面。
第二个,对于事务的支持不同,MyISAM 不支持事务,而InnoDB 支持ACID 特性的事务处理
第三个,对于锁的支持不同,MyISAM 只支持表锁,而InnoDB 可以根据不同的情况,支持行锁,表锁,间隙锁,临键锁
第四个,MyISAM 不支持外键,InnoDB 支持外键
因此基于这些特性,我们在实际应用中,可以根据不同的场景来选择合适的存储引擎。比如如果需要支持事务,那必须要选择InnoDB。如果大部分的表操作都是查询,可以选择MyISAM。
MySQL 优化相关的问题。
先说mysql有哪些优化点,然后分别说。
硬件层面的优化。
- 影响Mysql 性能的因素有,CPU、可用内存大小、磁盘读写速度、网络带宽从操作系层面来。比如提高带宽、使用ssd磁盘等等,配置升级等等。
架构设计层面的优化。
- 数据量上来了,要分库分表,进行读写分离来提高访问并发能力
- 热点数据,加入缓存来缓解访问压力
程序配置优化
- 配置最大连接数,是否开启binlog日志等等来优化。
sql优化
- 首当其冲就是索引了
- 慢查询的定位和排查,开启慢查询。
- 找到sql后,通过explain/desc查看执行计划,explain一般我们是关注type字段(看看是否用到索引,具体用到什么索引)、key字段(表示实际用到的索引,如果为null则没有用到)、extra字段(里面表明了很多信息,比如是否全表扫描等等)
索引优化
- 主键索引使用递增顺序数据表示,不要使用uuid等做主键防止页分裂现象。
- 加索引,避免索引列使用函数或者运算符,针对数据量大的,且查询比较频繁的表建立索引。
- 最左匹配原则,避免索引失效(联合索引下,范围查询右侧的列索引失效、字符串类型字段使用时,不加引号,索引失效、头部进行模糊匹配,则索引失效、or分开的条件字段都需要索引,否则索引失效)。
- where 子句中 like % 号需要放到右边。
- 查询减少 * ,需要字段查询,最好是覆盖索引查询 等等
索引设计原则
- 针对数据量大的,且查询比较频繁的表建立索引。一般数据量超过10w就要考虑了,几千几万条建不建立索引影响不大。
- 针对常作为查询条件(where)、排序(order by)、分组(group by)操作的字段建立索引。
- 尽量选择区分度高的列作为索引,建立唯一索引,区分度越高,使用索引的效率越高。比如用户的手机号、邮箱,姓名,对于每个用户来说他们的信息都不一样,这就是区分度高。什么性别、状态等等区分度不高,没有太大必要建立索引。
- 如果字符串类型的字段,字段的长度较长,可以针对于字段的特点,建立前缀索引。
- 尽量使用过联合索引,减少单列索引,查询时,联合索引很多时候可以覆盖索引,节省存储空间避免回表,提高查询效率。
- 要控制索引的数量,索引不是多多益善,索引越多维护索引结果的代价也就越大,会影响增删改的效率。
- 如果索引列不能存储null值,在创建表的时候使用not null约束。当优化器知道每列是否包含null值时,可以更好确定哪个索引有效于查询。
什么情况下不建索引?
我认为有几种情况不适合建立索引:
数据量太小的情况下,即使没有索引,查询的速度也比较快,这个时候建立索引反而会增加维护成本和查询时间
数据离散度不高的列,比如性别、年龄这种,创建索引反而会降低检索效率,从底层原理来说,相当于增加了B+树的扫描范围
存在函数操作的情况,如果查询条件包含函数操作,那这个时候可能不会走索引,所以建了索引意义不大
频繁变更的表,比如经常需要更新、删除或插入记录,那么对这个表建立索引的开销就会很大,甚至可能影响到整个数据库的性能。
为什么SQL 语句命中索引比不命中索引要快?
数据库索引最主要的作用就是帮助我们快速检索到想要的数据,从而不至于每次查询都做全局扫描。
假设不使用任何算法的情况下,我们要查询10 万条记录中的某一条,在最坏的情况下需要遍历10 万次。
但如果使用二分查找算法,则只需要进行log2 20000 次,也就是14.287712 次即可。这意味着我们只需对排序后的值进行14 次搜索,就可以使用二分查找到想要的唯一值,常见的索引数据结构有B 树和B+树。
当我们需要查找目标数据的时候,根据索引从B+树中去查找目标数据就行了。由于B+树的子树比较多,所以,只需要较少次数的磁盘IO 就能够查找到目标数据。
B+树的叶子节点挂着索引,如果命中了,直接返回数据,不命中的话,还需要回表去非叶子节点上面查找。
虽然,使用索引能减少磁盘IO 次数,提高查询效率,但是,索引也不能建立太多。如果一个表中所有字段的索引很大,也会导致性能l 下降。想象一下,如果一个索引和一个表一样长,那么它将再次成为一个需要检查的开销。这就好比字典的目录非常详细,但是其长度已经和所有的文字一样长,这个时候目录本身的效率就大大下降了。
Mysql 事务的实现原理
Mysql 里面的事务,满足ACID 特性,所以在我看来,Mysql 的事务实现原理,就是InnoDB 是如何保证ACID 特性的。
原子性,
也就是需要保证多个DML 操作是原子的,要么都成功,要么都失败。那么,失败就意味着要对原本执行成功的数据进行回滚,所以InnoDB 设计了一个UNDO_LOG 表,在事务执行的过程中,把修改之前的数据快照保存到UNDO_LOG 里面,一旦出现错误,就直接从UNDO_LOG里面读取数据执行反向操作就行了。
一致性,
表示数据的完整性约束没有被破坏,这个更多是依赖于业务层面的保证,数据库本身也提供了一些,比如主键的唯一余数,字段长度和类型的保证等等
隔离性,
也就是多个并行事务对同一个数据进行操作的时候,如何避免多个事务的干扰导致数据混乱的问题。提供了四种隔离级别的实现。分别是:RU(未提交读)RC(已提交读)RR(可重复读)Serializable(串行化)InnoDB 默认的隔离级别是RR(可重复读),然后使用了MVCC 机制解决了脏读和不可重复读的问题,然后使用了行锁/表锁的方式解决了幻读的问题。
持久性,
也就是只要事务提交成功,那对于这个数据的结果的影响一定是永久性的。不能因为宕机或者其他原因导致数据变更失效。
理论上来说,事务提交之后直接把数据持久化到磁盘就行了,但是因为随机磁盘IO 的效率确实很低,所以InnoDB 设计了Buffer Pool 缓冲区来优化,也就是数据发生变更的时候先更新内存缓冲区,然后在合适的时机再持久化到磁盘。
那在持久化这个过程中,如果数据库宕机,就会导致数据丢失,也就无法满足持久性了。所以InnoDB 引入了Redo_LOG 文件,这个文件存储了数据被修改之后的值,当我们通过事务对数据进行变更操作的时候,除了修改内存缓冲区里面的数据以外,还会把本次修改的值追加到REDO_LOG 里面。
当提交事务的时候,直接把REDO_LOG 日志刷到磁盘上持久化,一旦数据库出现宕机,在Mysql 重启在以后可以直接用REDO_LOG 里面保存的重写日志读取出来,再执行一遍从而保证持久性。
Mysql 主从集群同步延迟问题怎么解决
复制过程分为几个步骤:
- 主库的更新事件(update、insert、delete)被写到binlog
- 从库发起连接,连接到主库。
- 此时主库创建一个binlog dump thread,把binlog 的内容发送到从库。
- 从库启动之后,创建一个I/O 线程,读取主库传过来的binlog 内容并写入到relay log
- 从库还会创建一个SQL 线程,从relay log 里面读取内容,从Exec_Master_Log_Pos 位置开始执行读取到的更新事件,将更新内容写入到slave的db 主从数据同步涉及到网络数据传输,由于网络通信的延迟以及从库数据处理的效率问题,就会导致主从数据同步延迟的情况。
一般可以通过以下几个方法来解决
- 设计一主多从来分担从库压力,减少主从同步延迟问题
- 如果对数据一致性要求高,在从库存在延迟的情况下,可以强制走主库查询数据
- 可以在从库上执行show slave status 命令,获取seconds_behind_master 字段的延迟时间,然后通过sleep 阻塞等待固定时间后再次查询.
- 通过并行复制解决从库复制延迟的问题
主从复制的场景无法避免同步延迟的问题,如果一定要用强一致方案,那就应该考虑其他能够实现一致性场景的技术方案。
Mysql 的binlog 有几种格式?分别有什么区别
准确来说,Binlog 有三种格式:statement,row 和mixed。
statement,
记录的是SQL 的原文。好处是,不需要记录每一行的变化,减少了binlog 日志量,节约了IO,提高性能。由于sql 的执行是有上下文的,因此在保存的时候需要保存相关的信息,同时还有一些使用了函数之类的语句无法被记录复制。
row,
不记录sql 语句上下文相关信息,仅保存哪条记录被修改。记录单元为每一行的改动,基本是可以全部记下来但是由于很多操作,会导致大量行的改动(比如alter table),因此这种模式的文件保存的信息太多,日志量太大。
mixed,
一种折中的方案,普通操作使用statement 记录,当无法使用statement的时候使用row。
索引有哪些缺点以及具体有哪些索引类型
第一个,索引的优缺点 优点:
- 合理的增加索引,可以提高数据查询的效率,减少查询时间
- 有一些特殊的索引,可以保证数据的完整性,比如唯一索引
缺点: - 创建索引和维护索引需要消耗时间
- 索引需要额外占用物理空间
- 对创建了索引的表进行数据的增加、修改、删除时,会同步动态维护索引,这个部分会造成性能的影响
第二个,索引的类型
- 主键索引: 数据列不允许重复,不允许为NULL,一个表只能有一个主键。
- 唯一索引: 数据列不允许重复,允许为NULL 值,一个表允许多个列创建唯一索引。
- 普通索引: 基本的索引类型,没有唯一性的限制,允许为NULL 值。
- 全文索引:是目前搜索引擎使用的一种关键技术,对文本的内容进行分词、搜索。
- 覆盖索引:查询列要被所建的索引覆盖,不必读取数据行
- 组合索引:多列值组成一个索引,用于组合搜索,效率大于索引合并
什么是聚集索引和非聚集索引
简单来说,聚集索引就是基于主键创建的索引,除了主键索引以外的其他索引,称为非聚集索引,也叫做二级索引。
由于在InnoDB 引擎里面,一张表的数据对应的物理文件本身就是按照B+树来组织的一种索引结构,而聚集索引就是按照每张表的主键来构建一颗B+树,然后叶子节点里面存储了这个表的每一行数据记录。
所以基于InnoDB 这样的特性,聚集索引并不仅仅是一种索引类型,还代表着一种数据的存储方式。
同时也意味着每个表里面必须要有一个主键,如果没有主键,InnoDB 会默认选择或者添加一个隐藏列作为主键索引来存储这个表的数据行。一般情况是建议使用自增id 作为主键,这样的话id 本身具有连续性使得对应的数据也会按照顺序存储在磁盘上,写入性能和检索性能都很高。否则,如果使用uuid 这种随机id,那么在频繁插入数据的时候,就会导致随机磁盘IO,从而导致性能较低。
InnoDB 里面只能存在一个聚集索引,原因很简单,如果存在多个聚集索引,那么意味着这个表里面的数据存在多个副本,造成磁盘空间的浪费,以及数据维护的困难。
执行SQL 响应比较慢,你有哪些排查思路?
一般来说,SQL响应慢有如下问题:
第1 个原因:没有索引或者导致索引失效。
可以打开MySQL 的慢查询日志,收集一段时间的慢查询日志内容,然后找出耗时最长的SQL 语句, 对这些SQL 语句进行分析。通过explain分析,看看是否索引失效。或者没有走索引。
第2 个原因:单表数据量数据过多,导致查询瓶颈
单表数据量数据过多,导致查询瓶颈的情况。即使SQL 语句走了索引,表现性能也不会特别好。这个时候我们需要考虑对表进行切分。表切分规则一般分为两种,一种是水平切分,一种是垂直切分。
第3 个原因:网络原因或者机器负载过高。
可以进行读写分离,多主多从的方式进行部署。
第4 个原因:热点数据导致单点负载不均衡。
除了对数据库本身的调整以外,还可以增加缓存。将查询比较频繁的热点数据预存到缓存当中
为什么SQL 语句命中索引比不命中索引要快?
mysql的隔离级别
脏读:一个事物读到另外一个事务还没提交的数据。
如事务A中更新zhagnsan的money为2000元,但是事务A还未提交。此时事务B读取到A的更新但未提交的money为2000,这时候就叫脏读。
不可重复读:一个事物先后读取同一条记录,但两次读取的数据不同,为不可重复读。
如事务A中第一次查询zhangsan的money为1000元,A事务还未提交。此时事务B把zhangsan的money改为了2000元,事务A中再次查询zhangsan的money为2000元。这就是不可重复读。
幻读:一个事物按照条件查询数据时,没有对应的数据行,但是在插入数据时,又发现这行数据已经存在,好像出现了幻影一样。
如邮箱字段唯一,A事务开启的时候,还没有zhangsan@qq.com这条数据,在A事务中插入zhangsan@qq.com之前,B事务已经插入zhangsan@qq.com这条数据了。由于邮箱冲突,则A事务插入zhangsan数据失败。
综合并发事务问题,事务的隔离级别,就是解决这些问题。
隔离级别有以下:
隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
Read uncommitted | 会 | 会 | 会 |
Read committed(oracle默认) | 不会 | 会 | 会 |
Repeatable Read(mysql默认) |
不会 | 不会 | 会 |
Serializable | 不会 | 不会 | 不会 |
事务的隔离级别越高,数据越安全,但性能越低,具体得看业务容忍度。 还有就是加锁,一般问到如何解决脏读、不可重复读、幻读,就是
- 主要是通过事务的隔离级别来处理。
- 加锁,加行锁、间隙锁、表锁,但是加锁可能会造成性能问题,合理加锁。
- 合理设计事务和查询,尽量减少长事务、减少不必要的范围查询和重复查询,以及避免在事务中插入大量数据等等
说一下你对行锁、临键锁、间隙锁的理解
行锁,也称为记录锁。
当我们针对主键或者唯一索引加锁的时候,Mysql 默认会对查询的这一行数据加行锁,避免其他事务对这一行数据进行修改。
间隙锁,顾名思义,就是锁定一个索引区间。
在普通索引或者唯一索引列上,由于索引是基于B+树的结构存储,所以默认会存在一个索引区间。而间隙锁,就是某个事物对索引列加锁的时候,默认锁定对应索引的左右开区间范围。在基于索引列的范围查询,无论是否是唯一索引,都会自动触发间隙锁。
临键锁,(它相当于行锁+间隙锁的组合,也就是它的锁定范围既包
含了索引记录,也包含了索引区间它会锁定一个左开右闭区间的数据范围。
总的来说,行锁、临键锁、间隙锁只是表示锁定数据的范围,最终目的是为了解决幻读的问题。而临键锁相当于行锁+间隙锁,因此当我们使用非唯一索引进行精准匹配的时候,会默认加临键锁,因为它需要锁定匹配的这一行数据,还需要锁定这一行数据对应的左开右闭区间。因此在实际应用中,尽可能使用唯一索引或者主键索引进行查询,避免大面积的锁定造成性能影响。
b 树和b+树的理解,为什么mysql选择B+树。
先说B树:
B树是一种平衡树,由节点组成,每个阶段包含多个键值对,节点既有存储数据,又有存储索引,使得每个节点都能够直接执行数据,查找时,由根节点开始沿着路径逐级搜索,节点内部有序,能够快速定位。
再说B+树:
类似于B树,但在叶子节点上有所不同。所有叶子节点通过指针连接成一个链表,形成顺序访问的结构。B+树内部节点仅存储键值对的索引,数据全部存储在叶子节点中。
再说为什么选择B+树:
- B+树的有序叶子节点使得范围查询更高效,适用于数据库索引的大部分查询。
- B+树的叶子节点是链表,支持更快的顺序访问,利于范围扫描和顺序查询。
- B+树的结构利于减少磁盘IO,因为范围查询时可以更好利用连续存储的特征。
为什么一线互联网公司严禁使用存储过程?
- 存储过程不好调试,一旦涉及到非常复杂的逻辑,定位问题的时候比较麻烦
- 存储过程的一致性很差, 如果从Oracle 迁移到Mysql,涉及到部分数据库独有特性的时候,整个存储过程就需要重写,不仅成本高,而且还有可能对上游业务造成影响
- 存储过程的管理比较困难,一旦存储过程的量比较大的时候,就会陷入到一个混乱的状态
- 存储过程的优化和维护很麻烦, 随着业务的发展,数据库的表结构也许会发生变化,这些变化需要同步给到存储过程,并且有可能原来的SQL 语句的执行计划不是最优的,也需要重新维护。
在互联网公司,业务的迭代速度是日新月异的,把业务逻辑沉淀在应用程序中,反而更加灵活。
为什么SQL 语句不要过多的join?
我认为主要有两个方面的原因:
性能问题:每个join 操作都需要对两个或多个表进行连接操作,这个操作需要消耗大量的计算资源和时间,如果join 操作过多,会导致SQL 的执行效率降低,从而影响整个系统的性能。
可读性和维护性问题:join 操作会使SQL 语句变得复杂,难以理解和维护,特别是当join 操作涉及到多个表的时候,SQL 语句的复杂度会呈现指数级增长,给代码的可读性和可维护性带来挑战。
limit 1000000,10 加载很慢该怎么优化
如果id 是连续的,可以直接使用这样的方式。select id,name from user where id>1000000 limit 10.
这种方式其实就是先对数据做过滤,然后再limit,可以有效提升查询效率
通过order by+索引来解决 select id,name from user order by id limit 1000000,10
如果要查询所有的数据,那么可以先查询出这10条的id,然后再查询。如 select * from user where in id=(select id from user order by id limit 1000000,10)
,因为id是有索引的,索引查找都是很快的。
假如我Myql 的表很大,进行分页的时候,limit1000000 加载很慢的话,你是怎么解决的呢?
针对这种情况,有以下几种方案可以进行一定的优化。 1.如果id 是趋势递增的,那么每次查询都可以返回这次查询最大的ID,然后下次查询,加上大于上次最大id 的条件,这样会通过主键索引去扫描,并且扫描数量会少很多很多。因为只需要扫描where条件的数据
2.先limit 出来主键ID,然后用主表跟查询出来的ID 进行inner join 内连接,这样, 也能一定上提速,因为减少了回表,查询ID 只需要走聚集索引就行。
3.当然,如果mysql 级别优化不了了。我们也可以对分页数据进行缓存,比如Redis 缓存,数据进行变动的时候,做好缓存依赖即可。
4.因为越往后,一般用户行为触及不到,比如你去看淘宝,不会去翻后面几百页的数据, 所以,业务层面也可以做一些让步,比如不做后面几百页的数据。
存储MD5 的值应该用VARCHAR 还是CHAR
char 适合存储比较短的且是固定长度的字符串
varchar 适合存储可变长度的字符串
MD5 是一个固定长度的字符,不管数据怎么修改,长度不变,这个点很符合char类型。
另外,由于是固定长度,所以在数据变更的时候,不需要去调整存储空间大小,在效率上会比varchar 好。
表数据量大的时候,影响查询效率的主要原因有哪些
表数据量大的时候,主要有几个方面的因素影响查询效率 磁盘IO:数据量大意味着需要从磁盘中读取更多的数据,而磁盘IO 速度是相对较慢的,因此会影响查询效率。
索引失效:索引是提高查询效率的重要手段,但是如果索引失效,就会导致查询效率下降。
索引失效的原因可能是查询条件中使用了不支持索引的操作符,或者是数据分布不均匀导致索引失效。
数据分页:当需要查询大量数据的时候,数据库需要进行数据分页,而数据分页的过程需要占用大量的CPU 资源,因此也会影响查询效率。
锁竞争:当多个事物同时对同一个表进行读写操作时,就会产生锁竞争,而锁竞争会导致查询效率下降。
内存使用:当表数据量大的时候,需要占用更多的内存空间来缓存数据,而如果内存不足,就会导致数据库频繁地进行磁盘IO,从而影响查询效率。
数据量达到多少的时候要开始分库分表
对该表的查询维度、查询条件的复杂度,即便在几十万数据的表中也可能会出现查询性能低的问题。
硬件资源的配置性能,比如带宽、内存、磁盘也同样会影响查询效率。
甚至是表结构设计不合理也有影响,比如如果建立的是宽表,也就是一个表包含太多的列,也会影响查询效率
这个问题需要结合具体业务场景和系统架构来考虑,基于我的理解,通常有以下几个考虑因素:
单表数据量:如果单个表的数据量已经非常大,例如超过了百万级别,就需要开始考虑分表。
数据库性能:当单个数据库的性能无法满足业务需求时,就需要考虑分库。
数据访问频率:如果某些表的数据访问频率非常高,单个数据库节点无法满足高并发请求,就需要考虑将这些表分到不同的库或表中,以提高性能。
业务拆分:当系统的业务逻辑越来越复杂,不同的业务之间的数据耦合度越来越低,
就需要考虑对系统进行拆分,以方便管理和扩展。
MySQL update 是锁行还是锁表?
MySQL 的Update 操作既可以锁行,也可以锁表,具体使用哪种锁类型,取决于执行的Update 语句的条件、事务隔离级别等因素。
如果update 语句中的where 条件包含了索引列,并且只更新一条数据,那这个时候就加行锁。 如果where 条件中不包含索引列,这个时候会加表锁
另外,根据查询范围不同,Mysql 也会选择不同粒度的锁来避免幻读问题。比如针对主键索引的for update 操作:SELECT * FROM t WHERE id = 10 FOR UPDATE;
Mysql 会增加Next-Key Lock 来锁定id=10 索引所在的区间
另外,针对于索引区间的查询或者修改SELECT * FROM user WHERE id BETWEEN 1 AND 100 FOR UPDATE;
Mysql 会自动对索引间隙加锁,来解决幻读问题。