MySQL45讲实战篇笔记(17-22)

17. 如何正确显示随机数

直接使用order byrand(),语句需要Using temporary和 Using filesort, 查询的执行代
价往往是比较大的。
MySQL对临时表排序的执行过程

  1. 先全表扫描复制到临时表(扫描全部的行)
  2. 对临时表排序,全表扫描(扫描全部的行),取出要排序的字段值,和位置信息(临时表在内存中没有索引)
  3. 根据位置信息,取需要的数据(随机IO,只需要扫描n行)

limit关键字扫描多少行?
MySQL处理limit Y,1 的做法就是按顺序一个一个地读出来, 丢掉前Y个, 然后把下一个记录作为返回结果, 因此这一步需要扫描Y+1行

18. 为什么SQL语句逻辑相同,性能却差异巨大?

对索引字段做函数操作或者计算, 可能会破坏索引值的有序性, 因此优化器就决定放弃走树搜索功能。
此外隐式的类型转换或者字符集转换,实际也是加了"函数操作",因此也会放弃走索引。
解决方法:试着修改sql的查询条件,避免直接做函数操作,比如将函数操作放在查询条件的右边,比如where id=1000-1不要写成id+1=1000

19.为什么只查询一行语句也执行地这么慢?

如果MySQL数据库本身就有很大的压力, 导致数据库服务器CPU占用率很高或ioutil(IO利用率) 很高, 这种情况下所有语句的执行都有可能变慢,不在讨论范围内。

  • 查询长时间不返回
    大概率是表t被锁住了,分析原因的时候, 一般首先执行show processlist命令, 查看当前语句处于什么状态
    1.等MDL锁
    Waiting for table metadata lock,有一个线程正在表t上请求或者持有MDL写锁, 把select语句
    堵住了,处理方式: 找到谁持有MDL写锁, 然后把它kill掉
    2.等flush
    Waiting for table flush 可能情况是: 有一个flush tables命令被别的语句堵住了, 然后它又堵住了select语句。(flush table(s) 的作用关闭所有已打开的表对象(同一个库中,没有sql运行的表是不计算在内),同时将查询缓存中的结果清空。就是说Flush tables的一个效果就是会等待所有正在运行的SQL请求结束)
    3.等行锁
    解决方式:kill掉占用锁线程的语句(不一定有用),或者直接关闭占有线程的链接,此时会回滚,释放行锁
  • 查询慢
  1. 没有索引
  2. 有索引,但是为一致性读,都之前其他事务修改了多次,由于mvcc导致需要回放很多次undo log才能计算到一致性试图

20.幻读是什么?

幻读指的是一个事务在前后两次查询同一个范围的时候, 后一次查询看到了前一次查询没有看到的行。普通的查询是快照读, 是不会看到别的事务插入的数据的。 因此,幻读在“当前读”下才会出现。

幻读有什么问题?

  1. 加锁的语义失效
  2. 数据不一致问题,即使把所有的记录都加上锁, 还是阻止不了新插入的记录

产生原因:行锁只能锁住行, 但是新插入记录这个动作, 要更新的是记录之间的“间隙”

解决办法:InnoDB只好引入了是间隙锁(GapLock)

  • 跟间隙锁存在冲突关系的, 是“往这个间隙中插入一个记录”这个操作, 间隙锁之间都不存在冲突关系
  • 间隙锁和行锁合称next-keylock, 每个next-keylock是前开后闭区间。 也就是说 如果用select * from t for update要把整个表所有记录锁起来, 就形成了7个next-keylock, 分别是 (-∞,0]、 (0,5]、 (5,10]、 (10,15]、 (15,20]、 (20, 25]、 (25, +supremum
  • 间隙锁和next-key lock的引入, 帮我们解决了幻读的问题, 但同时也带来了一些“困扰”。 可能会导致同样的语句锁住更大的范围, 这其实是影响了并发度的,甚至会导致死锁
    -间隙锁是在可重复读隔离级别下才会生效的。

所以如果把隔离级别设置为读提交的话,就没有间隙锁了。 但同时需要解决可能出现的数据和日志不一致问题(RC模式下采用的是statment,binlog的记录顺序是以commit方式提交的,并发事务会导致日志不一致), 需要把binlog格式设置为row。(如果读提交隔离级别够用,即业务不需要可重复读的保
证,读提交+binlog_format = row 这个选择是合理)

21. 为什么我只改一行的语句, 锁这么多?(MySQL的加锁规则)

5.x系列<=5.7.24, 8.0系列 <=8.0.13,默认可重复读级别的加锁规则:
锁都是加在索引上的,总结为两个“原则”、 两个“优化”和一个“bug”

  1. 原则1: 加锁的基本单位是next-keylock, next-keylock是前开后闭区间,(m,n]
  2. 原则2: 查找过程中访问到的对象才会加锁。
  3. 优化1: 索引上的等值查询, 给唯一索引加锁的时候, next-keylock退化为行锁。
  4. 优化2: 索引上的等值查询, 向右遍历时且最后一个值不满足等值条件的时候, next-keylock退化为间隙锁。(非唯一索引一般是向后遍历,如果desc排序则会向前遍历保证顺序,但是找到第一个满足值后依然为向后再遍历一个间隙加锁,然后再从后往前倒叙遍历,具体参考21讲课后思考题)
  5. 一个bug:唯一索引上的范围查询会访问到不满足条件的第一个值为止

索引加锁
如果是覆盖索引,则锁不会加到主键索引上,符合规则2。 但如果select 选了其他字段,则会锁住主键索引

limit语句加锁
在循环扫描到满足limit的行数时候,就停止了,并不会继续向右扫描,按上述规则分析的锁范围很可能会变小。因此建议删除数据的时候尽量加limit, 这样不仅可以控制删除数据的条数让操作更安全, 还可以减小加锁的范围

next-key lock 两步加锁
分析加锁规则的时候可以用next-keylock来分析。 但是具体执行的时候, 是要分成间隙锁和行锁两段来执行的

例如:


image.png
  1. session A 启动事务后执行查询语句加lock in share mode, 在索引c上加了next-key
    lock(5,10] 和间隙锁(10,15);
  2. session B 的update语句也要在索引c上加next-keylock(5,10] , 进入锁等待;
  3. 然后session A要再插入(8,8,8)这一行, 被session B的间隙锁锁住。 由于出现了死
    锁, InnoDB让session B回滚。

你可能会问, session B的next-keylock不是还没申请成功吗?为什么会死锁?
其实是这样的, session B的“加next-keylock(5,10] ”操作, 实际上分成了两步, 先是加(5,10)的间隙锁加锁成功; 然后加c=10的行锁, 这时候才被锁住的。(申请到了间隙锁,因此session A的插入语句和sessionB的更新语句形成了死锁)

关于读提交隔离级别
间隙锁的部分,只剩下行锁的部分。
但其实读提交隔离级别在外键场景下还是有间隙锁, 相对比较复杂先不展开。
另外, 在读提交隔离级别下还有一个优化, 即: 语句执行过程中加上的行锁, 在语句执行完成
后, 就要把“不满足条件的行”上的行锁直接释放了, 不需要等到事务提交

也就是说, 读提交隔离级别下, 锁的范围更小, 锁的时间更短, 这也是不少业务都默认使用读提
交隔离级别的原因

22.MySQL有哪些“饮鸩止渴”提高性能的方法?

  • 短连接风暴
    正常的短连接模式就是连接到数据库后, 执行很少的SQL语句就断开, 下次需要的时候再重连。如果使用的是短连接, 在业务高峰期的时候, 就可能出现连接数突然暴涨的情况。
    解决方法:
    1) 先处理掉那些占着连接但是不工作的线程
    比如优先断开事务外空闲太久的连接,从服务端断开连接使用的是kill connection + id的命令, 一个客户端处于sleep状态时, 它的连接被服务端主动断开后, 这个客户端并不会马上知道。 直到客户端在发起下一个请求的时候, 才会收到报错
    2)减少连接过程的消耗
    比如让数据库跳过权限验证阶段,跳过权限验证的方法是: 重启数据库, 并使用–skip-grant-tables参数启动。 这样, 整个MySQL会跳过所有的权限验证阶段, 包括连接过程和语句执行过程在内。但是, 这种方法风险极高。

在实际开发中, 我们也要尽量避免一些低效的方法, 比如避免大量地使用短连接。 同时, 连接异常断开是常有的事, 代码里要有正确地重连并重试的机制。

  • 慢查询性能问题
    在MySQL中, 会引发性能问题的慢查询, 大体有以下三种可能:
    1.) 索引没有设计好;->紧急创建索引
    2.) SQL语句没写好;->query_rewrite功能, 查询重写,但是会存在误伤
    3.)MySQL选错了索引; ->使用force index
  • QPS突增问题
    1.) 一种是由全新业务的bug导致的。 假设你的DB运维是比较规范的, 也就是说白名单是一个个加的。 这种情况下, 如果你能够确定业务方会下掉这个功能, 只是时间上没那么快, 那么就可以从数据库端直接把白名单去掉。
    2.) 如果这个新功能使用的是单独的数据库用户, 可以用管理员账号把这个用户删掉, 然后断开现有连接。 这样这个新功能的连接不成功, 由它引发的QPS就会变成0。
    3.)如果这个新增的功能跟主体功能是部署在一起的, 那么我们只能通过处理语句来限制,可以使用上面提到的查询重写功能, 把压力最大的SQL语句直接重写成"select 1"返回

你可能感兴趣的:(MySQL45讲实战篇笔记(17-22))