[转帖]深入理解mysql-第十二章 mysql查询优化-Explain 详解(下)

深入,理解,mysql,第十二章,查询,优化,explain,详解 · 浏览次数 : 0

小编点评

## MySQL优化器步骤分析 **文章知识点与官方知识档案匹配** * 文章中对优化器的步骤分析部分与官方知识档案中的“优化过程”部分一致。 * 文章中对EXPLAIN语句分析部分与官方知识档案中的“EXPLAIN”部分一致。 **主要步骤分析** 1. **prepare阶段**: * 对单表查询的执行方案分析与官方知识档案中的“分析执行方案”部分一致。 * 对多表连接查询的执行方案分析与官方知识档案中的“分析执行方案”部分一致。 * 优化器最终选择成本最低的方案,因此这个阶段主要关注“优化方案”部分。 2. **optimize阶段**: * 对单表查询的执行方案分析与官方知识档案中的“分析执行方案”部分一致。 * 对多表连接查询的执行方案分析与官方知识档案中的“分析执行方案”部分一致。 * 优化器最终选择成本最低的方案,因此这个阶段主要关注“优化方案”部分。 3. **execute阶段**: * 对单表查询的执行方案分析与官方知识档案中的“分析执行方案”部分一致。 * 对多表连接查询的执行方案分析与官方知识档案中的“分析执行方案”部分一致。 * 优化器最终选择成本最低的方案,因此这个阶段主要关注“执行方案”部分。 **总结** * MySQL优化器主要分为三个阶段:**prepare阶段、optimize阶段、execute阶段**。 * **prepare阶段**主要关注对单表查询的执行方案分析。 * **optimize阶段**主要关注对多表连接查询的执行方案分析。 * **execute阶段**主要关注对单表查询的执行方案分析。 * 优化器最终选择成本最低的方案,因此这个阶段主要关注“优化方案”部分。

正文

我们前面两章详解了Explain的各个属性,我们看到的都是mysql已经生成的执行计划,那这个执行计划的是如何生成的?我们能看到一些过程指标数据吗?实际mysql贴心为我们提供了执行计划的各项成本评估指标的以及优化器生成执行计划的整个过程的方法。

一、查看执行计划计算的成本数据

  我们上边介绍的EXPLAIN语句输出中缺少了一个衡量执行计划好坏的重要属性 -成本。那如何查看具体的成本呢?MySQL也贴心的为我们提供了一种查看某个执行计划花费的成本的方式-Json格式的执行计划。我们只需要在EXPLAIN单词和真正的查询语句中间加上FORMAT=JSON。

  1. mysql> EXPLAIN FORMAT=JSON SELECT * FROM s1 INNER JOIN s2 ON s1.key1 = s2.key2 WHERE s1.common_field = 'a'\G
  2. *************************** 1. row ***************************
  3. EXPLAIN: {
  4. "query_block": {
  5. "select_id": 1, # 整个查询语句只有1SELECT关键字,该关键字对应的id号为1
  6. "cost_info": {
  7. "query_cost": "3197.16" # 整个查询的执行成本预计为3197.16
  8. },
  9. "nested_loop": [ # 几个表之间采用嵌套循环连接算法执行
  10. # 以下是参与嵌套循环连接算法的各个表的信息
  11. {
  12. "table": {
  13. "table_name": "s1", # s1表是驱动表
  14. "access_type": "ALL", # 访问方法为ALL,意味着使用全表扫描访问
  15. "possible_keys": [ # 可能使用的索引
  16. "idx_key1"
  17. ],
  18. "rows_examined_per_scan": 9688, # 查询一次s1表大致需要扫描9688条记录
  19. "rows_produced_per_join": 968, # 驱动表s1的扇出是968
  20. "filtered": "10.00", # condition filtering代表的百分比
  21. "cost_info": {
  22. "read_cost": "1840.84", # 稍后解释
  23. "eval_cost": "193.76", # 稍后解释
  24. "prefix_cost": "2034.60", # 单次查询s1表总共的成本
  25. "data_read_per_join": "1M" # 读取的数据量
  26. },
  27. "used_columns": [ # 执行查询中涉及到的列
  28. "id",
  29. "key1",
  30. "key2",
  31. "key3",
  32. "key_part1",
  33. "key_part2",
  34. "key_part3",
  35. "common_field"
  36. ],
  37. # 对s1表访问时针对单表查询的条件
  38. "attached_condition": "((`xiaohaizi`.`s1`.`common_field` = 'a') and (`xiaohaizi`.`s1`.`key1` is not null))"
  39. }
  40. },
  41. {
  42. "table": {
  43. "table_name": "s2", # s2表是被驱动表
  44. "access_type": "ref", # 访问方法为ref,意味着使用索引等值匹配的方式访问
  45. "possible_keys": [ # 可能使用的索引
  46. "idx_key2"
  47. ],
  48. "key": "idx_key2", # 实际使用的索引
  49. "used_key_parts": [ # 使用到的索引列
  50. "key2"
  51. ],
  52. "key_length": "5", # key_len
  53. "ref": [ # 与key2列进行等值匹配的对象
  54. "xiaohaizi.s1.key1"
  55. ],
  56. "rows_examined_per_scan": 1, # 查询一次s2表大致需要扫描1条记录
  57. "rows_produced_per_join": 968, # 被驱动表s2的扇出是968(由于后边没有多余的表进行连接,所以这个值也没啥用)
  58. "filtered": "100.00", # condition filtering代表的百分比
  59. # s2表使用索引进行查询的搜索条件
  60. "index_condition": "(`xiaohaizi`.`s1`.`key1` = `xiaohaizi`.`s2`.`key2`)",
  61. "cost_info": {
  62. "read_cost": "968.80", # 稍后解释
  63. "eval_cost": "193.76", # 稍后解释
  64. "prefix_cost": "3197.16", # 单次查询s1、多次查询s2表总共的成本
  65. "data_read_per_join": "1M" # 读取的数据量
  66. },
  67. "used_columns": [ # 执行查询中涉及到的列
  68. "id",
  69. "key1",
  70. "key2",
  71. "key3",
  72. "key_part1",
  73. "key_part2",
  74. "key_part3",
  75. "common_field"
  76. ]
  77. }
  78. }
  79. ]
  80. }
  81. }
  82. 1 row in set, 2 warnings (0.00 sec)

二、查询语句重写后的语句

    在我们使用EXPLAIN语句查看了某个查询的执行计划后,紧接着还可以使用SHOW WARNINGS语句查看与这个查询的执行计划有关的一些扩展信息,其中Message字段展示的信息类似于查询优化器将我们的查询语句重写后的语句。

  1. mysql> EXPLAIN SELECT s1.key1, s2.key1 FROM s1 LEFT JOIN s2 ON s1.key1 = s2.key1 WHERE s2.common_field IS NOT NULL;
  2. +----+-------------+-------+------------+------+---------------+----------+---------+-------------------+------+----------+-------------+
  3. | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
  4. +----+-------------+-------+------------+------+---------------+----------+---------+-------------------+------+----------+-------------+
  5. | 1 | SIMPLE | s2 | NULL | ALL | idx_key1 | NULL | NULL | NULL | 9954 | 90.00 | Using where |
  6. | 1 | SIMPLE | s1 | NULL | ref | idx_key1 | idx_key1 | 303 | xiaohaizi.s2.key1 | 1 | 100.00 | Using index |
  7. +----+-------------+-------+------------+------+---------------+----------+---------+-------------------+------+----------+-------------+
  8. 2 rows in set, 1 warning (0.00 sec)
  9. mysql> SHOW WARNINGS\G
  10. *************************** 1. row ***************************
  11. Level: Note
  12. Code: 1003
  13. Message: /* select#1 */ select `xiaohaizi`.`s1`.`key1` AS `key1`,`xiaohaizi`.`s2`.`key1` AS `key1` from `xiaohaizi`.`s1` join `xiaohaizi`.`s2` where ((`xiaohaizi`.`s1`.`key1` = `xiaohaizi`.`s2`.`key1`) and (`xiaohaizi`.`s2`.`common_field` is not null))
  14. 1 row in set (0.00 sec)

     比如我们上边的查询本来是一个左(外)连接查询,但是有一个s2.common_field IS NOT NULL的条件,着就会导致查询优化器把左(外)连接查询优化为内连接查询,从SHOW WARNINGSMessage字段也可以看出来,原本的LEFT JOIN已经变成了JOIN

   但是大家一定要注意,我们说Message字段展示的信息类似于查询优化器将我们的查询语句重写后的语句,并不是等价于,也就是说Message字段展示的信息并不是标准的查询语句,在很多情况下并不能直接拿来运行。

三、查看优化器生成执行计划的过程

    对于MySQL 5.6以及之前的版本来说,查询优化器就像是一个黑盒子一样,你只能通过EXPLAIN语句查看到最后优化器决定使用的执行计划,却无法知道它为什么做这个决策。在MySQL 5.6以及之后的版本中,MySQL提出了一个optimizer trace的功能,这个功能可以让我们方便的查看优化器生成执行计划的整个过程。

    功能的开启与关闭由系统变量optimizer_trace决定(默认是关闭的)。当我们打开这个功能,我们就可以输入我们想要查看优化过程的查询语句,当该查询语句执行完成后,就可以到information_schema数据库下的OPTIMIZER_TRACE表中查看完整的优化过程。具体的流程:

  1. # 1. 打开optimizer trace功能 (默认情况下它是关闭的):
  2. SET optimizer_trace="enabled=on";
  3. # 2. 这里输入你自己的查询语句
  4. SELECT ...;
  5. # 3. 从OPTIMIZER_TRACE表中查看上一个查询的优化过程
  6. SELECT * FROM information_schema.OPTIMIZER_TRACE;
  7. # 4. 可能你还要观察其他语句执行的优化过程,重复上边的第23
  8. ...
  9. # 5. 当你停止查看语句的优化过程时,把optimizer trace功能关闭
  10. SET optimizer_trace="enabled=off";

举例说明一下:

  1. SET optimizer_trace="enabled=on";
  2. SELECT * FROM s1 WHERE
  3. key1 > 'z' AND
  4. key2 < 1000000 AND
  5. key3 IN ('a', 'b', 'c') AND
  6. common_field = 'abc';
  7. SELECT * FROM information_schema.OPTIMIZER_TRACE\G

输出:

  1. *************************** 1. row ***************************
  2. # 分析的查询语句是什么
  3. QUERY: SELECT * FROM s1 WHERE
  4. key1 > 'z' AND
  5. key2 < 1000000 AND
  6. key3 IN ('a', 'b', 'c') AND
  7. common_field = 'abc'
  8. # 优化的具体过程
  9. TRACE: {
  10. "steps": [
  11. {
  12. "join_preparation": { # prepare阶段
  13. "select#": 1,
  14. "steps": [
  15. {
  16. "IN_uses_bisection": true
  17. },
  18. {
  19. "expanded_query": "/* select#1 */ select `s1`.`id` AS `id`,`s1`.`key1` AS `key1`,`s1`.`key2` AS `key2`,`s1`.`key3` AS `key3`,`s1`.`key_part1` AS `key_part1`,`s1`.`key_part2` AS `key_part2`,`s1`.`key_part3` AS `key_part3`,`s1`.`common_field` AS `common_field` from `s1` where ((`s1`.`key1` > 'z') and (`s1`.`key2` < 1000000) and (`s1`.`key3` in ('a','b','c')) and (`s1`.`common_field` = 'abc'))"
  20. }
  21. ] /* steps */
  22. } /* join_preparation */
  23. },
  24. {
  25. "join_optimization": { # optimize阶段
  26. "select#": 1,
  27. "steps": [
  28. {
  29. "condition_processing": { # 处理搜索条件
  30. "condition": "WHERE",
  31. # 原始搜索条件
  32. "original_condition": "((`s1`.`key1` > 'z') and (`s1`.`key2` < 1000000) and (`s1`.`key3` in ('a','b','c')) and (`s1`.`common_field` = 'abc'))",
  33. "steps": [
  34. {
  35. # 等值传递转换
  36. "transformation": "equality_propagation",
  37. "resulting_condition": "((`s1`.`key1` > 'z') and (`s1`.`key2` < 1000000) and (`s1`.`key3` in ('a','b','c')) and (`s1`.`common_field` = 'abc'))"
  38. },
  39. {
  40. # 常量传递转换
  41. "transformation": "constant_propagation",
  42. "resulting_condition": "((`s1`.`key1` > 'z') and (`s1`.`key2` < 1000000) and (`s1`.`key3` in ('a','b','c')) and (`s1`.`common_field` = 'abc'))"
  43. },
  44. {
  45. # 去除没用的条件
  46. "transformation": "trivial_condition_removal",
  47. "resulting_condition": "((`s1`.`key1` > 'z') and (`s1`.`key2` < 1000000) and (`s1`.`key3` in ('a','b','c')) and (`s1`.`common_field` = 'abc'))"
  48. }
  49. ] /* steps */
  50. } /* condition_processing */
  51. },
  52. {
  53. # 替换虚拟生成列
  54. "substitute_generated_columns": {
  55. } /* substitute_generated_columns */
  56. },
  57. {
  58. # 表的依赖信息
  59. "table_dependencies": [
  60. {
  61. "table": "`s1`",
  62. "row_may_be_null": false,
  63. "map_bit": 0,
  64. "depends_on_map_bits": [
  65. ] /* depends_on_map_bits */
  66. }
  67. ] /* table_dependencies */
  68. },
  69. {
  70. "ref_optimizer_key_uses": [
  71. ] /* ref_optimizer_key_uses */
  72. },
  73. {
  74. # 预估不同单表访问方法的访问成本
  75. "rows_estimation": [
  76. {
  77. "table": "`s1`",
  78. "range_analysis": {
  79. "table_scan": { # 全表扫描的行数以及成本
  80. "rows": 9688,
  81. "cost": 2036.7
  82. } /* table_scan */,
  83. # 分析可能使用的索引
  84. "potential_range_indexes": [
  85. {
  86. "index": "PRIMARY", # 主键不可用
  87. "usable": false,
  88. "cause": "not_applicable"
  89. },
  90. {
  91. "index": "idx_key2", # idx_key2可能被使用
  92. "usable": true,
  93. "key_parts": [
  94. "key2"
  95. ] /* key_parts */
  96. },
  97. {
  98. "index": "idx_key1", # idx_key1可能被使用
  99. "usable": true,
  100. "key_parts": [
  101. "key1",
  102. "id"
  103. ] /* key_parts */
  104. },
  105. {
  106. "index": "idx_key3", # idx_key3可能被使用
  107. "usable": true,
  108. "key_parts": [
  109. "key3",
  110. "id"
  111. ] /* key_parts */
  112. },
  113. {
  114. "index": "idx_key_part", # idx_keypart不可用
  115. "usable": false,
  116. "cause": "not_applicable"
  117. }
  118. ] /* potential_range_indexes */,
  119. "setup_range_conditions": [
  120. ] /* setup_range_conditions */,
  121. "group_index_range": {
  122. "chosen": false,
  123. "cause": "not_group_by_or_distinct"
  124. } /* group_index_range */,
  125. # 分析各种可能使用的索引的成本
  126. "analyzing_range_alternatives": {
  127. "range_scan_alternatives": [
  128. {
  129. # 使用idx_key2的成本分析
  130. "index": "idx_key2",
  131. # 使用idx_key2的范围区间
  132. "ranges": [
  133. "NULL < key2 < 1000000"
  134. ] /* ranges */,
  135. "index_dives_for_eq_ranges": true, # 是否使用index dive
  136. "rowid_ordered": false, # 使用该索引获取的记录是否按照主键排序
  137. "using_mrr": false, # 是否使用mrr
  138. "index_only": false, # 是否是索引覆盖访问
  139. "rows": 12, # 使用该索引获取的记录条数
  140. "cost": 15.41, # 使用该索引的成本
  141. "chosen": true # 是否选择该索引
  142. },
  143. {
  144. # 使用idx_key1的成本分析
  145. "index": "idx_key1",
  146. # 使用idx_key1的范围区间
  147. "ranges": [
  148. "z < key1"
  149. ] /* ranges */,
  150. "index_dives_for_eq_ranges": true, # 同上
  151. "rowid_ordered": false, # 同上
  152. "using_mrr": false, # 同上
  153. "index_only": false, # 同上
  154. "rows": 266, # 同上
  155. "cost": 320.21, # 同上
  156. "chosen": false, # 同上
  157. "cause": "cost" # 因为成本太大所以不选择该索引
  158. },
  159. {
  160. # 使用idx_key3的成本分析
  161. "index": "idx_key3",
  162. # 使用idx_key3的范围区间
  163. "ranges": [
  164. "a <= key3 <= a",
  165. "b <= key3 <= b",
  166. "c <= key3 <= c"
  167. ] /* ranges */,
  168. "index_dives_for_eq_ranges": true, # 同上
  169. "rowid_ordered": false, # 同上
  170. "using_mrr": false, # 同上
  171. "index_only": false, # 同上
  172. "rows": 21, # 同上
  173. "cost": 28.21, # 同上
  174. "chosen": false, # 同上
  175. "cause": "cost" # 同上
  176. }
  177. ] /* range_scan_alternatives */,
  178. # 分析使用索引合并的成本
  179. "analyzing_roworder_intersect": {
  180. "usable": false,
  181. "cause": "too_few_roworder_scans"
  182. } /* analyzing_roworder_intersect */
  183. } /* analyzing_range_alternatives */,
  184. # 对于上述单表查询s1最优的访问方法
  185. "chosen_range_access_summary": {
  186. "range_access_plan": {
  187. "type": "range_scan",
  188. "index": "idx_key2",
  189. "rows": 12,
  190. "ranges": [
  191. "NULL < key2 < 1000000"
  192. ] /* ranges */
  193. } /* range_access_plan */,
  194. "rows_for_plan": 12,
  195. "cost_for_plan": 15.41,
  196. "chosen": true
  197. } /* chosen_range_access_summary */
  198. } /* range_analysis */
  199. }
  200. ] /* rows_estimation */
  201. },
  202. {
  203. # 分析各种可能的执行计划
  204. #(对多表查询这可能有很多种不同的方案,单表查询的方案上边已经分析过了,直接选取idx_key2就好)
  205. "considered_execution_plans": [
  206. {
  207. "plan_prefix": [
  208. ] /* plan_prefix */,
  209. "table": "`s1`",
  210. "best_access_path": {
  211. "considered_access_paths": [
  212. {
  213. "rows_to_scan": 12,
  214. "access_type": "range",
  215. "range_details": {
  216. "used_index": "idx_key2"
  217. } /* range_details */,
  218. "resulting_rows": 12,
  219. "cost": 17.81,
  220. "chosen": true
  221. }
  222. ] /* considered_access_paths */
  223. } /* best_access_path */,
  224. "condition_filtering_pct": 100,
  225. "rows_for_plan": 12,
  226. "cost_for_plan": 17.81,
  227. "chosen": true
  228. }
  229. ] /* considered_execution_plans */
  230. },
  231. {
  232. # 尝试给查询添加一些其他的查询条件
  233. "attaching_conditions_to_tables": {
  234. "original_condition": "((`s1`.`key1` > 'z') and (`s1`.`key2` < 1000000) and (`s1`.`key3` in ('a','b','c')) and (`s1`.`common_field` = 'abc'))",
  235. "attached_conditions_computation": [
  236. ] /* attached_conditions_computation */,
  237. "attached_conditions_summary": [
  238. {
  239. "table": "`s1`",
  240. "attached": "((`s1`.`key1` > 'z') and (`s1`.`key2` < 1000000) and (`s1`.`key3` in ('a','b','c')) and (`s1`.`common_field` = 'abc'))"
  241. }
  242. ] /* attached_conditions_summary */
  243. } /* attaching_conditions_to_tables */
  244. },
  245. {
  246. # 再稍稍的改进一下执行计划
  247. "refine_plan": [
  248. {
  249. "table": "`s1`",
  250. "pushed_index_condition": "(`s1`.`key2` < 1000000)",
  251. "table_condition_attached": "((`s1`.`key1` > 'z') and (`s1`.`key3` in ('a','b','c')) and (`s1`.`common_field` = 'abc'))"
  252. }
  253. ] /* refine_plan */
  254. }
  255. ] /* steps */
  256. } /* join_optimization */
  257. },
  258. {
  259. "join_execution": { # execute阶段
  260. "select#": 1,
  261. "steps": [
  262. ] /* steps */
  263. } /* join_execution */
  264. }
  265. ] /* steps */
  266. }
  267. # 因优化过程文本太多而丢弃的文本字节大小,值为0时表示并没有丢弃
  268. MISSING_BYTES_BEYOND_MAX_MEM_SIZE: 0
  269. # 权限字段
  270. INSUFFICIENT_PRIVILEGES: 0
  271. 1 row in set (0.00 sec)

    上面其实这只是优化器执行过程中的一小部分,MySQL可能会在之后的版本中添加更多的优化过程信息。不过杂乱之中其实还是蛮有规律的,优化过程大致分为了三个阶段:

  • prepare阶段

  • optimize阶段

  • execute阶段

    我们所说的基于成本的优化主要集中在optimize阶段,对于单表查询来说,我们主要关注optimize阶段的"rows_estimation"这个过程,这个过程深入分析了对单表查询的各种执行方案的成本;对于多表连接查询来说,我们更多需要关注"considered_execution_plans"这个过程,这个过程里会写明各种不同的连接方式所对应的成本。反正优化器最终会选择成本最低的那种方案来作为最终的执行计划,也就是我们使用EXPLAIN语句所展现出的那种方案。

文章知识点与官方知识档案匹配,可进一步学习相关知识
MySQL入门技能树查询优化 SHOW STATUS58201 人正在系统学习中

与[转帖]深入理解mysql-第十二章 mysql查询优化-Explain 详解(下)相似的内容:

[转帖]深入理解mysql-第十二章 mysql查询优化-Explain 详解(下)

我们前面两章详解了Explain的各个属性,我们看到的都是mysql已经生成的执行计划,那这个执行计划的是如何生成的?我们能看到一些过程指标数据吗?实际mysql贴心为我们提供了执行计划的各项成本评估指标的以及优化器生成执行计划的整个过程的方法。 一、查看执行计划计算的成本数据 我们上边介绍的EXP

[转帖]深入理解mysql-第十章 mysql查询优化-Explain 详解(上)

目录 一、初识Explain 二、执行计划-table属性 三、执行计划-id属性 四、执行计划-select_type属性 一条查询语句在经过MySQL查询优化器的各种基于成本和规则的优化会后生成一个所谓的执行计划,这个执行计划展示了接下来具体执行查询的方式,比如多表连接的顺序是什么,对于每个表采

[转帖]深入理解mysql-第十一章 mysql查询优化-Explain 详解(中)

一、执行计划-type属性 执行计划的一条记录就代表着MySQL对某个表的执行查询时的访问方法,其中的type列就表明了这个访问这个单表的方法具体是什么,比方说下边这个查询: mysql> EXPLAIN SELECT * FROM s1 WHERE key1 = 'a';+ + + + + + +

[转帖]深入理解mysql-第六章 mysql存储引擎InnoDB的索引-B+树索引

一、引入索引 在没有索引的情况下,不论是根据主键列或者其他列的值进行查找,由于我们并不能快速的定位到记录所在的页,所以只能从第一个页沿着双向链表一直往下找,因为要遍历所有的数据页,时间复杂度就是O(n),所以这种方式显然是超级耗时的。所以我们需要采取一定的数据结构来存储数据,方便我们进行数据的增删改

[转帖]深入理解mysql-第五章 InnoDB记录存储结构-页结构

前言: 页是InnoDB管理存储空间的基本单位,上一章我们主要分析了页中的主要的构成行的存储结构-行格式,其中简单提了一下页的概念。这章我们详细讲解一下页的存储结构。 一、数据页结构 前边我们简单提了一下页的概念,它是InnoDB管理存储空间的基本单位,一个页的大小一般是16KB。和存储一条条数据的

[转帖]Redis 运维实战 第01期:Redis 复制

https://cloud.tencent.com/developer/article/1986816 作者简介 马听,多年 DBA 实战经验,对 MySQL、 Redis、ClickHouse 等数据库有一定了解,专栏《一线数据库工程师带你深入理解 MySQL》作者。 从这篇文章开始,将出几期 R

[转帖]MySQL 慢查询日志深入理解

https://www.jb51.net/article/210312.htm + 目录 什么是慢查询日志 MySQL的慢查询日志是 MySQL提供的一种日志记录,它用来记录在 MySQL 中响应时间超过阀值的语句,具体指运行时间超过long_query_time 值的 SQL,则会被记录到慢查询日

[转帖]Intel PAUSE指令变化如何影响MySQL的性能

https://zhuanlan.zhihu.com/p/581200704 导读 x86、arm指令都很多,无论是应用程序员还是数据库内核研发大多时候都不需要对这些指令深入理解,但是 Pause 指令和数据库操作太紧密了,本文通过一次非常有趣的性能优化来引入对 Pause 指令的理解,期望可以事半

【转帖】mysql一个索引块有多少指针_深刻理解MySQL系列之索引

索引 查找一条数据的过程 先看下InnoDB的逻辑存储结构:node 表空间:能够看作是InnoDB存储引擎逻辑结构的最高层,全部的数据都存放在表空间中。默认有个共享表空间ibdata1。若是启用innodb_file_per_table参数,须要注意每张表的表空间内存放的只是数据、索引和插入缓冲B

[转帖]深入理解同步机制---内核自旋锁

https://switch-router.gitee.io/blog/spinlock/ 进程(线程)间的同步机制是面试时的常见问题,所以准备用一个系列来好好整理下用户态与内核态的各种同步机制。本文就以内核空间的一种基础同步机制—自旋锁开始好了 自旋锁是什么 自旋锁就是一个二状态的原子(atomi