← 返回目录

6. 查询效率

第 167 / 230 卷日期:

5-4:count(字段)怎么计数?

1、如果这个“字段”是定义为 not null 的话

一行行地从记录里面读出这个字段,判断不能为 null,按行累加;

2、如果这个“字段”定义允许为 null

执行的时候,判断到有可能是 null,还要把值取出来再判断一下,不是 null 才累加。

从引擎返回的字段会涉及到解析数据行,以及拷贝字段值的操作。

5-5:count(主键 id)怎么计数?

对于 count(主键 id) 来说,InnoDB 引擎会遍历整张表,把每一行的 id 值都取出来,返回给 server 层。

server 层拿到 id 后,判断是不可能为空的,就按行累加。从引擎返回的主键id 会涉及到解析数据行,以及拷贝字段值的操作。

5-5:count(1)怎么计数?

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

5-6:count(*)怎么计数?

对于count(*)来说,并不会把全部字段取出来,而是专门做了优化,不取值。

count(*) 肯定不是 null,按行累加。

这些count()的原则如下:

server 层要什么就给什么;

InnoDB 只给必要的值;

现在的优化器只优化了 count(*) 的语义为“取行数”,其他“显而易见”的优化并没有做。

6. 查询效率

6-1:order by 是怎样工作的?

example:

select city,name,age from t where city='杭州' order by name limit 1000;

涉及到用户语句的排序,mysql 会给每个线程分配一块内存用于排序,也就是 sort_buffer。

这条语句的执行逻辑是:

1、先初始化 sort_bufer

2、然后放入 city,name,age 字段,不断地由主键id索引到整行再到三个字段的值,匹配查找的值存入 sort_buff

超大数据的排序相同。

如果要记录的字段太长,这样内存里能够同时放下的行数很少,要分成很多个临时文件,排序的性能会很差。

这时会换一个算法,叫做rowid排序,顾名思义,就是对主键 id 以及排序字段进行存放,这样就节省了空间,但是最后需要通过主键 id 去找到之前未取出的字段。对比全字段排序,rowid 排序多访问了一次表 t 的主键索引。

结论:

如果 MySQL 实在是担心排序内存太小,会影响排序效率,才会采用 rowid 排序算法,这样排序过程中一次可以排序更多行,但是需要再回到原表去取数据。

如果 MySQL 认为内存足够大,会优先选择全字段排序,把需要的字段都放到 sort_buffer 中,这样排序后就会直接从内存里面返回查询结果了,不用再回到原表去取数据。

如果想要避免排序,可以建立对应字段的索引。

如果想要进一步避免主键 id 的回表查询,可以使用覆盖索引,这种情况的索引建立成本会比较大,需要你去自己权衡是否使用

6-2:如何正确地显示随机消息?

example:

英语学习 App 首页有一个随机显示单词的功能,也就是根据每个用户的级别有一个单词表,然后这个用户每次访问首页的时候,都会随机滚动显示三个单词。他们发现随着单词表变大,选单词这个逻辑变得越来越慢,甚至影响

到了首页的打开速度

1、order by rand() 实现

创建临时表→按主键顺序取出所有的单词,并给他们一个随机小数→初始化 sort_buffer,从内存临时表取出数据放入 sort_buffer,按随机数排序,取出前三个,总扫描行数是2×n+3

2、磁盘临时表

如果 sort_buffer_size 设置的小,就会使用到磁盘临时表用于辅助排序,当然 mysql 高版本使用了优先队列排序的方法,就是只取三个值,构成一个堆,然后不断把剩下的值输入,用于更新队列。

这个过程不需要临时文件,因此对应的 number_of_tmp_files 是 0。当然,如果堆的大小设置的很大, sort_buffer 放不下,就会舍弃优先队列,重新使用临时表+归并排序来实现,number_of_tmp_files 就不再为 0。

3、随机id方法

我们实际上并不需要真正的为每行赋予一个随机值,然后进行排序,这样的成本就太高了。

可以借鉴主键id的唯一性,生成一个介于主键id最小值和最大值之间的随机数,然后取不小于这个随机数的主键id 的那一行即可。总扫描行数是3,的那hi是如果主键id有空洞,就会导致选择不同行的概率不同,不是真正的随机

4、严格随机id方法

取出表的总行数,生成一个总行数对应的随机值,取不小于这个随机数的主键id的那一行,总扫描行数是C+Y+1

6-3:为什么SQL语句逻辑相同,性能却差异巨大?

并且在 t_modified 上建立了索引

你以为对字段生成了索引,应该会返回的很快,但是mysql有一个规则:就是如果对字段做了函数计算,就用不上索引了。

实际上,mysql使用的B+ 树提供的对于索引的快速定位能力,来源于同一层兄弟节点的有序性。对索引字段做函数操作,可能会破坏索引值的有序性,因此优化器就决定放弃走树搜索功能,转而进行全表扫描,所以运行就变慢了

案例二:隐式类型转换:

如果在限制语句中错误的将字符串与数字进行比较,mysql 会将可以转化成数字的字符串转化成数字,没法转换的话,就会变成数字 0,因此如果出现这种输入的失误,相当于调用了字符串转数字的函数,还是会使得优化器会放

弃走树搜索功能,进行全表扫描

案例三:隐式字符编码转换

如果执行两个表的联合查询,两个表通过外键进行联结,如果两个表使用的字符集不同,会对低水平的字符集执行升级转换函数,(相当于将int赋给double的这个思路)。

如果限制语句是 where d.tradeid=l.tradeid,如果d的字符集低水平,就会执行函数操作,使得优化器会放弃走树搜索功能,进行全表扫描

总结:对索引字段做函数操作,可能会破坏索引值的有序性,因此优化器就决定放弃走树搜索功能

6-4:为什么我只查一行的语句,也执行这么慢?

原因从大到小可分为三种情况

第一 MySQL 数据库本身被堵住了,比如:系统或网络资源不够

第二 SQL 语句被堵住了,比如:表锁,行锁等,导致存储引擎不执行对应的 SQL 语句

第三确实是索引使用不当,没有走索引,类比上一节

第四是表中数据的特点导致的,走了索引,但由于一致性读,需要回滚多次才能读取出当前事务对应的值。

6-5:为什么临时表可以重名?

在实际应用中,临时表一般用于处理比较复杂的计算逻辑。

由于临时表是每个线程自己可见的,所以不需要考虑多个线程执行同一个处理逻辑时,临时表的重名问题。在线程退出的时候,临时表也能自动删除,省去了收尾和异常处理的工作。

在 binlog_format='row’的时候,临时表的操作不记录到 binlog 中,也省去了不少麻烦,防止出现主备不一致的情况

这里说到的临时表是用户临时表,而不是内存临时表

6-5:group-by使用原则

如果对 group by 语句的结果没有排序要求,要在语句后面加 order by null;

尽量让 group by 过程用上表的索引,确认方法是 explain 结果里没有 Using temporary 和 Using filesort;

全站分卷索引 · 230 卷
  1. 1代码随想录知识星球精华(最强八股文)第五版(计算机基础篇)
  2. 2DNS
  3. 3DNS解析过程
  4. 4递归查询和迭代查询
  5. 5HTTP特性与简述
  6. 6HTTP版本演变
  7. 7HTTP/2
  8. 8HTTP1.1协议的性能问题
  9. 9兼容HTTP1.1
  10. 10二进制帧
  11. 11并发传输
  12. 12服务器主动推送资源
  13. 13总结
  14. 14HTTP/3
  15. 15HTTP/2的缺点
  16. 16QUIC协议的特点
  17. 17HTTP/3协议
  18. 18HTTP缓存
  19. 19协商缓存
  20. 20HTTPS
  21. 21HTTPS的特点
  22. 22非对称加密
  23. 23摘要算法+数字签名
  24. 24数字证书
  25. 25证书信任链
  26. 26HTTPS是怎么建立连接的
  27. 27TCP与UDP头部格式
  28. 28TCP
  29. 29列举TCP选项
  30. 30TCP三次握手
  31. 31为什么需要三次握手?
  32. 32什么是半连接队列
  33. 33什么是SYN攻击
  34. 34为什么挥手需要四次
  35. 35TIME_WAIT 过多有什么危害?
  36. 36TCP的滑动窗口
  37. 374. 程序是如何表示发送方的四个部分的呢?
  38. 386. 接收窗口和发送窗口的大小是相等的吗?
  39. 391. 慢启动
  40. 403. 拥塞发生
  41. 414. 快速恢复
  42. 42IP基础
  43. 43IP地址
  44. 44在浏览器中输入URL并按下回车之后会发生什么
  45. 45TCP 抓包实践
  46. 46TCP连接与断开优化
  47. 47TCP内核参数
  48. 48主动方的优化
  49. 49被动方的优化
  50. 50Linux是如何收发网络包的
  51. 51收发流程
  52. 52面试题
  53. 53TCP/IP四层网络模型
  54. 54五层网络体系结构
  55. 55HTTP响应报文
  56. 56HTTP常见字段
  57. 57什么是TCP粘包问题?如何解决TCP粘包问题?
  58. 58UDP会不会产生粘包问题呢?
  59. 59请求报文
  60. 60时间消耗
  61. 61HTTP缓存有几种
  62. 62HTTP1.0和HTTP1.1的区别?
  63. 63HTPP1.1和HTTP1.0有什么区别
  64. 64HTTP/3
  65. 65HTTPS是如何建立连接的
  66. 66DNS是什么
  67. 67递归查询和迭代查询
  68. 68TCP与UDP的区别
  69. 69TCP和UDP分别在什么场景使用?
  70. 70三次握手的过程
  71. 71Keep-Alive的优缺点
  72. 72CDN的工作流程
  73. 73Session的工作原理
  74. 74TCP重传机制是怎么实现的?
  75. 75什么是SS0
  76. 76HTTP优化方案
  77. 77浏览器地址栏输入URL回车后涉及到的流程
  78. 781.5 浏览器发送HTTP/HTTPS请求到web服务器
  79. 79SYN攻击
  80. 80操作系统
  81. 81操作系统的功能
  82. 82进程和线程
  83. 83线程基础
  84. 84进程调度
  85. 85进程调度方式
  86. 86进程通信
  87. 87消息传递(消息队列)
  88. 88信号量
  89. 89信号
  90. 90线程通信
  91. 91多线程
  92. 92进程通信
  93. 93消息队列
  94. 94信号
  95. 95守护进程
  96. 96互斥与同步
  97. 974. 互斥锁解锁
  98. 983. 处理方法
  99. 99读写锁
  100. 1002. 销毁读写锁
  101. 1014. 写锁定
  102. 1021. 创建条件变量
  103. 1034. 阻塞线程
  104. 104信号量
  105. 1054. V操作
  106. 1062. 读者-写者问题
  107. 107存储系统
  108. 108虚拟内存
  109. 109内存分段
  110. 110内存分页
  111. 111虚拟内存
  112. 112文件系统
  113. 1135. 文件访问
  114. 114目录
  115. 1154. 目录操作
  116. 1163. 目录的实现
  117. 1177. 虚拟文件系统
  118. 1184. 文件系统性能
  119. 119磁盘编址
  120. 120三层抽象
  121. 121外设管理
  122. 122Linux
  123. 123select/poll/epoll的区别和联系
  124. 124Linux 内存管理
  125. 125Linux虚拟内存
  126. 126Linux 信号
  127. 127Linux设计
  128. 1283. 各种IO模型的比较
  129. 129Linux网络
  130. 130Linux中的软链接和硬链接有什么区别?
  131. 131什么是中断和异常
  132. 132信号和信号量有什么区别
  133. 133Linux中如何查看一个进程
  134. 134进程、线程的区别?
  135. 135进程之间的通信方式
  136. 136如何解除死锁
  137. 137分段和分页的区别
  138. 138IO多路复用
  139. 139MySQL表连接
  140. 140过滤条件
  141. 141执行一次update语句,发生了什么
  142. 142用户账号存储
  143. 143用户账号删除
  144. 144索引
  145. 145索引有哪些类别
  146. 1462. 按照存储分类
  147. 1475. 按照字段个数分类
  148. 148索引区分度
  149. 149索引什么时候会失效
  150. 150事务的四大特性ACID
  151. 151并行事务会出现什么问题
  152. 152幻读是如何解决的
  153. 153事务隔离的实现
  154. 154读已提交是怎么实现的
  155. 155锁
  156. 1561、全局锁
  157. 1573、行级锁
  158. 158锁的划分
  159. 1592.、程序员角度
  160. 160Innodb使用表锁还是行锁
  161. 161什么是binlog
  162. 162执行引擎
  163. 163MySQL常见问题
  164. 1642. 事务
  165. 1653. 脏页
  166. 1664. 表结构
  167. 1676. 查询效率
  168. 168MySQL分库分表
  169. 169垂直分表
  170. 170水平分库
  171. 171分库分表的问题
  172. 172数据库排序思路
  173. 173MySQL主从复制
  174. 1745. 主从复制模式
  175. 175面试题
  176. 176执行一条select语句,发生了什么?
  177. 177你对关系范式有哪些了解
  178. 178数据库
  179. 179Redis基础
  180. 180Redis单线程为什么还这么快
  181. 181Redis底层数据结构
  182. 182Redis持久化
  183. 183AOF日志
  184. 184RDB快照
  185. 185Redis功能
  186. 1862. 哨兵模式
  187. 1873. 切片集群
  188. 188Redis哨兵机制是什么
  189. 189什么是集群脑裂
  190. 190过期删除策略有哪些
  191. 191Redis的LRU算法和LFU算法有什么区别
  192. 192什么是缓存击穿
  193. 193如何保证删除缓存操作一定能成功?
  194. 194为什么Redis是单线程
  195. 195Redis底层使用了什么数据结构
  196. 196Redis实现分布式锁
  197. 197缓存中的热点数据和冷数据是什么
  198. 198缓存雪崩、击穿、穿透和解决办法【常问】
  199. 199Redis过期删除策略有哪些
  200. 200关键概念
  201. 201单例模式的应用场景
  202. 202工厂模式
  203. 203实际应用
  204. 204抽象工厂模式
  205. 205概念
  206. 206应用
  207. 207实现步骤
  208. 208主要角色
  209. 209代理模式应用
  210. 210策略模式
  211. 211示例
  212. 212装饰模式
  213. 213主要角色
  214. 21423种设计模式的概念
  215. 215创建型模式
  216. 216行为型模式
  217. 217设计原则
  218. 218迪米特法则
  219. 219你在实际开发中用过哪些设计模式
  220. 220依赖倒置
  221. 221代理模式和适配器模式有什么区别
  222. 222固定长度编码
  223. 223冯诺依曼模型
  224. 224总线
  225. 225输入/输出设备
  226. 226编译系统如何工作
  227. 227处理器解释指令
  228. 228场景题 & 系统设计
  229. 229如何设计一个本地缓存
  230. 230如何设计一个RPC框架