MySQL面试精华提炼
Posted 一品数据邦
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了MySQL面试精华提炼相关的知识,希望对你有一定的参考价值。
概念类
三大范式
1NF:表中的每一列(每个字段)都是不可再分的(即原子性)
2NF:在1NF上,非主键列完全依赖于主键,而不能是依赖于主键的一部分
3NF:在2NF上,表中的非主键只依赖于主键,不依赖于其他非主键
InnoDB与MyISAM
InnoDB支持事务,MyISAM不支持事务
InnoDB支持外键,MyISAM不支持外键
InnoDB支持MVCC,MyISAM不支持
select count(*) from table时,MyISAM更快,它有一个变量保存了整个表的总行数,可以直接读取;InnoDB就需要全表扫描
InnoDB不支持全文索引,而MyISAM支持全文索引(5.7以后的InnoDB也支持全文索引)
InnoDB支持表、行级锁,而MyISAM支持表级锁
InnoDB表必须有主键,而MyISAM可以没有主键
InnoDB表需要更多的内存和存储,而MyISAM可被压缩,存储空间较小
InnoDB按主键大小有序插入,MyISAM记录插入顺序是,按记录插入顺序保存
InnoDB存储引擎提供了具有提交、回滚、崩溃恢复能力的事务安全,与MyISAM比InnoDB写的效率差一些,并且会占用更多的磁盘空间以保留数据和索引
mysql内存结构及后台线程
master thread是一个非常核心的后台线程,主要负责将缓冲池中的数据异步刷新到磁盘
在默认情况下,MySQL有9组后台线程,分别如下:
master thread:主要负责将脏缓存页刷新到数据文件,执行purge操作,触发检查点,合并插入缓冲区等
insert buffer thread:主要负责插入缓冲区的合并操作
read thread:负责数据库读取操作,可配置多个读线程
write thread:负责数据库写入操作,可配置多个写线程
log thread:用于将重做日志刷新到logfile中
purge thread:5.5之后用单独的purge thread执行purge操作
lock thread:负责锁控制和死锁检测等
错误监控线程:主要负责错误监控和错误处理
page cleaner thread:5.6之后,用来执行buffer pool中脏页的flush操作
其中insert buffer thread,read thread,write thread,log thread属于IO thread。
hash索引和B+树索引
B+树可以进行范围查询,hash索引不能
B+树支持联合索引的最左侧原则,hash索引不支持
B+树支持order by排序,hash索引不支持
hash索引在等值查询上比B+树效率更高
B+树使用like进行模糊查询的时候,like后面(比如%开关)的话可以起到优化的作用,hash索引根本无法进行模糊查询
B树和B+树
1.B+树非叶子节点上是不存储数据的,仅存储键值,而B树节点中不仅存储键值,也会存储数据。InnoDB中页的默认大小是16KB,如果不存储数据,那么就会存储更多的键值,相应的树的阶数(节点的子节点树)就会更大,树就会更矮更胖,如此一来我们查找数据进行磁盘的IO次数又会再次减少,数据查询的效率也会更快。
2.B+树索引的所有数据圴存储在叶子节点,而且数据是按照顺序排列的,链表连着的。那么B+树使得范围查找,排序查找,分组查找以及去重查找变得异常简单。
创建索引
1.在执行create table时创建索引
primary key(‘id’),
key ‘idx_name’ (‘name’) using btree
2.使用alter table命令添加索引
alter table table_name add index index_name(column);
3.使用create index命令创建
create index index_name on table_name (column);
索引哪些情况下会失效
查询条件包含or,可能导致索引失效
字段类型是字符串,where时一定用引号括起来,否则索引失效
like通配符可能导致索引失效
联合索引,查询时的条件列不是联合索引中的第一个列,索引失效
在索引列上使用MySQL的内置函数,索引失效
对索引列运算,索引失效
索引字段上使用(!=或者<>,not in)时,可能会导致索引失效
索引字段上使用is NULL,is not NULL,可能导致索引失效
左连接查询或者右连接查询关联的字段编码格式不一样,可能导致索引失效
MySQL估计使用全表扫描要比使用索引快,则不使用索引
索引不适合的场景
小表
更新频繁的表
区分度低的字段
索引的一些潜规则
覆盖索引
回表
索引数据结构
最左前缀原则
索引下推
InnoDB引擎中的索引策略
覆盖索引
最左前缀原则
索引下推
索引下推优化是MySQL 5.6引入的,可以在索引遍历过程中,对索引中包含的字段先做判断,直接过滤掉不满足条件的记录,减少回表次数。
聚集索引与非聚集索引
一个表中只能拥有一个聚集索引,而非聚集索引一个表可以存在多个
聚集索引:索引的叶节点就是数据节点
非聚集索引:叶节点仍然是索引节点,只不过有一个指针指向对应的数据块
聚集索引:物理存储按照索引排序
非聚集索引:物理存储不按照索引排序
覆盖索引,回表
覆盖索引:查询列要被所建的索引覆盖,不必从数据表中读取
回表:二级索引无法直接查询所有列的数据,通过二级索引查询到聚簇索引后,再查询到想要的数据
在B+树的索引中,叶子节点可能存储了当前的key值,也可能存储了当前的key值以及整行的数据,这就是聚簇索引和非聚簇索引。在InnoDB中,只有主键索引是聚簇索引,如果没有主键,则挑选一个唯一键建立聚簇索引。如果没有唯一键,则隐式的生成一个键来建立聚簇索引。
当查询使用聚簇索引时,在对应的叶子节点,可以获取到整行数据,因此不用再进行回表查询。
非聚簇索引一定会回表查询?不一定,如果查询语句的字段全部命中了索引,那么就不必再进行回表查询(覆盖索引就是这么回事)
可以使用多少列创建索引?
任何标准表最多可以创建16个索引列
SQL提示
1.use index
SQL语句 use index(index_name);
推荐数据库使用某个索引,可以让MySQL不再考虑其他可用索引
2.ignore index
SQL语句 ignore index(index_name);
忽视数据库某个索引,可以让MySQL不再考虑这个索引
3.force index
SQL语句 force index(index_name);
强迫数据库使用某个索引,使用use index数据库还是可能不用这个索引,但是force index数据库必须使用这个索引
悲观锁和乐观锁
悲观锁:当前线程要进来修改数据时,别的线程都得拒之门外,比如,可以使用
select * from user where name=’jay’ for update;
select for update除了有查询的作用外,还会加锁,而且它是悲观锁,如果没用索引/主键的话就是表锁,否则就是行锁
乐观锁:有线程过来,先放过去修改,如果看到别的线程没修改过,就可以修改成功;如果别的线程修改过,就修改失败或者重试。实现方式:乐观锁一般会使用版本号机制或CAS算法实现。
事务ACID
A原子性:使用undo log来实现,发生错误,执行回滚即可回到事务开始的状态
C一致性:通过回滚、恢复,以及并发情况下的隔离性,从而实现一致性
I隔离性:通过锁以及MVCC,使事务相互隔离开
D持久性:使用redo log来实现,只要redo log持久化了,即使系统崩溃,也可通过redo log把数据恢复
MySQL中in和exists的区别
数据库最费劲的就是跟程序链接释放。假设链接了两次,每次做上百万次的数据集查询,查完就走,这样就只做了两次;相反建立了上百万次链接,申请链接释放反复重复,这样系统就受不了了。MySQL优化原则,就是小表驱动大表,小的数据集驱动大的数据集,从而让性能更优。
因此,我们要选择最外层循环小的,也就是,如果B的数据量小于A,适合使用in,比如:
select * from A where deptid in (select deptid from B);
如果B的数据量大于A,即适合选择exists,比如:
select * from A where exists (select 1 from B where A.deptid=B.deptid);
主键类型
如果是单机,选择自增ID;如果是分布式系统,优先考虑UUID
自增ID:数据存储空间小,查询效率高。但是如果数据量过大,会超出自增长的值范围,多库合并,也有可能有问题
自增ID一般用int类型,一般达不到最大值,可以考虑提前分库分表的
UUID:适合大量数据的插入和更新操作,但是它是无序的,插入数据效率慢,占用空间大
数据库自增主键存在什么问题
使用自增主键对数据库做分库分表,可能出现诸如主键重复等问题,简单的话,可以考虑使用UUID
自增主键会产生表锁,从而引发问题
自增主键可能会用完
InnoDB表的自增ID
聚簇索引表最大限度地提高了IO密集型应用的性能,但它也有以下几个限制:
1.插入速度严重依赖于插入顺序,按照主键的顺序插入是最快的方式,否则将会出现页分裂,严重影响性能。因此对于InnoDB表,我们一般都会定义一个自增的ID列为主键
2.更新主键的代价很高,因为将会导致被更新的行移动。因此,对于InnoDB表,我们一般定义主键为不可更新
3.二级索引访问需要两次索引查找,第一次找到主键值,第二次根据主键值找到行数据
二级索引的叶子节点存储的是主键值,而不是行指针,这是为了减少当出现行移动或数据页分裂时二级索引的维护工作,但会让二级索引占用更多的空间。
为什么要尽量设定一个主键
主键是数据库确保数据行在整张表唯一性的保障,即使业务上本张表没有主键,也建议添加一个自增长的ID列作为主键。设定了主键之后,在后续的删改查的时候可能更加快速以及确保操作数据范围安全。
主键使用自增ID还是UUID
推荐使用自增ID,不要使用UUID
在InnoDB存储引擎中,主键索引是作为聚簇索引存在的,也就是说,主键索引的B+树叶子节点上存储了主键索引以及全部的数据(按照顺序),如果主键索引是自增ID,那么只需要不断向后排列即可,如果是UUID,由于到来的ID与原来的大小不确定,会造成非常多的数据插入,数据移动,然后导致产生很多的内存碎片,进而造成插入性能的下降。
总之,在数据量大一些的情况下,用自增主键性能会好一些。
关于主键是聚簇索引,如果没有主键,InnoDB会选择一个唯一键来作为聚簇索引,如果没有唯一键,会生成一个隐式的主键。
分表后的ID怎么保证唯一性
主键默认都是自增的,那么分表之后的主键在不同表就肯定会有冲突了
1.设定步长,比如1-1024张表我们分别设定1-1024的基础步长,这样主键落到不同的表就不会冲突了
2.分布式ID,自己实现一套分布式ID生成算法或者使用开源的比如雪花算法这种
3.分表后不使用主键作为查询依据,而是每张表单独新增一个字段作为唯一主键使用,比如订单表订单号是唯一的,不管最终落在哪张表都基于订单号作为查询依据,更新也一样。
auto_increment
MySQL限制一个表中只有一个auto_increment属性的列,并且具有自增ID属性的列,必须是这表的主键或唯一索引的一部分
列设置为auto_increment时,如果在表中达到最大值,会发生什么
它会停止递增,任何进一步的插入都将产生错误,因为密钥已被使用
字段NOT NULL
主要是优化表查询性能
计划对列进行索引,应尽量避免把它设置为可空,因为这会让MySQL难以优化引用了可空列的查询,同时增加了引擎的复杂度
NULL值会占用更多的字节,且会在程序中造成很多与预期不符的情况
MVCC底层原理
MVCC,多版本并发控制,它是通过读取历史版本的数据,来降低并发事务冲突,从而提高并发性能的一种机制。它涉及以下几个知识点:
事务版本号
表的隐藏列
undo log
read view
数据库连接池
在内部对象池中,维护一定数量的数据库连接,并对外暴露数据库连接的获取和返回方法
应用程序与数据库建立连接的过程:
通过TCP协议的三次握手和数据库服务器建立连接
发送数据库用户账户密码,等待数据库验证用户身份
完成身份验证后,系统可以提交SQL语句到数据库执行
把连接关闭,TCP四次挥手告别
数据库连接池优点:
资源重用(连接复用)
更快的系统响应速度
新的资源分配手段
统一的连接管理,避免数据库连接泄漏
MySQL数据库服务器性能分析
show status,一些值得监控的变量值:
bytesreceived和bytessent 和服务器之间往来的流量
Com_*服务器正在执行的命令
Created_*在查询执行期间创建的临时表和文件
Handler_*存储引擎操作
Select_*不同类型的联接执行计划
Sort_*几种排序信息
show profiles是MySQL用来分析当前会话SQL语句执行的资源消耗情况
BLOB和TEXT
BLOB用于存储二进制数据,它的值被视为二进制字符串(字节字符串),它们没有字符集,并且排序和比较基于列值中的字节的数值
TEXT用于存储大字符串,它的值被视为非二进制字符串(字符字符串),它们有一个字符集,并根据字符集的排序规则对值进行排序和比较
MySQL中InnoDB引擎的行锁是怎么实现的?
基于索引来完成行锁的。
select * from t where id=666 for update;
for update可以根据条件来完成行锁锁定,并且id是有索引键的列,如果id不是索引键那么InnoDB将实行表锁。
count(1),count(*)与count(列名)
count(1)包括了忽略所有行,用1代表代码行,在统计结果的时候,不会忽略列值为NULL
count(*)包括了所有的列,相当于行数,在统计结果的时候,不会忽略列值为NULL
count(列名)只包括列名那一列,在统计结果的时候,会忽略列值为NULL
MySQL中的触发器
before insert
after insert
before update
after update
before delete
after delete
varchar(50)
varchar(50)表示字段是可变长度字符串,长度为50
如varchar(50)和varchar(200)存储”jay”字符串所占空间是一样的,后者在排序时会消耗更多内存
int(11)
表示长度为11的int类型。长度在大多数场景是没有意义的,它不会限制值的合法范围,只会影响显示字符的个数,而且需要和unsigned zerofill属性配合使用才有意义。
char(m)如何占据m个字符宽度?
如果实际存储内容不足m个,则后面加空格补齐。
取出来的时候,再把后面的空格去掉(所以,如果内容本身最后包含有空格,将会被清除;而这种情况在varchar类型中不会出现,因为varchar中有1到2个字节是用来记录字符串的长度)
SQL的生命周期
服务器与数据库建立连接
数据库进程拿到请求SQL
解析并生成执行计划,执行
读取数据到内存,并进行逻辑处理
通过步骤一的连接,发送结果到客户端
关掉连接,释放资源
DATETIME与TIMESTAMP
存储精度都为秒
DATETIME的日期范围是1001-9999年,TIMESTAMP的时间范围是1970-2038年
DATETIME存储时间与时区无关,TIMESTAMP存储时间与时区有关,显示的值也依赖于时区
DATETIME的存储空间为8字节,TIMESTAMP的存储空间为4字节
DATETIME的默认值为NULL,TIMESTAMP的字段默认不为空,默认值为当前时间(current_timestamp)
DATETIME类型适合用来记录数据的原始的创建时间,修改记录中其他字段的值,DATETIME字段的值不会改变,除非手动修改它。
TIMESTAMP类型适合用来记录数据的最后修改时间,只要修改了记录中其他字段的值,TIMESTAMP字段的值都会被自动更新。
InnoDB两种日志redo和undo
日志的存放形式
redo:在页修改时,先写到redo log buffer里面,然后写到redo log的文件系统缓存里面(fwrite),然后再同步到磁盘文件(fsync)
undo:在MySQL 5.5之前,undo只能存放在ibdata文件里面,5.6之后,可以通过设置innodb_undo_tablespace参数把undo log存放在ibdata之外。
事务是如何通过日志来实现的
事务在修改页时,要先记undo,在记undo之前要记undo的redo,然后修改数据页,再记数据页修改的redo。redo(里面包括undo的修改)一定要比数据页先持久化到磁盘。
当事务需要回滚时,因为有undo,可以把数据页回滚到前镜像的状态,崩溃恢复时,如果redo log中事务没有对应的commit记录,那么需要用undo把该事务的修改回滚到事务开始之前
如果有commit记录,就用redo前滚到该事务完成时并提交掉。
主从一致性校验
有多种工具,例如checksum,mysqldiff,pt-table-checksum等
Nosql
Memcached 纯内存
Redis 持久化缓存
MongoDB 面向文档
MySQL多实例
就是在同一台服务器上启用多个MySQL服务,它们监听不同的端口,运行多个服务进程,它们相互独立,互不影响地对外提供服务,便于节约服务器资源与后期架构扩展
多实例的配置方法
1.一个实例一个配置文件,不同端口
2.同一配置文件下配置不同实例,基于mysqld_multi工具
HEAP表的特性
HEAP表存在于内存中,用于临时高速存储
BLOB或TEXT字段是不允许的
只能使用比较运算符=,<,>,=<,=>
HEAP表不支持auto_increment
索引不可为NULL
HEAP表的大小可通过max_heap_table_size参数来进行控制
char_length和length
char_length是字符数,而length是字节数。latin字符的这两个数据是相同的,但是对于unicode和其他编码,它们是不同的
FEDERATED表是什么
FEDERATED表,允许访问位于其他服务器数据库上的表
MySQL中有哪些不同的表格
共有5种类型的表格:
MyISAM
HEAP
merge
InnoDB
isam
MySQL如何优化distinct
distinct在所有列上转换为group by,并与order by子句结合使用
select distinct t1.a from t1,t2 where t1.a=t2.a;
在MySQL数据库中有两种排序方式
通过有序索引扫描直接返回有序数据
通过对返回数据进行排序(filesort排序)
其中filesort会多排一次序,所以在使用order by 语句的时候尽量用到索引。
mysql_pconnect与mysql_connect
mysql_pconnect()打开一个持久的数据库连接,这意味着数据库不是在每次页面加载时被打开一个新连接,因此我们不能使用mysql_close()来关闭一个持久的连接
与mysql_pconnect不同,mysql_connect在每次页面被加载时打开连接,这个连接可以使用mysql_close()语句来关闭。
doublewrite两次写
doublewrite带给InnoDB存储引擎的是数据页的可靠性
当发生数据库宕机时,可能InnoDB存储引擎正在写入某个页到表中,而这个页只写了一部分,比如16KB的页,只写了前4KB,之后就发生了宕机,这种情况被称为部分写失效。
如果发生写失效,可以通过重做日志进行恢复。但是必须清楚地认识到,重做日志中记录的是对页的物理操作,如偏移量800,写’aaaaaaaaaa’记录。如果这个页本身已经发生了损坏,再对其进行重做是没有意义的。这就是说,在应用(apply)重做日志前,用户需要一个页的副本,当写入失效发生时,先通过页的副本来还原该页,再进行重做,这就是doublewrite。
doublewrite由两部分组成,一部分是内存中的doublewrite buffer,大小为2MB,另一部分是物理磁盘上共享表空间中连续的128个页,即2个区(extent),大小同样为2MB。在对缓冲池的脏页进行刷新时,并不直接写磁盘,而是会通过memcpy函数将脏页先复制到内存中的doublewrite buffer,之后通过doublewrite buffer再分两次,每次1MB顺序地写入共享表空间的物理磁盘上,然后马上调用fsync函数,同步磁盘,避免缓冲写带来的问题。
MySQL插件式存储引擎架构
InnoDB存储引擎特有的next-key locking算法,防止幻读产生。
MySQL被设计为一个单进程多线程架构的数据库,与sql server比较类似,但是与oracle多进程的架构有所不同。
在MySQL数据库中,可以没有配置文件,在这种情况下,MySQL会按照编译时的默认参数设置启动实例。
存储引擎是基于表的,而不是数据库。
InnoDB主要面向OLTP,其特点是行锁设计,支持外键,支持MVCC。若无显示指定主键,会自动生成一个6字节的rowid,并以此作为主键。
MyISAM不支持事务,表锁设计,支持全文索引,主要面向OLAP。
缓冲池中缓存的数据页类型有:
索引页
数据页
undo页
插入缓冲
自适应哈希索引
InnoDB存储的锁信息
数据字典信息
8M重做日志缓冲池持久化到磁盘的时机:
master thread每一秒将重做日志缓冲刷新到重做日志文件
每个事务提交时会将重做日志缓冲刷新到重做日志文件
当重做日志缓冲池剩余空间小于1/2时,重做日志缓冲刷新到重做日志文件
LSN:log sequence number
write ahead log策略,即当事务提交时,先写重做日志,再修改页
脏页刷到磁盘的时机
redo log可写空间满了
系统内存不足,需要淘汰脏页的时候,要把脏页同步回磁盘
系统不繁忙的时候
关闭数据库的时候
参数innodb_max_dirty_pages_pct是脏页比例上限,默认值是75%。
checkpoint技术的目的
缩短数据库的恢复时间
缓冲池不够用时,将脏页刷新到磁盘
重做日志不可用时,刷新脏页
在InnoDB存储引擎中,常见的页类型有
数据页
undo页
系统页
事务数据页
插入缓冲位图页
插入缓冲空闲列表页
未压缩的二进制大对象页
压缩的二进制大对象页
InnoDB存储引擎的关键特性有
插入缓冲
两次写
自适应哈希索引
异步IO
刷新邻接页
数据完整性有以下四种形式
实体完整性
域完整性
参照完整性
用户自定义完整性
InnoDB存储引擎表总是按照主键索引顺序进行存放的
MySQL中视图总是虚拟的表,本身不支持物化视图
InnoDB存储引擎的锁的算法有哪三种
record lock:单个行记录上的锁
gap lock:间隙锁,锁定一个范围,不包括记录本身
next-key lock:record+gap 锁定一个范围,包含记录本身
间隙锁
间隙锁是可重复读级别下才会有的锁,结合MVCC和间隙锁可以解决幻读的问题
next-key lock算法
在InnoDB存储引擎中,通过使用next-key lock算法来避免不可重复读的问题。在MySQL官方文档中将不可重复读的问题定义为phantom problem,即幻读问题。在next-key lock算法下,对于索引的扫描,不仅是锁住扫描到的索引,而且还锁住这些索引覆盖的范围(gap),因此在这个范围内的插入都是不允许的。这样就避免了另外的事务在这个范围内插入数据导致的不可重复读的问题。
MySQL乱码问题
character_set_client告诉服务器,我给你发送的数据是什么编码
character_set_connection告诉转换器,转换成什么编码
character_set_results查询的结果用什么编码
以上3者的字符集与系统的字符集一致
如果以上3者都为字符集N,则可以简写为set names N;该设置是临时生效的
character_set_database数据库字符集,配置文件指定或建库建表时指定
character_set_server服务器字符集
以上两个是server端的字符集参数
不乱码思想:linux,客户端,服务端,库,表,程序,字符集都要统一
MySQL性能抖动的原因
一般就是出现了flush操作的时候,比如redo log满了,那么写操作就会堵塞,导致写操作就慢了;查询操作如变慢,很可能就是内存满了,而且这个查询要淘汰的脏页个数太多,就会导致查询响应时间变长。
crash safe recovery
数据库要求:启动时,必须保证data page和logfile中LSN一致才能正常打开数据库,否则就需要自动恢复。
可以在CSR过程中,实现前滚的功能:logfile中LSN比data page中LSN大的部分都需要前滚。最后实现data page中的LSN与logfile中的LSN相等。
二进制日志文件的刷新
flush logs
重启MySQL也会自动滚动一个新的
日志文件达到1G大小(max_binlog_size)
备份时加入参数-F也可以自动滚动
从库IO线程阻塞如何解决
大事务------à拆成小事务
事务量大---àgroup commit
InnoDB master thread
InnoDB的主要工作都是在一个单独的master线程里完成的。master线程的优先级最高,它主要分为以下几个循环:主循环(loop),后台循环(background loop),刷新循环(flush loop),暂停循环(suspend loop)
每秒一次的操作
刷新日志缓冲区(总是)
合并插入缓冲(可能)
至多刷新100个脏数据页(可能)
如果没有当前用户活动,切换至background loop(可能)
每10秒一次的操作
合并至多5个插入缓冲(总是)
刷新日志缓冲(总是)
刷新100个或10个脏页到磁盘(总是)
产生一个检查点(总是)
删除无用undo页(总是)
后台循环(无用户活动或关闭数据库)
删除无用的undo页(总是)
合并20个插入缓冲(总是)
跳回到主循环(总是)
不断刷新100个页,直到符合条件跳转到flush loop(可能)
常用SQL mode
only_full_group_by 对于group by聚合操作,如果出现在select中的列、having或order by子句的非聚合列,没有在group by中出现,那么这个SQL语法检查报错
ansi_quotes 禁止用双引号来引用字符串
real_as_float real作为float的同义词
pipes_as_concat 将||视为字符串的连接操作符而非 或 运算符
strict_trans_tables 在事务存储引擎上启用严格模式出现,那么这个SQL语法检查报错
strict_all_tables在所有存储引擎上启用严格模式出现,那么这个SQL语法检查报错
error_for_division_by_zero 不允许0作为除数
no_auto_create_user 在用户不存在时不允许grant语句自动建立用户
no_zero_in_date 日期数据内不能含0
no_zero_date 日期数据不能含0
no_engine_substitution 当指定的存储引擎不可用时报错
优化类
优化SQL
加索引
避免返回不必要的数据
适当分批量进行
优化SQL结构
分库分表
读写分离
分库分表
水平分库:以字段为依据,按照一定的策略(hash、range等),将一个库中的数据拆分到多个库中
水平分表:以字段为依据,按照一定策略(hash、range等),将一个表中的数据拆分到多个表中
垂直分库:以表为依据,按照业务归属不同,将不同的表拆分到不同的库中
垂直分表:以字段为依据,按照字段的活跃性,将表中字段拆到不同的表(主表和扩展表)中
常用的分库分表中间件
Mycat
Atlas(Qihoo 360)
分库分表可能遇到的问题
事务问题:需要用分布式事务
跨节点JOIN的问题:解决这一问题可以分两次查询实现
跨节点的count,order by,group by以及聚合函数问题:分别在各个节点上得到结果后在应用程序端进行合并
数据迁移,容量规划,扩容等问题
ID问题:数据库被切分后,不能再依赖数据库自身的主键生成机制了,最简单可以考虑UUID
跨分片的排序分页问题
如果某个表有近千万数据,CRUD比较慢,如何优化
可以考虑优化表结构,分表
分表方案(水平分表,垂直分表,切分规则hash等)
分库分表中间件(Mycat,Atlas-sharding等)
分库分表的一些问题(事务问题?跨节点JOIN问题)
解决方案(分布式事务等)
还可以考虑索引优化
SQL优化的一般步骤
show status命令了解各种SQL的执行频率
通过慢查询日志定位那些执行效率较低的SQL语句
EXPLAIN分析低效SQL的执行计划
MySQL主从延迟
主从同步延迟的原因
一个服务器开放N个链接给客户端来连接的,这样又会有大并发的更新操作,但是从服务器里面读取binlog的线程仅有一个,当某个SQL在从服务器上执行的时间稍长 或者 由于某个SQL要进行锁表就会导致,主服务器的SQL大量积压,未被同步到从服务器里。这就导致了主从不一致,也就是主从延迟。
解决办法
主服务器要负责更新操作,对安全性的要求比从服务器要高,所以有些设置参数可以修改,比如
sync_binlog=1
innodb_flush_log_at_trx_commit=1
选择更好的硬件设备作为slave;
把一台从服务器单独作为备份使用,而不提供查询,那边它的负载下来了,执行relay log里面的SQL效率自然就高了;
增加从服务器,这个的目的还是分散读的压力,从而降低服务器负载
大表查询的优化方案
优化schema,SQL语句+索引
可以考虑加缓存,memcached,redis或者jvm本地缓存
主从复制,读写分离
分库分表
业务系统的SQL耗时
用EXPLAIN SQL
慢查询的统计
优化慢查询:
分析语句,是否加载了不必要的字段/数据
分析SQL执行计划,是否命中索引等
如果SQL复杂,优化SQL结构
如果表数据量太大,考虑分表
如何优化长难的查询语句
将一个大的查询分为多个小的相同的查询
减少冗余记录的查询
一个复杂查询可以考虑拆成多个简单查询
分解关联查询,让缓存的效率更高
MySQL数据库cpu飙升
排查过程:
使用top命令观察,确定是mysqld导致还是其他原因
如果是mysqld导致的,show processlist,查看session情况,确定是不是有消耗资源的SQL在运行
找出消耗高的SQL,看看执行计划是否准确,索引是否缺失,数据量是否太大
处理:
kill掉这些线程(同时观察cpu使用率是否下降)
进行相应的调整(比如加索引、改SQL、改内存参数)
重新跑这些SQL
其他情况:
也有可能是每个SQL消耗资源并不多,但是突然之间,有大量的session连进来导致cpu飙升,这种情况就需要跟应用一起来分析为何连接数会激增,再做出相应的调整,比如说限制连接数等。
大表数据查询,怎么优化?
优化schema,SQL语句+索引
第二加缓存,memcached,redis
主从复制,读写分离
垂直拆分,根据你模块的耦合度,将一个大的系统分为多个小的系统,也就是分布式系统
水平切分,针对数据量大的表,这一步最麻烦,最能考验技术水平,要选择一个合理的sharding key,为了有好的查询效率,表结构也要改动,做一定的冗余,应用也要改,SQL中尽量带sharding key,将数据定位到限定的表上去查,而不是扫描全部的表
双1验证
innodb_flush_log_at_trx_commit为0:log buffer将每秒一次地写入log file中,并且log file的flush(刷到磁盘)操作同时进行。该模式下,在事务提交的时候,不会主动触发写入磁盘的操作;
innodb_flush_log_at_trx_commit为1:每次事务提交时MySQL都会把log buffer的数据写入log file,并且flush到磁盘中去;
innodb_flush_log_at_trx_commit为2:每次事务提交时MySQL都会把log buffer的数据写入log file,但是flush操作并不会同时进行。该模式下,MySQL会每秒执行一次flush操作
注:由于进程调试策略问题,这个“每秒执行一次flush操作”并不是保证100%的“每秒”
sync_binlog
sync_binlog的默认值是0,像操作系统刷其他文件的机制一样,MySQL不会同步到磁盘中去,而是依赖操作系统来刷新binary log。
当sync_binlog=N(N>0),MySQL在每写N次二进制日志binary log时,会使用fdatasync()函数将它的写二进制日志binary log同步到磁盘中去。
注:如果启用了autocommit,那么每一个语句就会有一次写操作,否则每个事务对应一个写操作。
当两个参数设置为双1的时候,写入性能最差;sync_binlog=N(N>1), innodb_flush_log_at_trx_commit=2时,(在当前模式下)MySQL的写操作才能达到最高性能。
双1时是最安全的,在mysqld服务崩溃或者服务器主机crash的情况下,binary log最多可能丢失一个语句或者一个事务。双1会导致频繁的IO操作,因此该模式也是最慢的一种方式。
当innodb_flush_log_at_trx_commit设为0时,mysqld进程的崩溃会导致上一秒所有事务数据的丢失;
当innodb_flush_log_at_trx_commit设为2时,只有在操作系统崩溃或者系统掉电的情况下,上一秒所有事务数据才可能丢失。
“双1设置”适合数据安全性要求非常高,而且磁盘IO写能力足够支持业务,比如订单、交易、充值、支付消费系统。双1模式下,当磁盘IO无法满足业务需求时,推荐的做法是innodb_flush_log_at_trx_commit=2,sync_binlog=N(N为500或1000)且使用带蓄电池后备电源的缓存cache,防止系统断电异常。
MySQL优化
SQL优化主要分4个方向:SQL语句跟索引、表结构、系统配置、硬件
总优化思路就是最大化利用索引,尽可能避免全表扫描,减少无效数据的查询
减少数据访问
返回更少的数据
减少交互次数
减少服务器cpu开销
分表分区
SQL语句优化大致举例:
1.合理建立覆盖索引:可以有效减少回表
2.union,or,in都能命中索引,建议使用in
3.负向条件(!=,<>,not in,not exists,not like等)不会使用索引,建议用in
4.在列上进行运算或使用函数会使索引失效,从而进行全表扫描
5.小心隐式类型转换,原字符串用整型会触发CAST函数导致索引失效。原int用字符串则会走索引;
6.不建议使用%前缀模糊查询;
7.多表关联查询时,小表在前,大表在后。在MySQL中,执行from后的表关联查询是从左往右执行的(oracle反之),第一张表会涉及到全表扫描
8.调整where子句中的连接顺序,MySQL采用从左往右,自上而下的顺序解析where子句。根据这个原理,应将过滤数据多的条件往前放,最快速度缩小结果集
通过processlist表实时监控慢查询
select * from information_schema.processlist where time>60 and command<>’sleep’;
慢查询造成的磁盘IO爆表
MySQL输出大量日志
MySQL正在进行大批量写
慢查询产生了大量的磁盘临时表
解决慢查询造成的磁盘IO爆表问题
优化慢查询,减少使用磁盘临时表
增加tmp_table_size和max_heap_table_size参数的大小
InnoDB的读写参数优化
读取参数
global buffer pool以及local buffer
写入参数
innodb_flush_log_at_trx_commit
innodb_bufer_pool_size
与IO相关的参数
innodb_write_io_threads=8
innodb_read_io_threads=8
innodb_thread_concurrency=0
EXPLAIN SQL
查询效率低的SQL语句,可以通过EXPLAIN分析低效SQL的执行计划
select_type:表示select的类型,常见取值有simple(简单表,即不使用表连接或者子查询),primary,union,subquery
table:输出结果集的表
type:表示MySQL的访问方式(从上到下依次变快)
all,全表扫描
index,遍历整个索引
range,索引范围扫描
ref,使用非唯一索引扫描或唯一索引的前缀扫描
eq_ref,使用唯一索引
const/system,单表中最多只有一个匹配行
NULL,不用访问表或者索引就能直接得到结果
possible_key:表示查询时可能用到的索引
key:表示实际用到的索引
key_len:使用到索引字段的长度
rows:扫描行的数量
Extra:执行情况的说明和描述
EXPLAIN 执行计划中select_type和extra
select_type:
simple 不包含子查询或是union操作的查询
primary 查询中如果包含任何子查询,那么最外层的查询则被标注为primary
subquery select列表中的子查询
dependent subquery 依赖外部结果的子查询
union union操作的第二个或是之后的查询的值为union
dependent union当union作为子查询时,第二或是第二个后的查询的select_type值
union result union产生的结果集
derived 出现在from子句中的子查询
extra:
distinct 优化distinct操作,在找到第一个匹配的元组后即停止找同样值的动作
not exists 使用not exists来优化查询
using filesort 使用文件来进行排序,通常会出现在order by或group by查询中
using index 使用了覆盖索引进行查询
using temporary MySQL需要使用临时表来处理查询,常见于排序,子查询和分组查询
using where 需要在MySQL服务器层使用where条件来过滤数据
select tables optimized away 直接通过索引来获得数据,不用访问表
多种JOIN算法
LOOP JOIN(嵌套连接)
首先遍历A,将A表中的每一条记录与B表进行连接比较,如果满足A.id=B.id,则返回记录
MERGE JOIN(合并连接)
即首先对A,B表进行排序,然后同时遍历A和B表,进行A.id=B.id的验证,直到遍历到A和B表结束;
HASH JOIN(哈希连接)
即首先对A表所有记录的id进行hash计算,最终形成一个hash表,然后join时,遍历B表,对B表每条记录的id进行hash运算,然后在A的hash表中验证是否一致,最后得出结果
架构类
如何选择合适的分布式主键方案
数据库自增长序列或字段
UUID
redis生成ID
twitter的snowflake算法
利用zookeeper生成唯一ID
mongodb的objectid
MySQL如何保证复制过程中数据一致性
1.在MySQL 5.5以及之前,slave的SQL线程执行的relay log的位置只能保存在文件(relay-log.info)里面,并且该文件默认每执行10000次事务做一次同步到磁盘,这意味着slave意外crash重启时,SQL线程执行到的位置和数据库的数据是不一致的,将导致复制报错,如果不重搭复制,则有可能会导致数据不一致
2.MySQL 5.6引入参数relay_log_info_repository,将该参数设置为table时,MySQL将SQL线程执行到的位置存到mysql.slave_relay_log_info表,这样更新该表的位置和SQL线程执行的用户事务绑定成一个事务,这样slave意外宕机后,slave通过InnoDB的崩溃恢复可以把SQL线程执行到的位置和用户事务恢复到一致性的状态
3.MySQL 5.6引入GTID复制,每个GTID对应的事务在每个实例上面最多执行一次,这极大地提高了复制的数据一致性
4.MySQL 5.5引入半同步复制,用户安装半同步复制插件并且开启参数后,设置超时时间,可保证在超时时间内如果binlog不传到slave上面,那么用户提交事务时不会返回,直到超时后切成异步复制,但如果切成异步之前用户线程提交时在master上面等待的时候,事务已经提交,该事务对master上面的其他session是可见的,如果这时master宕机,那么到slave上面该事务又不可见了,该问题直到5.7才解决
5.MySQL 5.7引入无损半同步复制,引入参数rpl_semi_sync_master_wait_point,该参数默认为after_sync,指的是在切成半同步之前,事务不提交,而是接收到slave的ack确认之后才提交该事务,从此,复制真正可以做到无损了。
MySQL出现复制延迟有哪些原因
1.需要同步的从库数据太多
2.从库的硬件资源较差,需要提升
3.网络问题,需要提升网络带宽
4.主库的数据写入量较大,需要优化配置和硬件资源
5.SQL语句执行过长导致,需要优化
GTID复制和普通复制的区别
1.在主从复制环境中,主库发生过的事务,在全局都是由唯一GTID记录的,更方便failover和数据补偿
2.额外功能参数(3个)
3.change master to的时候不再需要binlog文件名和position号
4.在复制过程中,从库不再依赖master.info文件中记录的file name和position号,而是直接读取最后一个relaylog的GTID号
读写分离常见方案
应用程序根据业务逻辑来判断,增删改等写操作命令发给主库,查询命令发给备库
利用中间件来做代理,负责对数据库的请求识别出读还是写,并分发到不同的数据库中(如:mysql-proxy)
MySQL如何解决主从复制的延迟性
5.5是单线程复制,5.6是多库复制(对于单库或者单表的并发操作是没用的),5.7是真正意义上的多线程复制,它的原理是基于group commit,只要master上面的事务是group commit的,那slave上面也可以通过多个worker线程去并发执行。
MySQL主从复制解决的问题
数据分布:随意开始或停止复制,并在不同地理位置分布数据备份
负载均衡:降低单个服务器的压力
高可用和故障切换:帮助应用程序避免单点失败
升级测试:可以用更高版本的MySQL作为从库
主从复制的作用
主数据库出现问题,可以切换到从数据库
可以进行数据库层面的读写分离
可以在从数据库上进行日常备份
MMM
适用于对数据的一致性要求不是很高,但又想最大程度地保证业务可用性的场景。
MHA
能在最大程度上保证数据的一致性,以达到真正意义上的高可用。
MHA配置步骤:
配置一主多从的复制架构
安装centos的yum扩展源及依赖包
配置集群内各主机的ssh免认证
在各节点安装mha_node软件
在管理节点安装mha_manager
配置并启动MHA管理进程
主从延迟原因及解决方案
大事务:数万行的数据更新以及对大表的DDL操作
化大事务为小事务,分批更新数据
使用pt-online-schema-change工具进行DDL操作
网络延迟
减小单次事务处理的数据量以减少产生的日志文件大小
减少主上所同步的slave数量(一般建议不超过5台)
由主上多线程的写入,从上单线程恢复引起的延迟
使用MySQL 5.7之后的多线程复制
使用mgr复制架构
实操类
limit 1000000加载很慢,如何解决
方案1:如果id是连续的,可以这样,返回上次查询的最大记录(偏移量),再往下limit
select id,name from employee where id>1000000 limit 10;
方案2:order by + 索引(id为索引)
select id,name from employee order by id limit 1000000,10;
方案3:利用延迟关联或者子查询优化超多分页场景。(先快速定位需要获取的id段,然后再关联)
select a.* from employee a,(select id from employee where 条件 limit 1000000,10) b where a.id=b.id;
百万级别或以上的数据,你是如何删除的
先删除索引
删除无用数据
重建索引
一个6亿的表a,一个3亿的表b,通过外键tid关联,如何最快的查询出满足条件的第50000到第50200中的这200条数据记录
1.如果a表tid是自增长,并且是连续的,b表的id为索引
select * from a,b where a.tid=b.id and a.tid>50000 limit 200;
2.如果a表的tid不是连续的,那么就需要使用覆盖索引。tid要么是主键,要么是辅助索引,b表id也需要有索引
select * from b,(select tid from a limit 50000,200) a where b.id=a.tid;
一张表,里面有id自增主键,当insert了17条记录之后,删除了第15、16、17条记录,再把MySQL重启,insert一条记录,这条记录的id是18还是15?
如果表的类型是InnoDB,新增一条记录(不重启MySQL的情况下),这条记录的id是18;如果重启MySQL,这条记录的id是15.因为InnoDB表只把自增主键的最大id记录到内存中,重启数据库或者对表optimize操作,都会使最大id丢失。
如果表的类型是MyISAM,那么这条记录的id就是18.因为MyISAM表会把自增主键的最大id记录到数据文件里面,重启对其无影响。
以下三条SQL如何建索引(只建一条)
where a = 1 and b = 1
where b = 1
where b = 1 order by time desc
create index idx_b_a_time on tbl_name(b,a,time);
对于第一条SQL,因为最新MySQL版本会优化where子句后面的列顺序,以匹配复合索引顺序
表中有大字段x(TEXT类型),且字段x不会经常更新,以读为主
拆带来的问题:连接消耗+存储拆分空间
如果能容忍拆分带来的空间问题,拆的话最好和经常要查询的表的主键在物理结构上放置在一起(分区)顺序IO,减少连接消耗,最后这是一个文本列再加上一个全文索引来尽量抵消连接消耗
不拆可能带来的问题:查询性能
如果能容忍不拆分带来的查询性能损失的话,上面的方案在某个极致条件下肯定会出现问题,那么不拆就是最好的选择。
实际场景下,例如说商品表数据量比较大的情况下,会将商品描述单独存储到一个表中。即,使用拆的方案。
MySQL sleep线程过多如何解决?
1.show processlist,kill pid结束掉sleep进程
2.修改配置,重启服务
[mysqld]
wait_timeout=600
interactive_timeout=30
不重启方法
set global wait_timeout=600;
set global interactive_timeout=30;
sort_buffer_size
在每个connection(session)第一次连接时需要使用到,来提高访问性能
set global sort_buffer_size=2M;
误操作执行了一个drop库SQL语句,如何完整恢复
1.停止主从复制,在主库上执行锁表并刷新binlog操作(假设此处产生的最新binlog日志为mysql-bin.000012),接着恢复之前的全备文件(比如0点的全备)
2.将0点时的binlog文件与全备到故障期间的binlog文件合并导出成SQL语句
mysqlbinlog --no-defaults mysql-bin.000011 mysql-bin.000012 >bin.sql
3.将导出的SQL语句中drop语句删除,恢复到数据库中
mysql -uroot -pmysql123 <bin.sql
开启从库binlog功能
修改配置文件加上以下配置
log_bin=slave-bin
log_bin_index=slave-bin.index
查询第n高的工资
select distinct(salary) from employee order by salary desc limit n-1,1;
在MySQL中enum的用法
enum是一个字符串对象,用于指定一组预定义的值,并可在创建表时使用。
create table size(name enum(‘small’,’medium’,’large’));
显示前50行
select * from table limit 0,50;
如何维护数据库的数据字典
直接在生产库进行注释,利用工具导出成excel
mysqldump后接参数
-B 指定多个库,增加建库语句和use语句
-A 备份所有表
-F 刷新binlog日志
--master-data 增加binlog日志文件名及对应的位置点 取值为 2 时,会加注释(不执行)
-d 只备份表结构
--single-transaction 适合InnoDB事务数据库备份,通常启用该选项来保证备份的一致性。实际上它的工作原理是设定本次会话的隔离级别是:repeatable read,以确保本次会话(dump)时,不会看到其他会话已经提交了的数据。
在mysqldump工具中,如何只恢复某个库和某张表
全库备份
mysqldump -uroot -p --single-transaction -A --master-data=2 >dump.sql
只还原erp库的内容
mysql -uroot -p erp --one-database <dump.sql
导出用户建立及授权语句
pt-show-grant u=root,p=123456,h=localhost;
在线DDL存在的问题
有部分语句不支持在线DDL
长时间的DDL操作会引起严重的主从延迟
无法对DDL操作进行资源限制
对一个大表做在线DDL,怎么进行实施才能尽可能降低影响
pt-online-schema-change --alter=”add column modified_time timestamp” --execute D=stock,t=stock,u=dba,p=123456;
以上是关于MySQL面试精华提炼的主要内容,如果未能解决你的问题,请参考以下文章
MySQL 三万字精华总结,和面试官扯皮绰绰有余(收藏系列)
MySQL 三万字精华总结,和面试官扯皮绰绰有余(收藏系列)
MySQL 三万字精华总结 + 面试100 问,吊打面试官绰绰有余(收藏系列)