MySQL 常见问题总结

MySQL 常见问题总结

MySQL为什么会抖一下?
  • 针对InnoDB导致MySQL抖得原因,主要是InnoDB会在后台刷脏页,而刷脏页的过程是要将内存页写入磁盘。

    所以,无论是你的查询语句在需要内存的时候可能要求淘汰一个脏页,还是由于刷脏页的逻辑会占用IO资源并可能影响到了你要更新的语句,都可能造成MySQL"抖"一下原因;

怎么解决MySQL抖动的问题?
  • 设置合理参数配置,innodb_io_capacity的值,并且平时要多关注脏页比例,不要让它经常接近75%;
什么是脏页?
  • 当内存数据页跟磁盘数据页内容不一致的时候,我们称为这个内存页为"脏页";
什么是干净页?
  • 内存数据写入到磁盘后,内存和磁盘上的数据页内容就达到一致,称为"干净页";

    不论是脏页还是干净页,都在内存中;

脏页是怎么产生的?
  • 因为使用了WAL(write-ahead-log先写入日志,在写入磁盘)技术,这个技术会把数据库的随机写转化为顺序写,会产生脏页;
什么是随机写?为什么耗性能?
  • 随机写,需要重新定位位置,机械运动是很慢的,即使不是机械运动重新定位写磁盘的位置也是很耗时的;
什么是顺序写?
  • 数据写磁盘上的扇区就在上次的下一个位置,不需要重新定位写磁盘的位置速度会比随机写快一些;
WAL怎么把随机写转换为顺序写的?
  • 因为写redo log是顺序写的,先写redo log 等合适时机在写入磁盘,间接的将随机写变成顺序写,性能确实会提高不少;
为什么删除表的数据,表文件大小没发生改变?
  • 因为delete命令其实只是把记录的位置,或者数据页标记为"逻辑删除",但磁盘文件的大小是不会变的,物理空间没有实际释放;
表的数据信息存在哪里?
  • 它可以存储在共享表空间里,也可以单独存储在文件以.ibd为后缀的文件里。由参数innodb_file_per_table来控制。

  • innodb_file_per_table:参数OFF表示,共享空间;参数ON表示,以.ibd后缀文件;

    建议存储在单独的文件,因为在不需要的时候,使用drop table命令也能直接把对应的文件删除,如果存储在共享空间之中,即使表删除了空间也不会释放;

    MySQL5.6.6版本开始,它的默认值就是ON;

表的结构信息存在哪里?
  • 表的结构定义占有存储空间比较小,在MySQL8.0之前,表结构的定义信息存在.frm后缀的文件里;在MySQL8.0之后,允许把表结构的定义信息存在系统数据表之中;

    系统数据表,主要用于存储MySQL系统数据,比如:数据字典、undo log(默认)等文件

如何删除表数据后,表文件资源释放?

重建表,消除表因为进行大量的增删改操作而产生空洞,使用如下命令:

1:alter table t engine=innodb;

2:optimize table t(等于 recreate + analyze);

3:truncate table t(等于 drop + create);

analyze table t 其实不是重建表,只是对表的索引信息重新统计,没有修改数据,这个过程中加了MDL读锁;

什么是空洞?如何产生?
  • 空洞就是那些被标记可复用,但是还没被使用的存储空间;
  • 使用delete命令删除数据会产生空洞,标记为可复用;
  • 插入新的数据可能引起页分裂,也可能产生空洞;
  • 修改操作,有时是一种先删除后新增的动作也可能产生空洞;
什么是count()语句?

不同的存储引擎实现方式一样:

  • MyISAM引擎把一个表的总行数存在磁盘上,因此执行count(*)的时候会直接返回这个数,效率很高;

  • InnoDB引擎它执行count(*)的时候,需要把数据一行一行地从引擎里面读出来,然后在累积计数;

    对于InnoDB引擎来说,count()是一个聚合函数,对于返回的结果集,一行行地判断,如果count函数的参数不是NULL,累计值+1,否则不加,最后返回累计值;

count()效率排序、如何计数?
  • count(字段) < count(主键ID) < count(1) ≈ count(*)

    尽量使用count(*)

  • count(字段)计数:

    • 若"字段" 定义为 not null,一行行地从记录里面读出这个字段,判断不能为null,按行累加;
    • 若"字段"定义允许为 null,那么执行的时,判断到有可能是null,还要把值取出来在判断一下,不是null在累加;
  • count(主键ID)计数:InnoDB引擎会遍历整张表,把每一行的id值都取出来,返回给server层。server层拿到id后,判断不可能为空,就按行累加。从引擎返回的主键ID会涉及到解析数据行,以及拷贝字段值的操作;

  • count(1):InnoDB引擎遍历整张表,但不取值。server层对于返回的每一行,放一个数字"1"进去,判断不可能为空,按行累加;

  • count(*):并不会把全部字段取出来,而是专门做了优化,不取值。count( *)肯定不是null按行累加;

什么是幻读?
  • 幻读指的是一个事务在前后两次查询同一个范围的时候,后一次查询看到了前一次查询没有看到的行。

    幻读在"当前读"下会出现;幻读仅专指"新插入的行";

    for update 语句:表示当前读; 当前读的规则,就是要能读到所有已经提交的记录的最新值;

    update 的加锁语义和select…for update 是一致的;

如何解决幻读?
  • 间隙锁(Gap lock):两个值之间的锁;

    间隙锁和行锁合称:next-key lock:每个next-key lock是前开后闭区间;

间隙锁引入什么问题?
  • 可能会导致同样的语句锁住更大的范围,这其实是影响了并发度的;

  • 间隙锁在RR级别下才有效,RC级别下无间隙锁;

    不使用间隙锁方法:

    使用RC隔离 + binlog_format = row 组合;

为什么有kill不掉的语句?
  • 这些的"kill 不掉的情况",是因为发送kill命令的客户端,并没有强行停止目标线程的执行,而只是设置了状态,并唤醒对应的线程;而被kill的线程,需要执行到判断的状态"埋点",才会开始进入终止逻辑阶段(耗费时间);

  • 如果你发现一个线程处于killed状态,可做的事情,通过影响系统环境,让这个killed状态尽快结束;

    比如InnoDB并发的问题,就可以临时调大innodb_thread_concurrency的值,或者停掉别的线程,让出位子给这个线程执行;

    而回滚逻辑由于收到IO资源限制执行得比较慢,就通过减少系统压力让它加速;

  • kill query + 线程ID:表示终止这个线程中正在执行的语句;

  • kill connection + 线程ID:断开这个线程的(show processlist 会显示killed);

查大量数据,会不会把数据库内存打爆?
  • 由于MySQL采用的是边算边发的逻辑,因此对于数据量很大的查询结果来说,不会在server端保存完整的结果集。所以,客户端结果不及时,会堵住MySQL查询过程,但不会把内存打爆。

    如果一个查询的返回结果不会很多的话,建议使用my_sql_store_result,这个接口,直接把查询结果保存到本地内存

    show processlist;

    当一个线程处于,等待客户端接收结果的状态,会显示"Sending to client",如果显示成"Sending data",它的意思只是正在执行;

  • 对于InnoDB引擎内部,由于有淘汰策略,大查询也不会导致内存暴涨。并且,由于InnoDB对LRU算法做了改进,冷数据的全表扫描,对Buffer Pool的影响也能做到可控。

    Buffer Pool 有加速查询的作用,依赖于一个重要的指标,“内存命中率”,使用命令show engine innodb status,查看一个系统当前的BP命中率,一般情况下,一个稳定服务的线上系统,要保证响应时间符合要求的话,内存命中率在99%以上;

    InnoDB Buffer Pool 的大小由参数innodb_buffer_pool_size确定,一般建议设置成可用物理内存60%~80%,如果一个Buffer Pool满了,而要从磁盘读入一个数据页,那肯定是要淘汰一个旧的数据页;

    InnoDB内存管理用的是(Least Recently Used,LRU)算法,这个算法的核心就是淘汰最久未使用的数据;

什么场景自增主键可能不是连续的?

1.唯一键冲突;

2.事务回滚;

3.自增主键的批量申请;

自增主键的作用?
  • 让主键索引尽量地保持递增顺序插入,避免页分裂,使索引更紧凑;
自增主键的保存机制?

不同的存储引擎,机制不同:

  • MyISAM引擎的自增值保存在数据文件中;
  • InnoDB引擎的自增值,保存在了内存里,到了MySQL 8.0版本后,才有了"自增值持久化"的能力,放在了red log里面;
自增主键的修改机制?

在MySQL里面,如果字段id被定义为AUTO_INCREMENT,在插入一行数据的时候,自增值的行为如下:

1.如果插入数据时,id字段指定为0、null或者未指定值,那么把这个表当前的AUTO_INCREMENT值填到自增字段;

2.如果插入数据时id字段,指定了具体的值,就直接使用语句里指定的值;

插入值跟当前自增值的关系:

根据要插入的值和当前自增值的大小关系,自增值的变更结果也会有所不同。假设,某次要插入的值是X,当前的自增值是Y。

1:如果 X < Y,那么这个表的自增值不变;

2:如果 X ≥ Y,那就需要把当前自增值修改为新的自增值;

两表之间拷贝数据用什么方法,有什么注意事项?

1.使用insert…select 两表之间拷贝数据:

需要注意,可重复读隔离级别下,这个语句会给select的表里扫描到的记录和间隙加读锁;

如果insert…select 的对象是同一个表,有可能会造成循环写入。这种情况下,我们需要引入用户临时表来做优化;

insert 语句如果出现唯一键冲突,会在冲突的唯一值上加共享的next-key lock(S锁)。如果碰到由于唯一键约束导致报错后,要尽快提交或回滚事务,避免加锁时间过长;

2.导出成execl,然后拼sql成insert into values(),(),()形成,进行批量插入再;

3.多个线程分到几个任务执行,比如十个线程,每个线程10条记录,插入后,再查询新的100条记录处理;

怎么最快地复制一张表?

1.物理拷贝的方式速度最快,尤其是对大表拷贝来说是最快的方法;如果出现误删表的情况,用备份恢复出,误删之前的临时库,然后再把临时库中的表拷贝到生产库上,是恢复数据最快的方法。但是也会有一定的局限性:

  • 必须是全表拷贝,不能拷贝部分数据;
  • 需要到服务器上拷贝数据,再用户无法登录数据库主机的场景下无法使用;
  • 由于通过拷贝物理文件实现的,源表和目标表都是使用InnoDB引擎时才能使用;

2.使用mysqldump生成包含INSERT语句文件的方法,可以在where参数增加过滤条件,来实现只导出部分数据。这个方式不足之处,不能使用join这种比较复杂的where条件写法;

3.用select … into outfile 的方法是最灵活的,支持所有的SQL写法。但这个方法的缺点就是,每次只能导出一张表的数据,而且表结构也需要另外的语句单独备份;

后两种方式都是逻辑备份方式,是可以跨引擎使用的。

物理拷贝表的功能:

查看当前mysql数据文件所存放的位置:

mysql> show global variables like "%datadir%";
+---------------+---------------------------------------------+
| Variable_name | Value                                       |
+---------------+---------------------------------------------+
| datadir       | C:\ProgramData\MySQL\MySQL Server 5.7\Data\ |
+---------------+---------------------------------------------+

在MySQL5.6版本引入了可传输表空间的方法,可以通过导出 + 导入表空间的方式,实现物理拷贝表的功能;

举个栗子:

假设目标在db1库下,复制一个跟表t相同的表r,具体执行步骤如下:

执行create table r like t;   创建一个相同表结构的空表;
执行alter table r discard tablespace;  这时候r.ibd文件会被删除;
执行flush table t for export;   这时候db1目录下会生成一个t.cfg文件;
在db1目录下执行 cp t.cfg r.cfg;  cp t.ibd r.ibd; 这两个命令(拷贝得到两个文件, MYSQL进行要有读写权限);
执行unlock tables; 这时候t.cfg 文件会被删除;
执行alter table r import tablespace; 将这个r.ibd 文件作为表r的新的表空间, 由于文件的数据内容和t.ibd是相同的,所以表r中就有了和表t相同的数据;
  • 需要注意点:

    在第3步执行完flshu table 命令后,db1.t整个表处于只读状态,执行unlock tables 命令后才释放读锁;

    在执行import tablespace的时候,让文件里的表空间id和数据字典中一致,会修改r.ibd表的空间id。而表空间id存在每一个数据页中。如果文件很大,每个数据页都需要修改,可能import 语句执行是需要一些时间的。

MySQL中索引结构和Buffer Pool相关知识点:

1.在对被驱动表做全表扫描的时候,如果数据没有在Buffer Pool中,就需要等待这部分数据从磁盘读入;

从磁盘读入数据到内存中,会影响正常业务的Buffer Pool命中率,而且这个算法天然会对被驱动表的数据做多次访问,更容易将这些数据页放到Buffer Pool的头部;

2.即使被驱动表数据都在内存中,每次查找"下一个记录的操作",都是类似指针操作。而join_buffer中是数组,遍历的成本更低;

BNL算法的性能会更好;

发布了147 篇原创文章 · 获赞 170 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/Fe_cow/article/details/103217023