当前位置: 首页 > 后端技术 > Java

想进阿里必须啃透的12道MySQL面试题

时间:2023-04-01 18:42:58 Java

进阿里更新必须吃透的12道MySQL面试题,我亲手整理的1000多篇千篇一律的面试文章,免费放在文末。最近有面试需要刷题的同学可以直接翻到文末搞定。1.能告诉我myisam和innodb的区别吗?myisam引擎是5.1版本之前的默认引擎,支持全文搜索、压缩、空间函数等,但不支持事务和行级锁,所以一般用于查询量大的场景以及少量的insert,而且myisam不支持外键。并且索引和数据是分开存储的。InnoDB是基于聚簇索引构建的。与MyISAM相反,它支持事务、外键,通过MVCC支持高并发。索引和数据存储在一起。2、说下mysql索引,什么是聚簇索引和非聚簇索引?从数据结构来看,索引主要包括B+树和Hash索引。假设我们有一个具有以下结构的表:createtableuser(idint(11)notnull,ageint(11)notnull,primarykey(id),key(age));B+树是按照左小右大的顺序存储的结构,节点只包含id索引列,而叶子节点包含索引列和数据。将数据和索引存储在一起的索引方式称为聚簇索引。一张表只能有一个聚簇索引。假设没有定义主键,InnoDB会选择一个唯一的非空索引来代替,如果没有,它会隐式定义一个主键作为聚簇索引。这是主键聚簇索引存储的结构,那么非聚簇索引的结构是什么样的呢?非聚集索引(二级索引)存储的是主键id值,与myisam中存储的数据地址不同。最后看一下InnoDB和Myisam聚簇索引和非聚簇索引的区别3.知道什么是覆盖索引和回表吗?覆盖索引是指在一次查询中,如果一个索引包含或者覆盖了所有需要查询的字段的值,我们就称它为覆盖索引,不需要回表查询。判断一个查询是否为覆盖索引,我们只需要使用explainsql语句,看Extra的结果是否为“Usingindex”即可。以上面的user表为例,我们再添加一个name字段,然后尝试做一些查询。解释select*fromuserwhereage=1;//查询的名称无法从索引数据中获取explainselectid,agefromuserwhereage=1;//可以直接从索引中获取4.锁的种类有哪些?mysql的锁分为共享锁和独占锁,也称为读锁和写锁。读锁是共享的,可以通过lockinsharemode来实现。这时,他们只能读不能写。一个写锁是独占的,它会阻塞其他的写锁和读锁。从粒度上来说,可以分为两种:表锁和行锁。表锁将锁定整个表,并阻止其他用户对该表的所有读写操作。比如alter在修改表结构时会锁住表。行锁可以分为乐观锁和悲观锁。悲观锁可以通过forupdate来实现,乐观锁可以通过版本号来实现。5.能否谈谈事务的基本特征和隔离级别?事务的基本ACID特征是:原子性是指事务中的操作要么全部成功,要么全部失败。一致性意味着数据库总是从一种一致状态转换到另一种一致状态。比如A给B转了100元,假设SQL执行中途系统崩溃了,A不会损失100元,因为交易没有提交,修改也不会保存到数据库中。隔离是指一个事务的修改在最终提交之前对其他事务不可见。持久性意味着一旦提交事务,所做的更改将永久保存到数据库中。隔离有四种隔离级别,即:readuncommitReaduncommitted,可能读取其他事务未提交的数据,也称为脏读。用户本应该读到id=1的用户age应该是10,结果读到了其他事务还没有提交的事务,读到的结果age=20,这就是脏读。readcommit读已经提交,两次读的结果不一致,称为不可重复读。不可重复读解决了脏读问题,它只读取已提交的事务。用户发起一个事务读取id=1的用户,在查询中发现age=10,再次读取发现result=20。在同一个事务中读取同一个查询的不同结果称为不可重复读。repeatableread可重复读,这是mysql默认的级别,即每次读的结果都是一样的,但是可能会出现幻读。一般不使用serializableserial。它会对每行读取的数据进行锁定,这会造成很多超时和锁竞争问题。6、那你说说什么是幻读,什么是MVCC?要说幻读,首先要了解MVCC。MVCC全称为多版本并发控制。其实就是保存了某个时间节点的数据快照。我们实际上为每行数据隐藏了两列,创建时间版本号和过期(删除)时间版本号。每次启动新事务时,版本号都会自动递增。还是以上面的user表为例,假设我们插入两条数据,其实应该是这样的。此时假设小明执行查询。此时current_version=3select*fromuserwhereid<=3;同时小红此时启动一个事务修改id=1的记录,current_version=4updateusersetname='张三三'whereid=1;执行成功后的结果是这样的。如果此时小黑还在删除id=2的数据,current_version=5,执行后的结果是这样的。由于MVCC的原理是发现创建版本小于等于当前事务版本,删除版本为空或者大于当前事务版本,所以小明真正的查询应该是select*fromuserwhereid<=3andcreate_version<=3and(delete_version>3ordelete_versionisnull);所以小明最后找到的id=1的名字还是'张三',id=2的记录也可以查询到。这样做是为了确保事务读取的数据在事务开始之前就已经存在,或者是事务本身插入或修改的。了解了MVCC的原理之后,我们再说什么是幻读就简单多了。举一个常见的场景,当用户注册时,我们先查询用户名是否存在,如果不存在则插入,假设用户名是唯一索引。小明打开事务current_version=6查询名为“王五”的记录,发现不存在。小红启动事务current_version=7插入一条数据,结果如下:小明执行插入一条名为'WangWu'的记录,发现唯一索引冲突,无法插入。这是一个幻读。7、ACID的保证是什么?Atomicity是通过undolog来保证的,undolog记录了需要回滚的日志信息。事务回滚时,撤消成功执行的sqlC一致性一般由代码层面来保证。隔离由MVCC保证。D持久化由memory+redolog来保证,保证mysql修改数据,同时将这次操作记录在内存和redolog中。事务提交时,使用redolog刷盘,宕机时可以从redolog中恢复。8.你知道什么是间隙锁吗?间隙锁仅在可重复读取级别可用。结合MVCC和间隙锁可以解决幻读问题。我们还是以user为例,假设现在user表中有几条记录,我们执行:begin;select*fromuserwhereage=20forupdate;begin;insertintouser(age)values(10);#成功插入用户(年龄)值(11);#failure插入用户(年龄)值(20);#failure插入用户(年龄)值(21);#failure插入用户(年龄)值(30);#failure只有10才能插入成功,所以因为表有空隙,mysql自动给我们生成一个区间(左开右闭)(负无穷大,10],(10,20],(20,30],(30,正无穷大)因为存在20条记录,所以(10,20],(20,30]区间被锁定,无法插入删除。如果查询21?会定位到(20,30)interval(均开区间)根据21.需要注意唯一索引不会有间隙索引9.分表后如何保证ID的唯一性因为我们的主键默认是自增的,那么表后的主键在不同的表中会不一样存在冲突,有几种方式可以考虑:设置步长,比如我们对1-1024表设置基本步长1024,这样primarykey落在不同的表是不会冲突的分布式??ID,实现一个Set分布式ID生成算法或者使用开源的比如SnowflakeAlgorithm。分表后,不再以主键作为查询依据,而是在每张表中增加一个新的字段作为唯一的主键。比如订单表的订单号是唯一的,不管最后落在哪个表都是以订单号为查询依据,更新都是一样的。10.你的数据有多大?如何分库分表?首先,数据库和表的划分有纵向和横向两种。一般来说,我们拆分的顺序是先纵向,后横向。垂直分库是基于现在的微服务拆分。垂直分库已经实现。如果垂直分表表字段较多,拆分不常用的,大数据等。水平分表先根据业务场景确定使用什么字段作为分表字段(sharding_key)。例如,我们现在每天有1000万个订单。我们的场景大部分来自C端。我们可以使用user_id作为sharding_key。数据查询支持最多最近3个月的订单。3个多月的归档,3个月的数据量是9亿条,可以分为1024张表,所以每张表的数据大概是100万条。比如userid是100,那么我们都通过hash(100),然后对1024取模,就可以落到对应的表中了。11、分表后如何处理非sharding_key查询?可以制作一个映射表。比如此时商家要查询订单列表怎么办?不查询user_id就不能扫描全表?所以我们可以做一个映射关系表来保存商家和用户的关系。查询时,先通过商户查??询用户列表,再通过user_id查询。创建宽表,一般来说,商家对实时数据的要求不是很高。比如查询订单列表,可以将订单表同步到离线(实时)数仓,然后基于数仓创建宽表,再基于es等提供查询服务。如果数据量不是很大,比如一些后台查询,也可以通过多线程扫描表,然后聚合结果来完成。或者异步形式也是可能的。List>>taskList=Lists.newArrayList();for(intshardingIndex=0;shardingIndex<1024;shardingIndex++){taskList.add(()->(userMapper.getProcessingAccountList(shardingIndex)));}Listlist=null;try{list=taskExecutor.executeTask(taskList);}catch(Exceptione){//dosomething}publicclassTaskExecutor{publicListexecuteTask(Collection>任务)throwsException{Listresult=Lists.newArrayList();List>futures=ExecutorUtil.invokeAll(tasks);for(Futurefuture:futures){结果.add(future.get());}返回结果;}}12.告诉我如何做mysql主从同步?先了解下mysql主从同步的原理。master提交事务后,写入binlogslave连接master,获取binlogmaster创建dump线程,推送binglog给slaveslave,启动IO线程读取同步master的binlog并记录它在中继日志中。跟随log中的slave,启动一个sql线程读取relaylogevent,并在slave上执行。将自己的binglog记录为同步从站。由于mysql默认的复制方式是异步的,主库不关心从库发送日志到从库后是否处理过。这样会造成一个问题,如果主库挂了,从库处理失败。是的,此时从库提升为主库后,日志丢失,产生两个概念。全同步复制主库写入binlog后,强制将日志同步到从库,所有从库都执行完毕再返回给客户端,但是显然这种方式会严重影响性能。半同步复制和全同步复制的区别在于,半同步复制的逻辑如下。从库成功写入日志后,向主库返回ACK确认。主库至少收到一个从库的确认,才认为写操作完成。13、主从延迟如何解决?针对具体的业务场景,强制读写请求走主库,读请求走从库。如果没有数据,就去主库做二次查询。本文就先写到这里。我整理了一些面试中经常被问到的问题。后续会持续更新,需要PDF的好兄弟可以转发本文+关注【点此】获取