作者:微信小助手
<section data-mpa-powered-by="yiban.io"> 大家好,我是码哥,可以叫我靓仔。 <br> </section> <section data-tool="mdnice编辑器" data-website="https://www.mdnice.com" style="font-size: 16px;color: black;line-height: 1.6;letter-spacing: 0px;word-break: break-word;overflow-wrap: break-word;text-align: left;" pingfang=""> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.6;color: rgb(63, 63, 63);margin-top: 10px;margin-bottom: 10px;">我们用 explain 分析包含 group by 的 select 语句时,从输出结果的 Extra 列经常可以看到 <code style="overflow-wrap: break-word;margin-right: 2px;margin-left: 2px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;background: rgb(248, 245, 236);color: rgb(255, 53, 2);line-height: 1.5;font-size: 90%;padding: 3px 5px;border-radius: 2px;">Using temporary; Using filesort</code>。看到这个,我们就知道 MySQL 使用了临时表来实现 group by。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.6;color: rgb(63, 63, 63);margin-top: 10px;margin-bottom: 10px;">使用临时表实现 group by,成本高,执行慢。如果能够利用索引中记录已经排好序的特性,使用索引来实现 group by,那就是鸟枪换炮了。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.6;color: rgb(63, 63, 63);margin-top: 10px;margin-bottom: 10px;">本文我们一起来探寻 MySQL 使用索引实现 group by 的过程,使用临时表实现 group by 会单独用一篇文章来介绍。</p> <blockquote data-tool="mdnice编辑器" style="border-top: none;border-right: none;border-bottom: none;font-size: 0.9em;overflow: auto;color: rgb(91, 91, 91);border-left-color: rgb(158, 158, 158);background: rgba(158, 158, 158, 0.1);padding-top: 1px;padding-bottom: 1px;margin-top: 20px;margin-bottom: 20px;"> <p style="color: rgb(63, 63, 63);line-height: 1.5;font-size: 16px;margin: 10px;">本文内容基于 MySQL 5.7.35 源码。</p> </blockquote> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.6;color: rgb(63, 63, 63);margin-top: 10px;margin-bottom: 10px;"><strong style="color: rgb(255, 53, 2);line-height: 1.5;">内容目录</strong></p> <ol data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 引言 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 紧凑索引扫描 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 松散索引扫描 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 两种索引扫描怎么选? <br>4.1 松散索引扫描成本更高怎么办? <br>4.2 为什么松散索引扫描会比紧凑索引扫描成本高? <br> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 总结 </section></li> </ol> <section class="mp_profile_iframe_wrp"> <mpprofile class="js_uneditable custom_select_card mp_profile_iframe" data-pluginname="mpprofile" data-id="MzkzMDI1NjcyOQ==" data-headimg="http://mmbiz.qpic.cn/mmbiz_png/EoJib2tNvVtf7icAmS0BQH6oDVG37Q8NzcfdguS5qAqOhfxvZyIKqmuX5BbnDjynrBbZzktp1EiaeFLzapp1nHysw/0?wx_fmt=png" data-nickname="码哥字节" data-alias="MageByte" data-signature="拥抱硬核技术和对象,面向人民币编程。" data-from="0"></mpprofile> </section> <h2 data-tool="mdnice编辑器" style="margin: 80px 10px 40px;text-align: center;color: rgb(63, 63, 63);font-size: 140%;"><span style="display: none;"></span>1. 引言</h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.6;color: rgb(63, 63, 63);margin-top: 10px;margin-bottom: 10px;">使用索引实现 group by,最简单的方式,大概就是这样了:</p> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 存储引擎按顺序一条一条读取记录,返回给 server 层。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> server 层判断记录是否符合 where 条件。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> server 层对符合条件的记录进行聚合函数逻辑处理。 </section></li> </ul> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.6;color: rgb(63, 63, 63);margin-top: 10px;margin-bottom: 10px;">这种实现方式被称为<code style="overflow-wrap: break-word;margin-right: 2px;margin-left: 2px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;background: rgb(248, 245, 236);color: rgb(255, 53, 2);line-height: 1.5;font-size: 90%;padding: 3px 5px;border-radius: 2px;">紧凑索引扫描</code>。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.6;color: rgb(63, 63, 63);margin-top: 10px;margin-bottom: 10px;">紧凑索引扫描会对满足 where 条件的所有记录进行聚合函数处理,而对于 min()、max() 来说,实际需要的只有每个分组中聚合函数字段值最小或最大的那条记录。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.6;color: rgb(63, 63, 63);margin-top: 10px;margin-bottom: 10px;">如果 server 层能直接从存储引擎读取到每个分组中聚合函数需要的那条记录,而不必读取每个分组中的所有记录进行聚合函数处理,是不是就可以节省很多时间了?</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.6;color: rgb(63, 63, 63);margin-top: 10px;margin-bottom: 10px;">是的,这种只读取分组中部分记录实现 group by 的方式,被称为<code style="overflow-wrap: break-word;margin-right: 2px;margin-left: 2px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;background: rgb(248, 245, 236);color: rgb(255, 53, 2);line-height: 1.5;font-size: 90%;padding: 3px 5px;border-radius: 2px;">松散索引扫描</code>。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.6;color: rgb(63, 63, 63);margin-top: 10px;margin-bottom: 10px;">为了方便描述,本文在需要的时候会以具体 SQL 作为示例说明,示例 SQL 的表结构如下:</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;"><span style="display: block;background: url("https://mmbiz.qpic.cn/mmbiz_svg/XCopLcwfzeckrSbEysLMibBhb3pZu4TfWjjJkogGqTibdrrXHE2nOWxFfXZJkDqoj5CicvDk0fm6WbtgicTZSEXNukNJ0DFmlHJia/640?wx_fmt=svg") 10px 10px / 40px no-repeat rgb(40, 44, 52);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;padding: 16px;color: #abb2bf;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #282c34;border-radius: 5px;"><span style="color: #c678dd;line-height: 26px;">CREATE</span> <span style="color: #c678dd;line-height: 26px;">TABLE</span> <span style="color: #98c379;line-height: 26px;">`t_group_by`</span> (<br> <span style="color: #98c379;line-height: 26px;">`id`</span> <span style="color: #e6c07b;line-height: 26px;">int</span>(<span style="color: #d19a66;line-height: 26px;">10</span>) <span style="color: #c678dd;line-height: 26px;">unsigned</span> <span style="color: #c678dd;line-height: 26px;">NOT</span> <span style="color: #56b6c2;line-height: 26px;">NULL</span> AUTO_INCREMENT,<br> <span style="color: #98c379;line-height: 26px;">`i1`</span> <span style="color: #e6c07b;line-height: 26px;">int</span>(<span style="color: #d19a66;line-height: 26px;">10</span>) <span style="color: #c678dd;line-height: 26px;">unsigned</span> <span style="color: #c678dd;line-height: 26px;">DEFAULT</span> <span style="color: #98c379;line-height: 26px;">'0'</span>,<br> <span style="color: #98c379;line-height: 26px;">`c1`</span> <span style="color: #e6c07b;line-height: 26px;">char</span>(<span style="color: #d19a66;line-height: 26px;">11</span>) <span style="color: #c678dd;line-height: 26px;">DEFAULT</span> <span style="color: #98c379;line-height: 26px;">''</span>,<br> <span style="color: #98c379;line-height: 26px;">`e1`</span> enum(<span style="color: #98c379;line-height: 26px;">'北京'</span>,<span style="color: #98c379;line-height: 26px;">'上海'</span>,<span style="color: #98c379;line-height: 26px;">'广州'</span>,<span style="color: #98c379;line-height: 26px;">'深圳'</span>,<span style="color: #98c379;line-height: 26px;">'天津'</span>,<span style="color: #98c379;line-height: 26px;">'杭州'</span>,<span style="color: #98c379;line-height: 26px;">'成都'</span>,<span style="color: #98c379;line-height: 26px;">'重庆'</span>,<span style="color: #98c379;line-height: 26px;">'苏州'</span>,<span style="color: #98c379;line-height: 26px;">'南京'</span>,<span style="color: #98c379;line-height: 26px;">'洽尔滨'</span>,<span style="color: #98c379;line-height: 26px;">'沈阳'</span>,<span style="color: #98c379;line-height: 26px;">'长春'</span>,<span style="color: #98c379;line-height: 26px;">'厦门'</span>,<span style="color: #98c379;line-height: 26px;">'福州'</span>,<span style="color: #98c379;line-height: 26px;">'南昌'</span>,<span style="color: #98c379;line-height: 26px;">'泉州'</span>,<span style="color: #98c379;line-height: 26px;">'德清'</span>,<span style="color: #98c379;line-height: 26px;">'长沙'</span>,<span style="color: #98c379;line-height: 26px;">'武汉'</span>) <span style="color: #c678dd;line-height: 26px;">DEFAULT</span> <span style="color: #98c379;line-height: 26px;">'北京'</span>,<br> <span style="color: #98c379;line-height: 26px;">`d1`</span> <span style="color: #e6c07b;line-height: 26px;">decimal</span>(<span style="color: #d19a66;line-height: 26px;">10</span>,<span style="color: #d19a66;line-height: 26px;">2</span>) <span style="color: #c678dd;line-height: 26px;">DEFAULT</span> <span style="color: #56b6c2;line-height: 26px;">NULL</span>,<br> PRIMARY <span style="color: #c678dd;line-height: 26px;">KEY</span> (<span style="color: #98c379;line-height: 26px;">`id`</span>),<br> <span style="color: #c678dd;line-height: 26px;">KEY</span> <span style="color: #98c379;line-height: 26px;">`idx_e1_i1`</span> (<span style="color: #98c379;line-height: 26px;">`e1`</span>,<span style="color: #98c379;line-height: 26px;">`i1`</span>) <span style="color: #c678dd;line-height: 26px;">USING</span> BTREE<br>) <span style="color: #c678dd;line-height: 26px;">ENGINE</span>=<span style="color: #c678dd;line-height: 26px;">InnoDB</span> <span style="color: #c678dd;line-height: 26px;">DEFAULT</span> <span style="color: #c678dd;line-height: 26px;">CHARSET</span>=utf8;<br></code></pre> <h2 data-tool="mdnice编辑器" style="margin: 80px 10px 40px;text-align: center;color: rgb(63, 63, 63);font-size: 140%;"><span style="display: none;"></span>2. 紧凑索引扫描</h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.6;color: rgb(63, 63, 63);margin-top: 10px;margin-bottom: 10px;">group by 字段包含在索引中,并且满足索引最左匹配原则,server 层就可以顺序读取索引中的记录实现 group by,而不需要借助临时表。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.6;color: rgb(63, 63, 63);margin-top: 10px;margin-bottom: 10px;">紧凑索引扫描中的<code style="overflow-wrap: break-word;margin-right: 2px;margin-left: 2px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;background: rgb(248, 245, 236);color: rgb(255, 53, 2);line-height: 1.5;font-size: 90%;padding: 3px 5px;border-radius: 2px;">紧凑</code>,表示 server 层从存储引擎读取记录时,以索引范围扫描或全索引扫描方式,按顺序一条一条读取记录,不会跳过中间的某条记录,示意图如下:</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="1.023201856148492" src="/upload/dd60f19840000a1ae072af6ee1625383.png" data-type="png" data-w="431" style="display: block;margin-right: auto;margin-left: auto;height: auto !important;"> <figcaption style="margin-top: 5px;text-align: center;color: #888;font-size: 14px;"> 紧凑索引扫描 </figcaption> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.6;color: rgb(63, 63, 63);margin-top: 10px;margin-bottom: 10px;">接下来,我们以 <code style="overflow-wrap: break-word;margin-right: 2px;margin-left: 2px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;background: rgb(248, 245, 236);color: rgb(255, 53, 2);line-height: 1.5;font-size: 90%;padding: 3px 5px;border-radius: 2px;">avg()</code> 为例介绍<code style="overflow-wrap: break-word;margin-right: 2px;margin-left: 2px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;background: rgb(248, 245, 236);color: rgb(255, 53, 2);line-height: 1.5;font-size: 90%;padding: 3px 5px;border-radius: 2px;">紧凑索引扫描</code>的执行过程,示例 SQL 如下:</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;"><span style="display: block;background: url("https://mmbiz.qpic.cn/mmbiz_svg/XCopLcwfzeckrSbEysLMibBhb3pZu4TfWjjJkogGqTibdrrXHE2nOWxFfXZJkDqoj5CicvDk0fm6WbtgicTZSEXNukNJ0DFmlHJia/640?wx_fmt=svg") 10px 10px / 40px no-repeat rgb(40, 44, 52);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;padding: 16px;color: #abb2bf;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #282c34;border-radius: 5px;"><span style="color: #c678dd;line-height: 26px;">select</span><br> e1, <span style="color: #c678dd;line-height: 26px;">avg</span>(i1) <span style="color: #c678dd;line-height: 26px;">as</span> t<br><span style="color: #c678dd;line-height: 26px;">from</span> t_group_by<br><span style="color: #c678dd;line-height: 26px;">where</span> d1 > <span style="color: #d19a66;line-height: 26px;">5452415</span><br><span style="color: #c678dd;line-height: 26px;">group</span> <span style="color: #c678dd;line-height: 26px;">by</span> e1<br></code></pre> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.6;color: rgb(63, 63, 63);margin-top: 10px;margin-bottom: 10px;"><strong style="color: rgb(255, 53, 2);line-height: 1.5;">词法分析 & 语法分析阶段</strong>,avg(i1) 被解析为 Item_sum_avg 类,以下是该类的实例属性的其中 3 个:</p> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> <code style="overflow-wrap: break-word;margin-right: 2px;margin-left: 2px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;background: rgb(248, 245, 236);color: rgb(255, 53, 2);line-height: 1.5;font-size: 90%;padding: 3px 5px;border-radius: 2px;">sum</code>,保存分组求和结果。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> <code style="overflow-wrap: break-word;margin-right: 2px;margin-left: 2px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;background: rgb(248, 245, 236);color: rgb(255, 53, 2);line-height: 1.5;font-size: 90%;padding: 3px 5px;border-radius: 2px;">count</code>,保存分组计数。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> <code style="overflow-wrap: break-word;margin-right: 2px;margin-left: 2px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;background: rgb(248, 245, 236);color: rgb(255, 53, 2);line-height: 1.5;font-size: 90%;padding: 3px 5px;border-radius: 2px;">args</code>,avg() 函数的参数,avg() 只能有一个参数。args[0] 为 i1 字段对应的 Item_field 类实例。 </section></li> </ul> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-cropselx1="0" data-cropselx2="578" data-cropsely1="0" data-cropsely2="186" data-ratio="0.3208092485549133" src="/upload/7dbe9c9bad65daf0e636d3bd8464b4a1.png" data-type="png" data-w="692" style="display: block;margin-right: auto;margin-left: auto;height: 185px;width: 578px;"> <figcaption style="margin-top: 5px;text-align: center;color: #888;font-size: 14px;"> Item_sum_avg </figcaption> </figure> <blockquote data-tool="mdnice编辑器" style="border-top: none;border-right: none;border-bottom: none;font-size: 0.9em;overflow: auto;color: rgb(91, 91, 91);border-left-color: rgb(158, 158, 158);background: rgba(158, 158, 158, 0.1);padding-top: 1px;padding-bottom: 1px;margin-top: 20px;margin-bottom: 20px;"> <p style="color: rgb(63, 63, 63);line-height: 1.5;font-size: 16px;margin: 10px;"><strong style="color: rgb(255, 53, 2);line-height: 1.5;">avg() 只有一个参数,为什么参数属性名是 args?</strong><br><br>Item_sum_avg 类的实例属性 <code style="overflow-wrap: break-word;margin-right: 2px;margin-left: 2px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;background: rgb(248, 245, 236);color: rgb(255, 53, 2);line-height: 1.5;font-size: 90%;padding: 3px 5px;border-radius: 2px;">args</code> 是从父类 Item_sum 继承得到的。<br><br>Item_sum_count 类(<code style="overflow-wrap: break-word;margin-right: 2px;margin-left: 2px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;background: rgb(248, 245, 236);color: rgb(255, 53, 2);line-height: 1.5;font-size: 90%;padding: 3px 5px;border-radius: 2px;">count() 对应的类</code>)的实例属性 <code style="overflow-wrap: break-word;margin-right: 2px;margin-left: 2px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;background: rgb(248, 245, 236);color: rgb(255, 53, 2);line-height: 1.5;font-size: 90%;padding: 3px 5px;border-radius: 2px;">args</code> 也是从父类 Item_sum 继承的,count() 可以有多个参数,所以,用 args 来表示聚合函数的参数。</p> </blockquote> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.6;color: rgb(63, 63, 63);margin-top: 10px;margin-bottom: 10px;"><strong style="color: rgb(255, 53, 2);line-height: 1.5;">查询准备阶段(prepare 阶段)</strong>,i1 字段对应的 Item_field 类实例会关联到表 t_group_by 的 i1 字段。</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-cropselx1="0" data-cropselx2="578" data-cropsely1="0" data-cropsely2="169" data-ratio="0.31650246305418717" src="/upload/e1ea9c512eed1f47f6e89d514a6347e6.png" data-type="png" data-w="812" style="display: block;margin-right: auto;margin-left: auto;height: 183px;width: 578px;"> <figcaption style="margin-top: 5px;text-align: center;color: #888;font-size: 14px;"> Item_sum_avg </figcaption> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.6;color: rgb(63, 63, 63);margin-top: 10px;margin-bottom: 10px;"><strong style="color: rgb(255, 53, 2);line-height: 1.5;">执行阶段</strong>,server 层从存储引擎读取到一条记录之后,判断记录是否符合 where 条件(<code style="overflow-wrap: break-word;margin-right: 2px;margin-left: 2px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;background: rgb(248, 245, 236);color: rgb(255, 53, 2);line-height: 1.5;font-size: 90%;padding: 3px 5px;border-radius: 2px;">d1 > 5452415</code>)。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.6;color: rgb(63, 63, 63);margin-top: 10px;margin-bottom: 10px;">记录不符合 where 条件,继续读取下一条记录。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.6;color: rgb(63, 63, 63);margin-top: 10px;margin-bottom: 10px;">记录符合 where 条件,进行聚合函数逻辑处理。<br><em style="color: black;">如果当前记录的<code style="overflow-wrap: break-word;margin-right: 2px;margin-left: 2px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;background: rgb(248, 245, 236);color: rgb(255, 53, 2);line-height: 1.5;font-size: 90%;padding: 3px 5px;border-radius: 2px;">分组前缀</code>(示例 SQL 中 group by 的 e1 字段值)和上一条记录的分组前缀<code style="overflow-wrap: break-word;margin-right: 2px;margin-left: 2px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;background: rgb(248, 245, 236);color: rgb(255, 53, 2);line-height: 1.5;font-size: 90%;padding: 3px 5px;border-radius: 2px;">不一样</code>,说明需要结束上一个分组,并开启新分组。</em></p> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> <code style="overflow-wrap: break-word;margin-right: 2px;margin-left: 2px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;background: rgb(248, 245, 236);color: rgb(255, 53, 2);line-height: 1.5;font-size: 90%;padding: 3px 5px;border-radius: 2px;">结束上一个分组</code>:通过 <code style="overflow-wrap: break-word;margin-right: 2px;margin-left: 2px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;background: rgb(248, 245, 236);color: rgb(255, 53, 2);line-height: 1.5;font-size: 90%;padding: 3px 5px;border-radius: 2px;">sum / count</code> 计算得到分组平均值(即 avg(i1) 的结果),把 <code style="overflow-wrap: break-word;margin-right: 2px;margin-left: 2px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;background: rgb(248, 245, 236);color: rgb(255, 53, 2);line-height: 1.5;font-size: 90%;padding: 3px 5px;border-radius: 2px;">分组前缀</code>及 <code style="overflow-wrap: break-word;margin-right: 2px;margin-left: 2px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;background: rgb(248, 245, 236);color: rgb(255, 53, 2);line-height: 1.5;font-size: 90%;padding: 3px 5px;border-radius: 2px;">分组平均值</code>发送给客户端。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> <code style="overflow-wrap: break-word;margin-right: 2px;margin-left: 2px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;background: rgb(248, 245, 236);color: rgb(255, 53, 2);line-height: 1.5;font-size: 90%;padding: 3px 5px;border-radius: 2px;">开启新分组</code>:Item_sum_avg 类的实例属性 sum、count <code style="overflow-wrap: break-word;margin-right: 2px;margin-left: 2px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;background: rgb(248, 245, 236);color: rgb(255, 53, 2);line-height: 1.5;font-size: 90%;padding: 3px 5px;border-radius: 2px;">清零</code>,当前记录的 e1 字段值作为 <code style="overflow-wrap: break-word;margin-right: 2px;margin-left: 2px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;background: rgb(248, 245, 236);color: rgb(255, 53, 2);line-height: 1.5;font-size: 90%;padding: 3px 5px;border-radius: 2px;">新分组前缀</code>,然后,新分组进行分组求和(sum 加上 i1 字段值)、分组计数(count 加 1)。 </section></li> </ul> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.6;color: rgb(63, 63, 63);margin-top: 10px;margin-bottom: 10px;"><em style="color: black;">如果当前记录的<code style="overflow-wrap: break-word;margin-right: 2px;margin-left: 2px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;background: rgb(248, 245, 236);color: rgb(255, 53, 2);line-height: 1.5;font-size: 90%;padding: 3px 5px;border-radius: 2px;">分组前缀</code>和上一条记录的分组前缀<code style="overflow-wrap: break-word;margin-right: 2px;margin-left: 2px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;background: rgb(248, 245, 236);color: rgb(255, 53, 2);line-height: 1.5;font-size: 90%;padding: 3px 5px;border-radius: 2px;">一样</code>,说明还是同一个分组,<code style="overflow-wrap: break-word;margin-right: 2px;margin-left: 2px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;background: rgb(248, 245, 236);color: rgb(255, 53, 2);line-height: 1.5;font-size: 90%;padding: 3px 5px;border-radius: 2px;">只需要</code>进行分组求和、分组计数,不需要计算平均值。</em></p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.6;color: rgb(63, 63, 63);margin-top: 10px;margin-bottom: 10px;">分组求和、分组计数代码如下:</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;"><span style="display: block;background: url("https://mmbiz.qpic.cn/mmbiz_svg/XCopLcwfzeckrSbEysLMibBhb3pZu4TfWjjJkogGqTibdrrXHE2nOWxFfXZJkDqoj5CicvDk0fm6WbtgicTZSEXNukNJ0DFmlHJia/640?wx_fmt=svg") 10px 10px / 40px no-repeat rgb(40, 44, 52);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;padding: 16px;color: #abb2bf;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #282c34;border-radius: 5px;"><span style="line-height: 26px;"><span style="color: #c678dd;line-height: 26px;">bool</span> <span style="color: #61aeee;line-height: 26px;">Item_sum_avg::add</span><span style="line-height: 26px;">()</span><br></span>{<br> <span style="color: #5c6370;font-style: italic;line-height: 26px;">// 分组求和</span><br> <span style="color: #c678dd;line-height: 26px;">if</span> (Item_sum_sum::add())<br> <span style="color: #c678dd;line-height: 26px;">return</span> TRUE;<br> <span style="color: #5c6370;font-style: italic;line-height: 26px;">// 分组计数(字段值不为 NULL 才进行计数)</span><br> <span style="color: #c678dd;line-height: 26px;">if</span> (!aggr->arg_is_null(<span style="color: #56b6c2;line-height: 26px;">true</span>))<br> count++;<br> <span style="color: #c678dd;line-height: 26px;">return</span> FALSE;<br>}<br></code></pre> <blockquote data-tool="mdnice编辑器" style="border-top: none;border-right: none;border-bottom: none;font-size: 0.9em;overflow: auto;color: rgb(91, 91, 91);border-left-color: rgb(158, 158, 158);background: rgba(158, 158, 158, 0.1);padding-top: 1px;padding-bottom: 1px;margin-top: 20px;margin-bottom: 20px;"> <p style="color: rgb(63, 63, 63);line-height: 1.5;font-size: 16px;margin: 10px;">只有字段值不为 NULL,分组计数(count)才会加 1。</p> </blockquote> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.6;color: rgb(63, 63, 63);margin-top: 10px;margin-bottom: 10px;">了解 avg() 之后,count()、sum() 也就明白了。count()、sum() 和 avg() 的执行过程基本一样,不同之处在于:</p> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> count() 对应的类 Item_sum_count 只有 count 属性, <code style="overflow-wrap: break-word;margin-right: 2px;margin-left: 2px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;background: rgb(248, 245, 236);color: rgb(255, 53, 2);line-height: 1.5;font-size: 90%;padding: 3px 5px;border-radius: 2px;">只需要进行分组计数</code>,不需要分组求和、计算平均值。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> sum() 对应的类 Item_sum_sum 只有 sum 属性, <code style="overflow-wrap: break-word;margin-right: 2px;margin-left: 2px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;background: rgb(248, 245, 236);color: rgb(255, 53, 2);line-height: 1.5;font-size: 90%;padding: 3px 5px;border-radius: 2px;">只需要进行分组求和</code>,不需要分组计数、计算平均值。 </section></li> </ul> <h2 data-tool="mdnice编辑器" style="margin: 80px 10px 40px;text-align: center;color: rgb(63, 63, 63);font-size: 140%;"><span style="display: none;"></span>3. 松散索引扫描</h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.6;color: rgb(63, 63, 63);margin-top: 10px;margin-bottom: 10px;">松散索引扫描,从存储引擎读取分组记录时,会跳着读,读取分组前缀之后,直接通过<code style="overflow-wrap: break-word;margin-right: 2px;margin-left: 2px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;background: rgb(248, 245, 236);color: rgb(255, 53, 2);line-height: 1.5;font-size: 90%;padding: 3px 5px;border-radius: 2px;">分组前缀</code>(group by 字段的值)定位到分组中符合 where 条件的第一条或最后一条记录,而不需要读取分组的所有记录,然后就接着读取下一个分组的分组前缀,这样可以减少 select 语句执行过程中需要读取的记录数,从而比紧凑索引扫描更快(有例外情况,后面会介绍)。</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="1.023201856148492" src="/upload/100c26d5c4e072e585e2904d48fe105d.png" data-type="png" data-w="431" style="display: block;margin-right: auto;margin-left: auto;height: auto !important;"> <figcaption style="margin-top: 5px;text-align: center;color: #888;font-size: 14px;"> 松散索引扫描 </figcaption> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.6;color: rgb(63, 63, 63);margin-top: 10px;margin-bottom: 10px;">如果 select 语句执行过程中使用了松散索引扫描实现 group by,explain 输出结果的 Extra 列会显示 <code style="overflow-wrap: break-word;margin-right: 2px;margin-left: 2px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;background: rgb(248, 245, 236);color: rgb(255, 53, 2);line-height: 1.5;font-size: 90%;padding: 3px 5px;border-radius: 2px;">Using index for group-by</code>。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.6;color: rgb(63, 63, 63);margin-top: 10px;margin-bottom: 10px;">松散索引扫描用于 min()、max(),可以减少需要读取的记录数;用于 count(distinct)、sum(distinct)、avg(distinct) ,可以对记录<code style="overflow-wrap: break-word;margin-right: 2px;margin-left: 2px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;background: rgb(248, 245, 236);color: rgb(255, 53, 2);line-height: 1.5;font-size: 90%;padding: 3px 5px;border-radius: 2px;">去重</code>,避免使用临时表去重。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.6;color: rgb(63, 63, 63);margin-top: 10px;margin-bottom: 10px;">我们以 <code style="overflow-wrap: break-word;margin-right: 2px;margin-left: 2px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;background: rgb(248, 245, 236);color: rgb(255, 53, 2);line-height: 1.5;font-size: 90%;padding: 3px 5px;border-radius: 2px;">min()</code> 为例介绍<code style="overflow-wrap: break-word;margin-right: 2px;margin-left: 2px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;background: rgb(248, 245, 236);color: rgb(255, 53, 2);line-height: 1.5;font-size: 90%;padding: 3px 5px;border-radius: 2px;">松散索引扫描</code>的执行过程,示例 SQL 如下:</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;"><span style="display: block;background: url("https://mmbiz.qpic.cn/mmbiz_svg/XCopLcwfzeckrSbEysLMibBhb3pZu4TfWjjJkogGqTibdrrXHE2nOWxFfXZJkDqoj5CicvDk0fm6WbtgicTZSEXNukNJ0DFmlHJia/640?wx_fmt=svg") 10px 10px / 40px no-repeat rgb(40, 44, 52);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;padding: 16px;color: #abb2bf;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #282c34;border-radius: 5px;"><span style="color: #c678dd;line-height: 26px;">select</span><br> e1, <span style="color: #c678dd;line-height: 26px;">min</span>(i1)<br><span style="color: #c678dd;line-height: 26px;">from</span> t_group_by<br><span style="color: #c678dd;line-height: 26px;">group</span> <span style="color: #c678dd;line-height: 26px;">by</span> e1<br></code></pre> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.6;color: rgb(63, 63, 63);margin-top: 10px;margin-bottom: 10px;"><strong style="color: rgb(255, 53, 2);line-height: 1.5;">词法分析 & 语法分析阶段</strong>,min(i1) 被解析为 Item_sum_min 类,以下是该类的实例属性的其中 2 个:</p> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> <code style="overflow-wrap: break-word;margin-right: 2px;margin-left: 2px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;background: rgb(248, 245, 236);color: rgb(255, 53, 2);line-height: 1.5;font-size: 90%;padding: 3px 5px;border-radius: 2px;">value</code>,该属性类型为 Item_cache,Item_cache 子类的实例属性 value 保存 <code style="overflow-wrap: break-word;margin-right: 2px;margin-left: 2px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;background: rgb(248, 245, 236);color: rgb(255, 53, 2);line-height: 1.5;font-size: 90%;padding: 3px 5px;border-radius: 2px;">分组最小值</code>(分组记录中 i1 字段的最小值)。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> <code style="overflow-wrap: break-word;margin-right: 2px;margin-left: 2px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;background: rgb(248, 245, 236);color: rgb(255, 53, 2);line-height: 1.5;font-size: 90%;padding: 3px 5px;border-radius: 2px;">args</code>,min() 函数的参数,args[0] 为 i1 字段对应的 Item_field 类实例。 </section></li> </ul> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="0.5736994219653179" src="/upload/b43c78b61f4543cffe563bf4114ba8c1.png" data-type="png" data-w="692" style="display: block;margin-right: auto;margin-left: auto;height: auto !important;"> <figcaption style="margin-top: 5px;text-align: center;color: #888;font-size: 14px;"> Item_sum_min </figcaption> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.6;color: rgb(63, 63, 63);margin-top: 10px;margin-bottom: 10px;"><strong style="color: rgb(255, 53, 2);line-height: 1.5;">查询准备阶段</strong>,i1 字段对应的 Item_field 类实例会关联到表 t_group_by 的 i1 字段。</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-cropselx1="0" data-cropselx2="578" data-cropsely1="0" data-cropsely2="326" data-ratio="0.562807881773399" src="/upload/cb604d72ac1a06ee7ab13acc282bdfa1.png" data-type="png" data-w="812" style="display: block;margin-right: auto;margin-left: auto;height: 325px;width: 578px;"> <figcaption style="margin-top: 5px;text-align: center;color: #888;font-size: 14px;"> Item_sum_min </figcaption> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.6;color: rgb(63, 63, 63);margin-top: 10px;margin-bottom: 10px;"><strong style="color: rgb(255, 53, 2);line-height: 1.5;">执行阶段</strong>,读取分组最小值的过程分为两步:</p> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> <code style="overflow-wrap: break-word;margin-right: 2px;margin-left: 2px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;background: rgb(248, 245, 236);color: rgb(255, 53, 2);line-height: 1.5;font-size: 90%;padding: 3px 5px;border-radius: 2px;">读取分组前缀</code>(示例 SQL 中 group by 的 e1 字段值),从存储引擎读取分组的第一条记录,得到分组前缀。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> <code style="overflow-wrap: break-word;margin-right: 2px;margin-left: 2px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;background: rgb(248, 245, 236);color: rgb(255, 53, 2);line-height: 1.5;font-size: 90%;padding: 3px 5px;border-radius: 2px;">根据分组前缀读取分组最小值</code>(分组记录中 i1 字段的最小值),用前面得到的分组前缀限定索引扫描范围,从存储引擎读取分组中 i1 字段的最小值,保存到 value 属性中。 </section></li> </ul> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="0.35389133627019087" src="/upload/984351c0bbf41d8489af8958b9ec0374.png" data-type="png" data-w="681" style="display: block;margin-right: auto;margin-left: auto;height: auto !important;"> <figcaption style="margin-top: 5px;text-align: center;color: #888;font-size: 14px;"> 读取分组最小值 </figcaption> </figure> <h2 data-tool="mdnice编辑器" style="margin: 80px 10px 40px;text-align: center;color: rgb(63, 63, 63);font-size: 140%;"><span style="display: none;"></span>4. 两种索引扫描怎么选?</h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.6;color: rgb(63, 63, 63);margin-top: 10px;margin-bottom: 10px;">松散索引扫描虽然具备提升 select 语句执行效率的能力,但只有在适用的场景下才能发挥它的威力,因此,它的使用<code style="overflow-wrap: break-word;margin-right: 2px;margin-left: 2px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;background: rgb(248, 245, 236);color: rgb(255, 53, 2);line-height: 1.5;font-size: 90%;padding: 3px 5px;border-radius: 2px;">需要满足以下条件</code>:</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.6;color: rgb(63, 63, 63);margin-top: 10px;margin-bottom: 10px;"><strong style="color: rgb(255, 53, 2);line-height: 1.5;">条件 1</strong>,select 语句只能是单表查询,不能是连接查询。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.6;color: rgb(63, 63, 63);margin-top: 10px;margin-bottom: 10px;"><strong style="color: rgb(255, 53, 2);line-height: 1.5;">条件 2</strong>,group by 字段必须满足索引的最左匹配原则。例如:表中有一个索引包含 c1, c2, c3 三个字段,group by c1, c2 满足最左匹配原则。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.6;color: rgb(63, 63, 63);margin-top: 10px;margin-bottom: 10px;"><strong style="color: rgb(255, 53, 2);line-height: 1.5;">条件 3</strong>,如果 select 字段列表中包含聚合函数,聚合函数必须满足这些条件:</p> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 所有聚合函数的参数都必须是同一个字段。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 聚合函数字段必须是索引中的字段,并且 <code style="overflow-wrap: break-word;margin-right: 2px;margin-left: 2px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;background: rgb(248, 245, 236);color: rgb(255, 53, 2);line-height: 1.5;font-size: 90%;padding: 3px 5px;border-radius: 2px;">group by 字段 + 聚合函数字段</code>也必须满足索引最左匹配原则。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 聚合函数要么是 min()、max() 中的 1 ~ 2 个;要么是 count(distinct)、sum(distinct)、avg(distinct) 中的 1 ~ 3个。 <br> <blockquote style="border-top: none;border-right: none;border-bottom: none;font-size: 0.9em;overflow: auto;color: rgb(91, 91, 91);border-left-color: rgb(158, 158, 158);background: rgba(158, 158, 158, 0.1);padding-top: 1px;padding-bottom: 1px;margin-top: 20px;margin-bottom: 20px;"> <p style="color: rgb(63, 63, 63);line-height: 1.5;font-size: 16px;margin: 10px;">松散索引扫描中,两类聚合函数不能同时存在,因为它们对于<code style="overflow-wrap: break-word;margin-right: 2px;margin-left: 2px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;background: rgb(248, 245, 236);color: rgb(255, 53, 2);line-height: 1.5;font-size: 90%;padding: 3px 5px;border-radius: 2px;">分组前缀</code>处理逻辑不一样。在读取数据时,min()、max() 用 <code style="overflow-wrap: break-word;margin-right: 2px;margin-left: 2px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;background: rgb(248, 245, 236);color: rgb(255, 53, 2);line-height: 1.5;font-size: 90%;padding: 3px 5px;border-radius: 2px;">group by 字段值</code>作为分组前缀;count(distinct)、sum(distinct)、avg(distinct) 用 <code style="overflow-wrap: break-word;margin-right: 2px;margin-left: 2px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;background: rgb(248, 245, 236);color: rgb(255, 53, 2);line-height: 1.5;font-size: 90%;padding: 3px 5px;border-radius: 2px;">group by 字段值 + 聚合函数中的字段值</code>作为分组前缀。</p> </blockquote> </section></li> </ul> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.6;color: rgb(63, 63, 63);margin-top: 10px;margin-bottom: 10px;"><strong style="color: rgb(255, 53, 2);line-height: 1.5;">条件 4</strong>,索引中所有字段必须是全字段索引,不能是前缀索引。<br>例如:有个字段 <code style="overflow-wrap: break-word;margin-right: 2px;margin-left: 2px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;background: rgb(248, 245, 236);color: rgb(255, 53, 2);line-height: 1.5;font-size: 90%;padding: 3px 5px;border-radius: 2px;">c1 varchar(20)</code>,索引中该字段为 <code style="overflow-wrap: break-word;margin-right: 2px;margin-left: 2px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;background: rgb(248, 245, 236);color: rgb(255, 53, 2);line-height: 1.5;font-size: 90%;padding: 3px 5px;border-radius: 2px;">index(c1(10))</code>,这样的索引就不能用于松散索引扫描。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.6;color: rgb(63, 63, 63);margin-top: 10px;margin-bottom: 10px;">满足以上条件,还只是站在了使用松散索引扫描的门外,想要登堂入室,还必须进行成本评估。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.6;color: rgb(63, 63, 63);margin-top: 10px;margin-bottom: 10px;">如果松散索引扫描的成本比紧凑索引扫描的成本低,自然就要用<code style="overflow-wrap: break-word;margin-right: 2px;margin-left: 2px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;background: rgb(248, 245, 236);color: rgb(255, 53, 2);line-height: 1.5;font-size: 90%;padding: 3px 5px;border-radius: 2px;">松散索引扫描</code>来提升 select 语句的执行效率了。</p> <h3 data-tool="mdnice编辑器" style="margin-top: 40px;margin-bottom: 20px;font-weight: bold;line-height: 1.5;color: rgb(63, 63, 63);font-size: 120%;"><span style="display: none;"></span>4.1 松散索引扫描成本更高怎么办?<span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.6;color: rgb(63, 63, 63);margin-top: 10px;margin-bottom: 10px;">松散索引扫描成本比紧凑索引扫描成本更高时,如果 select 语句中的聚合函数是 min()、max() 中的 1 ~ 2 个,就会使用<code style="overflow-wrap: break-word;margin-right: 2px;margin-left: 2px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;background: rgb(248, 245, 236);color: rgb(255, 53, 2);line-height: 1.5;font-size: 90%;padding: 3px 5px;border-radius: 2px;">紧凑索引扫描</code>。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.6;color: rgb(63, 63, 63);margin-top: 10px;margin-bottom: 10px;">松散索引扫描<code style="overflow-wrap: break-word;margin-right: 2px;margin-left: 2px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;background: rgb(248, 245, 236);color: rgb(255, 53, 2);line-height: 1.5;font-size: 90%;padding: 3px 5px;border-radius: 2px;">自带去重功能</code>,不需要借助临时表,和包含 distinct 关键字的聚合函数天生更匹配。紧凑索引扫描则需要借助<code style="overflow-wrap: break-word;margin-right: 2px;margin-left: 2px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;background: rgb(248, 245, 236);color: rgb(255, 53, 2);line-height: 1.5;font-size: 90%;padding: 3px 5px;border-radius: 2px;">临时表</code>对记录进行去重。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.6;color: rgb(63, 63, 63);margin-top: 10px;margin-bottom: 10px;">如果聚合函数是 count(distinct)、sum(distinct)、avg(distinct) 中的 1 ~ 3 个,虽然紧凑索引扫描读取记录成本更低,但必须使用<code style="overflow-wrap: break-word;margin-right: 2px;margin-left: 2px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;background: rgb(248, 245, 236);color: rgb(255, 53, 2);line-height: 1.5;font-size: 90%;padding: 3px 5px;border-radius: 2px;">临时表</code>对记录去重,这样一来,<code style="overflow-wrap: break-word;margin-right: 2px;margin-left: 2px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;background: rgb(248, 245, 236);color: rgb(255, 53, 2);line-height: 1.5;font-size: 90%;padding: 3px 5px;border-radius: 2px;">紧凑索引扫描读取数据 + 临时表去重</code>的成本就比松散索引扫描成本更高了。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.6;color: rgb(63, 63, 63);margin-top: 10px;margin-bottom: 10px;">这就很尴尬了,两种方式各有优缺点,两难之下,MySQL 要怎么办?</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.6;color: rgb(63, 63, 63);margin-top: 10px;margin-bottom: 10px;">两难之下,最好的选择就是找到第三个选项。为此,MySQL 祭出了一个大招,既要和紧凑索引扫描一样顺序读取数据,又要用松散索引扫描自带的去重能力。如果用了这个大招,在 explain 输出结果的 Extra 列可以看到 <code style="overflow-wrap: break-word;margin-right: 2px;margin-left: 2px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;background: rgb(248, 245, 236);color: rgb(255, 53, 2);line-height: 1.5;font-size: 90%;padding: 3px 5px;border-radius: 2px;">Using index for group-by (scanning)</code>。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.6;color: rgb(63, 63, 63);margin-top: 10px;margin-bottom: 10px;">MySQL 把紧凑索引扫描中使用的<code style="overflow-wrap: break-word;margin-right: 2px;margin-left: 2px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;background: rgb(248, 245, 236);color: rgb(255, 53, 2);line-height: 1.5;font-size: 90%;padding: 3px 5px;border-radius: 2px;">顺序读取记录</code>嵌入到松散索引扫描的逻辑里,当评估紧凑索引扫描成本比松散索引扫描低时,对于包含 distinct 关键字的聚合函数,就会用顺序读取记录代替跳着读取记录,并且在顺序读取记录的过程中完成记录去重。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.6;color: rgb(63, 63, 63);margin-top: 10px;margin-bottom: 10px;">对于松散索引扫描的这个变种,到写完本文为止,我还没有在哪里看到官方有正式的命名,为了方便记忆,估且把它命名为<code style="overflow-wrap: break-word;margin-right: 2px;margin-left: 2px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;background: rgb(248, 245, 236);color: rgb(255, 53, 2);line-height: 1.5;font-size: 90%;padding: 3px 5px;border-radius: 2px;">顺序松散索引扫描</code>吧。</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="0.6988906497622821" src="/upload/e6befa1e1cc5d6de44f9ee0c95b28d2d.png" data-type="png" data-w="631" style="display: block;margin-right: auto;margin-left: auto;height: auto !important;"> <figcaption style="margin-top: 5px;text-align: center;color: #888;font-size: 14px;"> 顺序松散索引扫描 </figcaption> </figure> <h3 data-tool="mdnice编辑器" style="margin-top: 40px;margin-bottom: 20px;font-weight: bold;line-height: 1.5;color: rgb(63, 63, 63);font-size: 120%;"><span style="display: none;"></span>4.2 为什么松散索引扫描会比紧凑索引扫描成本高?<span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.6;color: rgb(63, 63, 63);margin-top: 10px;margin-bottom: 10px;"><code style="overflow-wrap: break-word;margin-right: 2px;margin-left: 2px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;background: rgb(248, 245, 236);color: rgb(255, 53, 2);line-height: 1.5;font-size: 90%;padding: 3px 5px;border-radius: 2px;">紧凑索引扫描</code>,存储引擎按顺序一条一条读取记录,返回给 server 层,server 层判断记录是否符合 where 条件,然后对符合条件的记录进行聚合函数逻辑处理。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.6;color: rgb(63, 63, 63);margin-top: 10px;margin-bottom: 10px;"><code style="overflow-wrap: break-word;margin-right: 2px;margin-left: 2px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;background: rgb(248, 245, 236);color: rgb(255, 53, 2);line-height: 1.5;font-size: 90%;padding: 3px 5px;border-radius: 2px;">松散索引扫描</code>,对于每个分组,都会从存储引擎读取两次数据,第一次是读取分组的第一条记录,得到<code style="overflow-wrap: break-word;margin-right: 2px;margin-left: 2px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;background: rgb(248, 245, 236);color: rgb(255, 53, 2);line-height: 1.5;font-size: 90%;padding: 3px 5px;border-radius: 2px;">分组前缀</code>;第二次是根据<code style="overflow-wrap: break-word;margin-right: 2px;margin-left: 2px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;background: rgb(248, 245, 236);color: rgb(255, 53, 2);line-height: 1.5;font-size: 90%;padding: 3px 5px;border-radius: 2px;">分组前缀</code>读取分组中索引扫描范围的第一条或最后一条记录。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.6;color: rgb(63, 63, 63);margin-top: 10px;margin-bottom: 10px;">如果分组中的记录数量多,第二次读取记录时,能<code style="overflow-wrap: break-word;margin-right: 2px;margin-left: 2px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;background: rgb(248, 245, 236);color: rgb(255, 53, 2);line-height: 1.5;font-size: 90%;padding: 3px 5px;border-radius: 2px;">跳过</code>的记录就多,节省的成本就多,松散索引扫描就会比紧凑索引扫描更快。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.6;color: rgb(63, 63, 63);margin-top: 10px;margin-bottom: 10px;">如果分组中的记录数量少,第二次读取记录时,能跳过的记录就少,每个分组读取两次记录增加的成本<code style="overflow-wrap: break-word;margin-right: 2px;margin-left: 2px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;background: rgb(248, 245, 236);color: rgb(255, 53, 2);line-height: 1.5;font-size: 90%;padding: 3px 5px;border-radius: 2px;">超过了</code>跳过记录节省的成本,松散索引扫描就会比紧凑索引扫描更慢。</p> <h2 data-tool="mdnice编辑器" style="margin: 80px 10px 40px;text-align: center;color: rgb(63, 63, 63);font-size: 140%;"><span style="display: none;"></span>5. 总结</h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.6;color: rgb(63, 63, 63);margin-top: 10px;margin-bottom: 10px;"><code style="overflow-wrap: break-word;margin-right: 2px;margin-left: 2px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;background: rgb(248, 245, 236);color: rgb(255, 53, 2);line-height: 1.5;font-size: 90%;padding: 3px 5px;border-radius: 2px;">引言</code>小节,介绍了 MySQL 实现 group by 的两种索引扫描方式:紧凑索引扫描、松散索引扫描。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.6;color: rgb(63, 63, 63);margin-top: 10px;margin-bottom: 10px;"><code style="overflow-wrap: break-word;margin-right: 2px;margin-left: 2px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;background: rgb(248, 245, 236);color: rgb(255, 53, 2);line-height: 1.5;font-size: 90%;padding: 3px 5px;border-radius: 2px;">紧凑索引扫描</code>小节,以 avg() 为例介绍了紧凑索引扫描的执行过程,avg() 在词法分析 & 语法分析阶段会被解析为 Item_sum_avg 类。该类的实例属性 sum、count、args 分别用于保存分组求和结果、分组计数、avg() 函数的参数。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.6;color: rgb(63, 63, 63);margin-top: 10px;margin-bottom: 10px;">在执行阶段,通过把 avg() 字段值累加到 sum 属性进行分组求和;对 count 属性进行自增实现分组计数;通过 sum / count 计算得到分组平均值。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.6;color: rgb(63, 63, 63);margin-top: 10px;margin-bottom: 10px;">Item_sum_count、Item_sum_sum、Item_sum_avg、Item_sum_min、Item_sum_max 类的实例属性 args 都继承自父类 Item_sum,用于保存聚合函数参数,count() 支持多个参数,所以,参数的属性名为 args 而不是 arg。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.6;color: rgb(63, 63, 63);margin-top: 10px;margin-bottom: 10px;"><code style="overflow-wrap: break-word;margin-right: 2px;margin-left: 2px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;background: rgb(248, 245, 236);color: rgb(255, 53, 2);line-height: 1.5;font-size: 90%;padding: 3px 5px;border-radius: 2px;">松散索引扫描</code>小节,以 min() 为例介绍了松散索引扫描的执行过程,执行阶段,分为两步读取分组最小值:读取分组前缀,根据分组前缀读取分组最小值。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.6;color: rgb(63, 63, 63);margin-top: 10px;margin-bottom: 10px;"><code style="overflow-wrap: break-word;margin-right: 2px;margin-left: 2px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;background: rgb(248, 245, 236);color: rgb(255, 53, 2);line-height: 1.5;font-size: 90%;padding: 3px 5px;border-radius: 2px;">两种索引扫描怎么选?</code> 小节,介绍了使用松散索引扫描必须满足的一系列条件。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.6;color: rgb(63, 63, 63);margin-top: 10px;margin-bottom: 10px;">当松散索引扫描比紧凑索引扫描成本高时,min()、max() 会选择用紧凑索引扫描,MySQL 为 count(distinct)、sum(distinct)、avg(distinct) 引入松散索引扫描的变种,<code style="overflow-wrap: break-word;margin-right: 2px;margin-left: 2px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;background: rgb(248, 245, 236);color: rgb(255, 53, 2);line-height: 1.5;font-size: 90%;padding: 3px 5px;border-radius: 2px;">顺序读取索引记录(和紧凑索引扫描一样)+ 松散索引扫描自带的记录去重功能</code>,避免了使用临时表对记录去重。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.6;color: rgb(63, 63, 63);margin-top: 10px;margin-bottom: 10px;">还介绍了松散索引扫描比紧凑索引扫描的成本高,是因为分组中记录数量少时,两次读取存储引擎数据增加的成本超过了跳着读取索引记录节省的成本。</p> </section> <p><span style="color: rgb(255, 76, 0);font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;font-size: 12px;text-align: left;">如果本文对你有所帮助,还请帮忙</span><strong style="color: rgb(255, 76, 0);font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;font-size: 12px;text-align: left;">点赞、在看、转发朋友圈</strong><span style="color: rgb(255, 76, 0);font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;font-size: 12px;letter-spacing: 0px;text-align: left;">,让更多人看到,我们一起进步,谢谢 ^_^</span><br></p> <p><span style="color:#ff4c00;font-family:Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, PingFang SC, Cambria, Cochin, Georgia, Times, Times New Roman, serif;"><span style="font-size: 12px;"><br></span></span></p> <hr style="border-style: solid;border-width: 1px 0 0;border-color: rgba(0,0,0,0.1);-webkit-transform-origin: 0 0;-webkit-transform: scale(1, 0.5);transform-origin: 0 0;transform: scale(1, 0.5);"> <p style="text-align: center;"><span style="color: rgb(255, 104, 39);font-size: 16px;"><strong><br></strong></span></p> <p style="text-align: center;"><span style="color: rgb(255, 104, 39);font-size: 16px;"><strong>欢迎关注公众号:</strong></span><span style="font-size: 16px;color: rgb(0, 128, 255);"><strong>码哥字节</strong></span></p> <section class="mp_profile_iframe_wrp"> <mpprofile class="js_uneditable custom_select_card mp_profile_iframe" data-pluginname="mpprofile" data-id="MzkzMDI1NjcyOQ==" data-headimg="http://mmbiz.qpic.cn/mmbiz_png/EoJib2tNvVtf7icAmS0BQH6oDVG37Q8NzcfdguS5qAqOhfxvZyIKqmuX5BbnDjynrBbZzktp1EiaeFLzapp1nHysw/0?wx_fmt=png" data-nickname="码哥字节" data-alias="MageByte" data-signature="拥抱硬核技术和对象,面向人民币编程。" data-from="0"></mpprofile> </section>
作者:微信小助手
<p style="white-space: normal;" data-mpa-powered-by="yiban.io"><strong style="font-size: 16px;text-align: left;"><span style="font-size: 15px;text-align: justify;color: rgb(171, 25, 66);">1 现象</span></strong></p> <p data-tool="mdnice编辑器" style="white-space: normal;text-align: left;"><br></p> <p data-tool="mdnice编辑器" style="white-space: normal;text-align: left;"><span style="font-size: 15px;font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;text-align: justify;">某后端系统,处于整个调用链路偏后的位置,对接口性能有着比较严格的要求。因此对外承诺的三个 9 响应时间为 200 多毫秒。</span></p> <p data-tool="mdnice编辑器" style="white-space: normal;text-align: left;"><span style="font-size: 15px;font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;text-align: justify;"><br></span></p> <p data-tool="mdnice编辑器" style="white-space: normal;text-align: left;"><span style="font-size: 15px;font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;text-align: justify;">然而,从某天开始一到上午流量高峰,服务耗时就报警。随机从集群内的某些机器上报了出来,过了流量高峰就好很多……</span></p> <p data-tool="mdnice编辑器" style="white-space: normal;text-align: left;"><span style="font-size: 15px;font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;text-align: justify;"><br></span></p> <h2 data-tool="mdnice编辑器" style="white-space: normal;text-align: left;"><strong><span style="font-size: 15px;font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;text-align: justify;color: rgb(171, 25, 66);">2 问题排查</span></strong></h2> <p data-tool="mdnice编辑器" style="white-space: normal;text-align: left;"><br></p> <p data-tool="mdnice编辑器" style="white-space: normal;text-align: left;"><span style="font-size: 15px;font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;text-align: justify;">一般排查接口耗时,基本都习惯从外部流量、相关内部接口、逻辑块耗时、底层存储耗时等接口层面来观察。</span></p> <p data-tool="mdnice编辑器" style="white-space: normal;text-align: left;"><span style="font-size: 15px;font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;text-align: justify;"><br></span></p> <p data-tool="mdnice编辑器" style="white-space: normal;text-align: left;"><span style="font-size: 15px;font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;text-align: justify;">如果能在接口层面就找到了瓶颈是最理想的情况,否则就要从 JVM 层面来排查。比如线程运行异常、GC 异常等,势必要耗费更多精力了。</span></p> <p data-tool="mdnice编辑器" style="white-space: normal;text-align: left;"><span style="font-size: 15px;font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;text-align: justify;"><br></span></p> <h3 data-tool="mdnice编辑器" style="white-space: normal;text-align: left;"><strong><span style="font-size: 15px;font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;text-align: justify;color: rgb(171, 25, 66);">2.1 服务器性能、流量分配等外在因素</span></strong></h3> <p data-tool="mdnice编辑器" style="white-space: normal;text-align: left;"><br></p> <p data-tool="mdnice编辑器" style="white-space: normal;text-align: left;"><span style="font-size: 15px;font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;text-align: justify;">虽然这类问题发生的概率比较小,但是看到几十台机器中只有几台在报。感觉还是有必要排查一下。</span></p> <p data-tool="mdnice编辑器" style="white-space: normal;text-align: left;"><span style="font-size: 15px;font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;text-align: justify;"><br></span></p> <ul data-tool="mdnice编辑器" class="list-paddingleft-1" style="white-space: normal;text-align: left;"> <li style="white-space: normal;text-align: le
作者:微信小助手
<section data-tool="mdnice编辑器" data-website="https://www.mdnice.com" style="line-height: 1.6;word-break: break-word;overflow-wrap: break-word;text-align: left;font-size: 16px;color: rgb(74, 74, 74);letter-spacing: 0.5444px;padding-left: 5px;padding-right: 5px;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;" data-mpa-powered-by="yiban.io"> <blockquote data-tool="mdnice编辑器" style="border-top: none;border-right: none;border-bottom: none;font-size: 0.9em;overflow: auto;color: rgb(106, 115, 125);border-left-width: 2px;padding: 8px 10px 8px 15px;background: rgb(255, 249, 249);border-left-color: rgb(239, 112, 96);margin-top: 0px;margin-bottom: 20px;letter-spacing: 0.5444px;"> <p style="text-align: justify;font-family: "Avenir, -apple-system-font, 微软雅黑, sans-serif";font-size: 16px;margin-bottom: 10px;color: rgb(74, 74, 74);line-height: 30px;letter-spacing: 0.5444px;">我看过那么多所谓的教程,大部分都是教“如何使用工具”的,没有多少是教“如何制作工具”的,能教“如何仿制工具”的都已经是凤毛麟角,中国 软件行业,缺的是真正可以“制作工具”的程序员,而绝对不缺那些“使用工具”的程序员!...... ”这个业界最不需要的就是“会使用XX工具的工程师”,而是“有创造力的软件工程师”!业界所有的饭碗,本质就是“有创造力的软件工程师”提供出来的啊!</p> </blockquote> <p data-tool="mdnice编辑器" style="text-align: justify;line-height: 30px;font-family: "Avenir, -apple-system-font, 微软雅黑, sans-serif";letter-spacing: 0.5444px;margin-bottom: 20px;">写这篇文章,想和大家从头到脚说说任务调度,希望大家读完之后,能够理解实现一个任务调度系统的核心逻辑。</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;padding: 0px 0.5em;"> <img class="rich_pages wxw-img" data-ratio="1.2636363636363637" src="/upload/c810c4ee85120e5544ce90fd96c41fc6.png" data-type="png" data-w="880" style="display: block;margin-right: auto;margin-left: auto;margin-bottom: 15px;box-shadow: rgb(210, 210, 210) 0em 0em 0.5em 0px;font-size: 17px;"> </figure> <h1 data-tool="mdnice编辑器" style="font-weight: bold;color: black;font-size: 24px;margin-top: 35px;margin-bottom: 20px;"><span style="display: none;"></span><span style="font-size: 18px;color: rgb(234, 84, 41);letter-spacing: 0.5444px;padding-bottom: 10px;border-bottom: 2px solid rgb(234, 84, 41);">1 Quartz</span></h1> <p data-tool="mdnice编辑器" style="text-align: justify;line-height: 30px;font-family: "Avenir, -apple-system-font, 微软雅黑, sans-serif";letter-spacing: 0.5444px;margin-bottom: 20px;">Quartz是一款Java开源任务调度框架,也是很多Java工程师接触任务调度的起点。</p> <p data-tool="mdnice编辑器" style="text-align: justify;line-height: 30px;font-family: "Avenir, -apple-system-font, 微软雅黑, sans-serif";letter-spacing: 0.5444px;margin-bottom: 20px;padding: 0px 0.5em;">下图显示了任务调度的整体流程:<img class="rich_pages wxw-img" data-ratio="0.9397590361445783" src="/upload/dbd36010de3388f3d9f0afe3187b02b7.png" data-type="png" data-w="1162" style="display: block;margin-right: auto;margin-left: auto;margin-bottom: 15px;box-shadow: rgb(210, 210, 210) 0em 0em 0.5em 0px;font-size: 17px;"></p> <p data-tool="mdnice编辑器" style="text-align: justify;line-height: 30px;font-family: "Avenir, -apple-system-font, 微软雅黑, sans-serif";letter-spacing: 0.5444px;margin-bottom: 20px;">Quartz的核心是三个组件。</p> <ul data-tool="mdnice编辑器" style="margin-top: 8px;color: black;list-style-type: square;padding-left: 1.2em;margin-bottom: 20px;" class="list-paddingleft-1"> <li> <section style="margin-bottom: 10px;line-height: 25px;font-family: "Avenir, -apple-system-font, 微软雅黑, sans-serif";color: rgb(74, 74, 74);letter-spacing: 0.5444px;"> 任务:Job 用于表示被调度的任务; </section></li> <li> <section style="margin-bottom: 10px;line-height: 25px;font-family: "Avenir, -apple-system-font, 微软雅黑, sans-serif";color: rgb(74, 74, 74);letter-spacing: 0.5444px;"> 触发器:Trigger 定义调度时间的元素,即按照什么时间规则去执行任务。一个Job可以被多个Trigger关联,但是一个Trigger 只能关联一个Job; </section></li> <li> <section style="margin-bottom: 10px;line-height: 25px;font-family: "Avenir, -apple-system-font, 微软雅黑, sans-serif";color: rgb(74, 74, 74);letter-spacing: 0.5444px;"> 调度器 :工厂类创建Scheduler,根据触发器定义的时间规则调度任务。 </section></li> </ul> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direc
作者:微信小助手
<section style="font-size: 16px;"> <section style="padding-top: 8px;padding-bottom: 8px;outline: 0px;line-height: 26px;visibility: visible;margin-left: 0px;margin-right: 0px;"> 之前也写过一篇关于 <code style="margin-right: 2px;margin-left: 2px;padding: 2px 4px;outline: 0px;font-size: 14px;border-radius: 4px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.047);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;visibility: visible;">Spring Validation</code>使用的文章,不过自我感觉还是浮于表面,本次打算彻底搞懂 <code style="margin-right: 2px;margin-left: 2px;padding: 2px 4px;outline: 0px;font-size: 14px;border-radius: 4px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.047);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;visibility: visible;">Spring Validation</code>。 </section> <section style="padding-top: 8px;padding-bottom: 8px;outline: 0px;line-height: 26px;visibility: visible;margin-left: 0px;margin-right: 0px;"> 本文会详细介绍 <code style="margin-right: 2px;margin-left: 2px;padding: 2px 4px;outline: 0px;font-size: 14px;border-radius: 4px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.047);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;visibility: visible;">Spring Validation</code>各种场景下的最佳实践及其实现原理,死磕到底! </section> <h2 data-tool="mdnice编辑器" style="margin: 30px 0px 15px;outline: 0px;font-weight: bold;font-size: 22px;visibility: visible;">简单使用</h2> <section style="padding-top: 8px;padding-bottom: 8px;outline: 0px;line-height: 26px;visibility: visible;margin-left: 0px;margin-right: 0px;"> <code style="margin-right: 2px;margin-left: 2px;padding: 2px 4px;outline: 0px;font-size: 14px;border-radius: 4px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.047);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;visibility: visible;">Java API</code>规范 ( <code style="margin-right: 2px;margin-left: 2px;padding: 2px 4px;outline: 0px;font-size: 14px;border-radius: 4px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.047);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;visibility: visible;">JSR303</code>) 定义了 <code style="margin-right: 2px;margin-left: 2px;padding: 2px 4px;outline: 0px;font-size: 14px;border-radius: 4px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.047);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;visibility: visible;">Bean</code>校验的标准 <code style="margin-right: 2px;margin-left: 2px;padding: 2px 4px;outline: 0px;font-size: 14px;border-radius: 4px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.047);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;visibility: visible;">validation-api</code>,但没有提供实现。 <code style="margin-right: 2px;margin-left: 2px;padding: 2px 4px;outline: 0px;font-size: 14px;border-radius: 4px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.047);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;visibility: visible;">hibernate validation</code>是对这个规范的实现,并增加了校验注解如 <code style="margin-right: 2px;margin-left: 2px;padding: 2px 4px;outline: 0px;font-size: 14px;border-radius: 4px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.047);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;visibility: visible;">@Email</code>、 <code style="margin-right: 2px;margin-left: 2px;padding: 2px 4px;outline: 0px;font-size: 14px;border-radius: 4px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.047);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;visibility: visible;">@Length</code>等。 <code style="margin-right: 2px;margin-left: 2px;padding: 2px 4px;outline: 0px;font-size: 14px;border-radius: 4px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.047);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;visibility: visible;">Spring Validation</code>是对 <code style="margin-right: 2px;margin-left: 2px;padding: 2px 4px;outline: 0px;font-size: 14px;border-radius: 4px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.047);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;visibility: visible;">hibernate validation</code>的二次封装,用于支持 <code style="margin-right: 2px;margin-left: 2px;padding: 2px 4px;outline: 0px;font-size: 14px;border-radius: 4px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.047);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;visibility: visible;">spring mvc</code>参数自动校验。接下来,我们以 <code style="margin-right: 2px;margin-left: 2px;padding: 2px 4px;outline: 0px;font-size: 14px;border-radius: 4px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.047);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;visibility: visible;">spring-boot</code>项目为例,介绍 <code style="margin-right: 2px;margin-left: 2px;padding: 2px 4px;outline: 0px;font-size: 14px;border-radius: 4px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.047);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;visibility: visible;">Spring Validation</code>的使用。 </section> <h3 data-tool="mdnice编辑器" style="margin: 30px 0px 15px;outline: 0px;font-weight: bold;font-size: 20px;visibility: visible;">引入依赖</h3> <section style="padding-top: 8px;padding-bottom: 8px;outline: 0px;line-height: 26px;visibility: visible;margin-left: 0px;margin-right: 0px;"> 如果 <code style="margin-right: 2px;margin-left: 2px;padding: 2px 4px;outline: 0px;font-size: 14px;border-radius: 4px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.047);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;visibility: visible;">spring-boot</code>版本小于 <code style="margin-right: 2px;margin-left: 2px;padding: 2px 4px;outline: 0px;font-size: 14px;border-radius: 4px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.047);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;visibility: visible;">2.3.x</code>, <code style="margin-right: 2px;margin-left: 2px;padding: 2px 4px;outline: 0px;font-size: 14px;border-radius: 4px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.047);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;visibility: visible;">spring-boot-starter-web</code>会自动传入 <code style="margin-right: 2px;margin-left: 2px;padding: 2px 4px;outline: 0px;font-size: 14px;border-radius: 4px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.047);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;visibility: visible;">hibernate-validator</code>依赖。如果 <code style="margin-right: 2px;margin-left: 2px;padding: 2px 4px;outline: 0px;font-size: 14px;border-radius: 4px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.047);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;visibility: visible;">spring-boot</code>版本大于 <code style="margin-right: 2px;margin-left: 2px;padding: 2px 4px;outline: 0px;font-size: 14px;border-radius: 4px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.047);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;visibility: visible;">2.3.x</code>,则需要手动引入依赖: </section> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;outline: 0px;visibility: visible;"> <section style="padding: 16px;outline: 0px;overflow-x: auto;background: rgb(39, 40, 34);color: rgb(221, 221, 221);display: -webkit-box;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;border-radius: 0px;font-size: 12px;visibility: visible;margin-left: 0px;margin-right: 0px;"> <span style="outline: 0px;color: rgb(249, 38, 114);line-height: 26px;visibility: visible;"><<span style="outline: 0px;line-height: 26px;visibility: visible;">dependency</span>></span> <br style="outline: 0px;visibility: visible;"> <span style="outline: 0px;color: rgb(249, 38, 114);line-height: 26px;visibility: visible;"><<span style="outline: 0px;line-height: 26px;visibility: visible;">groupId</span>></span>org.hibernate <span style="outline: 0px;color: rgb(249, 38, 114);line-height: 26px;visibility: visible;"></<span style="outline: 0px;line-height: 26px;visibility: visible;">groupId</span>></span> <br style="outline: 0px;visibility: visible;">&nbs
作者:じ☆ve不哭
**copy.bat** ``` @echo off setlocal enabledelayedexpansion for /f "" %%i in (.\list.txt) do ( @set "name=%%i" rem @set "name=%name::=%" @set "name=!name::=!" echo f| xcopy %%i "D:\复制到这里\!name!" /A /Y ) ``` **list.txt** ``` F:\xxxxx\xxx\xxxx\xxxxxx.Jpeg E:\xxxxx\xxx\xxxx\xxxxxx.Jpeg D:\xxxxx\xxx\xxxx\xxxxxx.Jpeg ```
作者:微信小助手
<section powered-by="xiumi.us" style="outline: 0px;max-width: 100%;box-sizing: border-box;color: rgb(34, 34, 34);font-family: -apple-system, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: 15px;letter-spacing: 0.75px;white-space: normal;background-color: rgb(255, 255, 255);visibility: visible;overflow-wrap: break-word !important;"> <section powered-by="xiumi.us" style="outline: 0px;max-width: 100%;box-sizing: border-box;visibility: visible;overflow-wrap: break-word !important;"> <blockquote data-type="2" data-url="" data-author-name="" data-content-utf8-length="84" data-source-title="" style="color: rgba(0, 0, 0, 0.5);outline: 0px;max-width: 100%;visibility: visible;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <section style="outline: 0px;max-width: 100%;visibility: visible;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <section style="outline: 0px;max-width: 100%;visibility: visible;box-sizing: border-box !important;overflow-wrap: break-word !important;"> 作者:互联网服务器团队- Tang Wenjian </section> </section> </blockquote> </section> </section> <section style="font-size: 15px;line-height: 1.6;box-sizing: border-box;"> <section style="margin-top: 10px;margin-bottom: 10px;text-align: center;box-sizing: border-box;" powered-by="xiumi.us"> <section style="padding: 3px;display: inline-block;border-bottom: 1px solid rgb(65, 94, 255);font-size: 17px;color: rgb(65, 94, 255);box-sizing: border-box;"> <p style="box-sizing: border-box;"><br></p> <p style="box-sizing: border-box;">一、 背景</p> </section> </section> <section style="box-sizing: border-box;" powered-by="xiumi.us"> <p style="box-sizing: border-box;"><br style="box-sizing: border-box;"></p> </section> <section style="box-sizing: border-box;" powered-by="xiumi.us"> <p style="box-sizing: border-box;">使用过 Redis 的同学应该都知道,它基于键值对(key-value)的内存数据库,所有数据存放在内存中,内存在 Redis 中扮演一个核心角色,所有的操作都是围绕它进行。</p> <p style="box-sizing: border-box;"><br style="box-sizing: border-box;"></p> <p style="box-sizing: border-box;">我们在实际维护过程中经常会被问到如下问题,比如数据怎么存储在 Redis 里面能节约成本、提升性能?Redis内存告警是什么原因导致?</p> <p style="box-sizing: border-box;"><br style="box-sizing: border-box;"></p> <p style="box-sizing: border-box;">本文主要是通过分析 Redis内存结构、介绍内存优化手段,同时结合生产案例,帮助大家在优化内存使用,快速定位 Redis 相关内存异常问题。</p> </section> <section style="box-sizing: border-box;" powered-by="xiumi.us"> <p style="box-sizing: border-box;"><br style="box-sizing: border-box;"></p> </section> <section style="margin-top: 10px;margin-bottom: 10px;text-align: center;box-sizing: border-box;" powered-by="xiumi.us"> <section style="padding: 3px;display: inline-block;border-bottom: 1px solid rgb(65, 94, 255);font-size: 17px;color: rgb(65, 94, 255);box-sizing: border-box;"> <p style="box-sizing: border-box;">二、 Redis 内存管理</p> </section> </section> <section style="box-sizing: border-box;" powered-by="xiumi.us"> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> </section> <section style="box-sizing: border-box;" powered-by="xiumi.us"> <p style="white-space: normal;box-sizing: border-box;">本章详细介绍 Redis 是怎么管理各内存结构的,然后主要介绍几个占用内存可能比较多的内存结构。首先我们看下Redis 的内存模型。</p> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> <p style="white-space: normal;box-sizing: border-box;">内存模型如图:</p> <p style="white-space: normal;box-sizing: border-box;"><br></p> <p style="text-align: center;margin-bottom: 0em;"><img class="rich_pages wxw-img js_insertlocalimg" data-ratio="0.4941569282136895" data-s="300,640" src="/upload/558024c7dc437e544d61957e89a90c1f.png" data-type="png" data-w="1198" style=""></p> </section> <section style="box-sizing: border-box;" powered-by="xiumi.us"> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> </section> <section style="box-sizing: border-box;" powered-by="xiumi.us"> <p style="white-space: normal;box-sizing: border-box;"><strong style="box-sizing: border-box;">【used_memory】</strong>:Redis内存占用中最主要的部分,Redis分配器分配的内存总量(单位是KB)(在编译时指定编译器,默认是jemalloc),主要包含自身内存(字典、元数据)、对象内存、缓存,lua内存。</p> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> <p style="white-space: normal;box-sizing: border-box;"><strong style="box-sizing: border-box;">【自身内存】</strong>:自身维护的一些数据字典及元数据,一般占用内存很低。</p> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> <p style="white-space: normal;box-sizing: border-box;"><strong style="box-sizing: border-box;">【对象内存】</strong>:所有对象都是Key-Value型,Key对象都是字符串,Value对象则包括5种类(String,List,Hash,Set,Zset),5.0还支持stream类型。</p> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> <p style="white-space: nor
作者:じ☆ve不哭
电脑突然蓝屏,随之就关机了。 开着的linux虚拟机躺枪 再次重启虚拟机 进入恢复模式 如下提示:  度娘一顿搜: 很有牌面: 整齐划一的解决方案 ``` xfs_repair -v -L /dev/dm-0 ``` 我的虚拟机无动于衷!!! 浏览器中一顿点点点: **终于等到你还好没放弃** ``` xfs_repair -v -L /dev/sda3 (3后面加个空格再回车) ```  完了重启虚拟机 ``` reboot ``` 第二次蓝屏关机,虚拟机又中了一枪…  出现这个红色叉号,,该怎么解决呢?? ``` 找到虚拟机安装位置,把上面的文件夹统统删了(物理删除的话,linux会误以为导入的其它window文件,使的虚拟机不好用) ```  再正常打开虚拟机就okk了 转载自: 作者:waving at cars https://blog.csdn.net/weixin_46609492/article/details/120270264
作者:じ☆ve不哭
强制InnoDB恢复 为了研究数据库页损坏,你能用SELECT … INTO OUTFILE从数据库中转储表。通常,以这种方式获得的大部分数据是完整的。严重的损坏可能导致SELECT* FROM tbl_name语句或InnoDB的后台操作崩溃或断言,甚至造成InnoDB前滚恢复崩溃。 在这样的情况下,可以使用innodb_force_recovery选项强制InnoDB存储引擎启动同时阻止后台操作运行,这样你就能转储表了。例如,你可以在重新启动服务器之前添加以下行到选项文件的[mysqld]部分: ``` [mysqld] innodb_force_recovery = N ``` 只有在紧急情况下将innodb_force_recovery设为大于0的值,你才能启动InnoDB并转储表。在进行此操作之前,确保你有数据库的备份副本,以备需要重建它。4及以上的值可以永久破坏数据文件。只有在数据库的独立物理副本的成功地测试了设置,才能在生产服务器实例使用4及以上的innodb_force_recovery设置。当强制InnoDB恢复,你应该总是以innodb_force_recovery=1启动,且仅在需要时增加值。 innodb_force_recovery默认为0(没有强制恢复的正常启动)。对于innodb_force_recovery允许的非零值是1至6。较大值包括较小值的功能。例如,为3的值包括所有的值1和2的功能。 如果你能以innodb_force_recovery为3或更低值转储你的表,那么你是比较安全的,只有在损坏的个人页的一些数据会丢失。4或更大的值被认为是危险的,因为数据文件可以被永久地损坏。值6被认为是严重的,数据库页被留在一个陈旧的状态,这反过来又可能带给B-trees和其它数据库结构更多的损坏。 作为一个安全措施,InnoDB 在innodb_force_recovery大于0时阻止INSERT,UPDATE或DELETE操作。对于MySQL5.6.15,将innodb_force_recovery设为4或更高会让InnoDB处于只读模式。 **1 (SRV_FORCE_IGNORE_CORRUPT)** 即使服务器检测到损坏的页仍让它运行。试图使SELECT* FROM tbl_name跳过损坏的索引记录和页,这样有助于转储表。忽略检查到的 corrupt 页。尽管检测到了损坏的 page 仍强制服务运行。一般设置为该值即可,然后 dump 出库表进行重建。 **2 (SRV_FORCE_NO_BACKGROUND)** 阻止主线程和任何清除线程的运行。如果崩溃会在清除操作中发生,该恢复值会阻止它。如主线程需要执行 full purge 操作,会导致 crash。 阻止 master thread 和任何 purge thread 运行。若 crash 发生在 purge 环节则使用该值。 **3 (SRV_FORCE_NO_TRX_UNDO)** 不在崩溃恢复后运行事务回滚。 **4 (SRV_FORCE_NO_IBUF_MERGE)** 阻止插入缓冲合并操作。如果它们会导致崩溃,不要做这些。不计算表统计。这个值可以永久损坏数据文件。使用这个值后,准备好删除并重建所有辅助索引。在MySQL5.6.15中,设置InnoDB为只读。 **5 (SRV_FORCE_NO_UNDO_LOG_SCAN)** 在启动数据库时不查看撤消日志:InnoDB将即使未完成的事务也作为已提交。这个值可以永久损坏数据文件。在MySQL5.6.15中,设置InnoDB为只读。 **6 (SRV_FORCE_NO_LOG_REDO)** 不执行前滚的操作。恢复时不做 redo log roll-forward。这个值可能永久损坏数据文件。数据库页被留在一个陈旧的状态,这反过来又可能带给B-trees和其它数据库结构更多的损坏。在MySQL5.6.15中,设置InnoDB为只读。 参数使用建议: - 你可以从表中SELECT来转储它们。innodb_force_recovery的值为3或更低,你可以DROP或CREATE表。在MySQL 5.6.27中,DROP TABLE还受大于3的innodb_force_recovery值支持。 - 如果你知道一个给定表在回滚造成崩溃,你可以将其删除。如果遇到所造成失败的大规模导入的失控回滚或ALTER TABLE,你可以杀掉mysqld进程,并设置innodb_force_recovery为3使数据库启动而不回滚,然后DROP导致失控回滚的表。 - 如果表数据中的损坏阻止你转储整个表的内容,带ORDER BY primary_key DESC子句的查询能够转储损坏部分后的表的部分。 - 如果一个高innodb_force_recovery值需要启动InnoDB,可能有被破坏的数据结构,可能导致复杂查询(含有WHERE,ORDER BY或其他子句的查询)失败。在这种情况下,你可能只能运行基本的SELECT* FROM t查询。 - 将innodb_force_recovery参数设置大于0启动服务后,应通过修改端口或域名(VIP)指向来屏蔽应用访问。 - 将innodb_force_recovery参数设置大于0启动服务后,可以通过mysqlcheck命令来对表进行检查/分析/优化/修复。 - 使用force_recovery重启服务前,建议对数据库所有文件进行备份,避免修复过程中对数据进行二次损害。
作者:じ☆ve不哭
> 有时候java进程会出现莫名其妙的问题,需要dump下来分析,那么这个脚本就派上用场了,一键dump所有java进程。不用再麻烦的敲命令的。 效果见具体参见下图:  ``` #!/bin/sh DUMP_PIDS=`ps --no-heading -C java -f --width 1000 |awk '{print $2}'` if [ -z "$DUMP_PIDS" ]; then echo "The server $HOST_NAME is not started!" exit 1; fi DUMP_ROOT=~/dump if [ ! -d $DUMP_ROOT ]; then mkdir $DUMP_ROOT fi DUMP_DATE=`date +%Y%m%d%H%M%S` DUMP_DIR=$DUMP_ROOT/dump-$DUMP_DATE if [ ! -d $DUMP_DIR ]; then mkdir $DUMP_DIR fi for PID in $DUMP_PIDS ; do #Full thread dump 用来查线程占用,死锁等问题 $JAVA_HOME/bin/jstack $PID > $DUMP_DIR/jstack-$PID.dump 2>&1 echo -e ".\c" #打印出一个给定的Java进程、Java core文件或远程Debug服务器的Java配置信息,具体包括Java系统属性和JVM命令行参数。 $JAVA_HOME/bin/jinfo $PID > $DUMP_DIR/jinfo-$PID.dump 2>&1 echo -e ".\c" #jstat能够动态打印jvm(Java Virtual Machine Statistics Monitoring Tool)的相关统计信息。如young gc执行的次数、full gc执行的次数,各个内存分区的空间大小和可使用量等信息。 $JAVA_HOME/bin/jstat -gcutil $PID > $DUMP_DIR/jstat-gcutil-$PID.dump 2>&1 echo -e ".\c" $JAVA_HOME/bin/jstat -gccapacity $PID > $DUMP_DIR/jstat-gccapacity-$PID.dump 2>&1 echo -e ".\c" #未指定选项时,jmap打印共享对象的映射。对每个目标VM加载的共享对象,其起始地址、映射大小及共享对象文件的完整路径将被打印出来, $JAVA_HOME/bin/jmap $PID > $DUMP_DIR/jmap-$PID.dump 2>&1 echo -e ".\c" #-heap打印堆情况的概要信息,包括堆配置,各堆空间的容量、已使用和空闲情况 $JAVA_HOME/bin/jmap -heap $PID > $DUMP_DIR/jmap-heap-$PID.dump 2>&1 echo -e ".\c" #-dump将jvm的堆中内存信息输出到一个文件中,然后可以通过eclipse memory analyzer进行分析 #注意:这个jmap使用的时候jvm是处在假死状态的,只能在服务瘫痪的时候为了解决问题来使用,否则会造成服务中断。 $JAVA_HOME/bin/jmap -dump:format=b,file=$DUMP_DIR/jmap-dump-$PID.dump $PID 2>&1 echo -e ".\c" #显示被进程打开的文件信息 if [ -r /usr/sbin/lsof ]; then /usr/sbin/lsof -p $PID > $DUMP_DIR/lsof-$PID.dump echo -e ".\c" fi done #主要负责收集、汇报与存储系统运行信息的。 if [ -r /usr/bin/sar ]; then /usr/bin/sar > $DUMP_DIR/sar.dump echo -e ".\c" fi #主要负责收集、汇报与存储系统运行信息的。 if [ -r /usr/bin/uptime ]; then /usr/bin/uptime > $DUMP_DIR/uptime.dump echo -e ".\c" fi #内存查看 if [ -r /usr/bin/free ]; then /usr/bin/free -t > $DUMP_DIR/free.dump echo -e ".\c" fi #可以得到关于进程、内存、内存分页、堵塞IO、traps及CPU活动的信息。 if [ -r /usr/bin/vmstat ]; then /usr/bin/vmstat > $DUMP_DIR/vmstat.dump echo -e ".\c" fi #报告与CPU相关的一些统计信息 if [ -r /usr/bin/mpstat ]; then /usr/bin/mpstat > $DUMP_DIR/mpstat.dump echo -e ".\c" fi #报告与IO相关的一些统计信息 if [ -r /usr/bin/iostat ]; then /usr/bin/iostat > $DUMP_DIR/iostat.dump echo -e ".\c" fi #报告与网络相关的一些统计信息 if [ -r /bin/netstat ]; then /bin/netstat > $DUMP_DIR/netstat.dump echo -e ".\c" fi echo "OK!" ``` 转载自:IT运维技术圈 原文地址:[https://mp.weixin.qq.com/s/eQuFUzEo2be3BrXn17pjXg](https://mp.weixin.qq.com/s/eQuFUzEo2be3BrXn17pjXg)
作者:微信小助手
<p data-tool="mdnice编辑器" style="margin: 10px;padding-top: 8px;padding-bottom: 8px;outline: 0px;max-width: 100%;color: rgb(0, 0, 0);font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;text-align: left;white-space: normal;line-height: 1.75;letter-spacing: 0.2em;font-size: 15px;word-spacing: 0.1em;visibility: visible;box-sizing: border-box !important;overflow-wrap: break-word !important;">我们都知道,消息从生产端到消费端消费要经过3个步骤:</p> <ol data-tool="mdnice编辑器" class="list-paddingleft-1" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;outline: 0px;max-width: 100%;color: rgb(0, 0, 0);font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;font-size: 16px;text-align: left;white-space: normal;visibility: visible;overflow-wrap: break-word !important;"> <li style="outline: 0px;max-width: 100%;visibility: visible;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <section style="margin-top: 5px;margin-bottom: 5px;outline: 0px;max-width: 100%;line-height: 26px;color: rgb(1, 1, 1);font-size: 15px;visibility: visible;box-sizing: border-box !important;overflow-wrap: break-word !important;"> 生产端发送消息到RabbitMQ; </section></li> <li style="outline: 0px;max-width: 100%;visibility: visible;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <section style="margin-top: 5px;margin-bottom: 5px;outline: 0px;max-width: 100%;line-height: 26px;color: rgb(1, 1, 1);font-size: 15px;visibility: visible;box-sizing: border-box !important;overflow-wrap: break-word !important;"> RabbitMQ发送消息到消费端; </section></li> <li style="outline: 0px;max-width: 100%;visibility: visible;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <section style="margin-top: 5px;margin-bottom: 5px;outline: 0px;max-width: 100%;line-height: 26px;color: rgb(1, 1, 1);font-size: 15px;visibility: visible;box-sizing: border-box !important;overflow-wrap: break-word !important;"> 消费端消费这条消息; </section></li> </ol> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;outline: 0px;max-width: 100%;color: rgb(0, 0, 0);font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;font-size: 16px;text-align: left;white-space: normal;display: flex;flex-direction: column;justify-content: center;align-items: center;visibility: visible;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <a target="_blank" href="https://mp.weixin.qq.com/s?__biz=MzUzMTA2NTU2Ng==&mid=2247487551&idx=1&sn=18f64ba49f3f0f9d8be9d1fdef8857d9&scene=21#wechat_redirect" textvalue="你已选中了添加链接的内容" linktype="text" imgurl="" imgdata="null" tab="innerlink" data-linktype="1" wah-hotarea="click" style="outline: 0px;-webkit-tap-highlight-color: rgba(0, 0, 0, 0);cursor: pointer;max-width: 100%;visibility: visible;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span class="js_jump_icon h5_image_link" data-positionback="static" style="margin: 20px 39.6771px;outline: 0px;max-width: 100%;line-height: 0;vertical-align: bottom;user-select: none;width: 608.354px;visibility: visible;box-sizing: border-box !important;overflow-wrap: break-word !important;"><img class="rich_pages wxw-img" data-ratio="0.46880907372400754" src="/upload/f12dba8fd0e26e21f6aba3f902f1edcc.png" data-type="png" data-w="529" style="outline: 0px;border-width: 0px;border-style: initial;border-color: initial;border-radius: 0px 0px 5px 5px;display: block;object-fit: contain;box-shadow: rgb(132, 161, 168) 0px 10px 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;visibility: visible !important;width: 517.094px !important;"></span></a> </figure> <p data-tool="mdnice编辑器" style="margin: 10px;padding-top: 8px;padding-bottom: 8px;outline: 0px;max-width: 100%;color: rgb(0, 0, 0);font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;text-align: left;white-space: normal;line-height: 1.75;letter-spacing: 0.2em;font-size: 15px;word-spacing: 0.1em;visibility: visible;box-sizing: border-box !important;overflow-wrap: break-word !important;">这3个步骤中的每一步都有可能导致消息丢失,消息丢失不可怕,可怕的是丢失了我们还不知道,所以要有一些措施来保证系统的可靠性。这里的可靠并不是一定就100%不丢失了,磁盘损坏,机房爆炸等等都能导致数据丢失,当然这种都是极小概率发生,能做到99.999999%消息不丢失,就是可靠的了。下面来具体分析一下问题以及解决方案。</p> <h2 data-tool="mdnice编辑器"><a href="https://mp.weixin.qq.com/s?__biz=MzUzMTA2NTU2Ng==&mid=2247487551&idx=1&sn=18f64ba49f3f0f9d8be9d1fdef8857d9&scene=21#wechat_redirect" data-linktype="2" wah-hotarea="click"><strong><span style="color: rgb(123, 12, 0);">生产端可靠性投递</span></strong></a></h2> <p data-tool="mdnice编辑器" style="margin: 10px;padding-top: 8px;padding-bottom: 8px;outline: 0px;max-width: 100%;color: rgb(0, 0, 0);font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;text-align: left;white-space: normal;line-height: 1.75;letter-spacing: 0.2em;font-size: 15px;word-spacing: 0.1em;visibility: visible;box-sizing: border-box !important;overflow-wrap: break-word !important;">生产端可靠性投递,即生产端要确保将消息正确投递到RabbitMQ中。生产端投递的消息丢失的原因有很多,比如消息在网络传输的过程中发生网络故障消息丢失,或者消息投递到RabbitMQ时RabbitMQ挂了,那消息也可能丢失,而我们根本不知道发生了什么。针对以上情况,RabbitMQ本身提供了一些机制。</p> <blockquote data-tool="mdnice编辑器" style="margin-top: 20px;margin-bottom: 20px;padding-top: 10px;padding-right: 10px;padding-bottom: 10px;outline: 0px;border-width: initial;border-style: none;border-color: initial;color: rgb(14, 136, 235);font-size: 0.9em;max-width: 100%;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;text-align: left;white-space: normal;overflow: auto;line-height: 1.8;border-radius: 0px 0px 10px 10px;background-image: initial;background-position: initial;background-size: initial;background-repeat: initial;background-attachment: initial;background-origin: initial;background-clip: initial;box-shadow: rgb(132, 161, 168) 0px 10px 15px;visibility: visible;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <p style="padding-top: 8px;padding-bottom: 8px;outline: 0px;max-width: 100%;letter-spacing: 0.2em;word-spacing: 0.1em;line-height: 26px;font-size: 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;">基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能。</p> <p style="padding-top: 8px;padding-bottom: 8px;outline: 0px;max-width: 100%;letter-spacing: 0.2em;word-spacing: 0.1em;line-height: 26px;font-size: 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;">项目地址:https://github.com/YunaiV/ruoyi-vue-pro</p> </blockquote> <h2 data-tool="mdnice编辑器"><a href="https://mp.weixin.qq.com/s?__biz=MzUzMTA2NTU2Ng==&mid=2247487551&idx=1&sn=18f64ba49f3f0f9d8be9d1fdef8857d9&scene=21#wechat_redirect" data-linktype="2" wah-hotarea="click"><span style="color: rgb(123, 12, 0);"><strong>事务消息机制</strong></span></a></h2> <p data-tool="mdnice编辑器" style="margin: 10px;padding-top: 8px;padding-bottom: 8px;outline: 0px;max-width: 100%;color: rgb(0, 0, 0);font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;text-align: left;white-space: normal;line-height: 1.75;letter-spacing: 0.2em;font-size: 15px;word-spacing: 0.1em;box-sizing: border-box !important;overflow-wrap: break-word !important;">事务消息机制由于会严重降低性能,所以一般不采用这种方法,我就不介绍了,而采用另一种轻量级的解决方案——confirm消息确认机制。</p> <blockquote data-tool="mdnice编辑器" style="margin-top: 20px;margin-bottom: 20px;padding-top: 10px;padding-right: 10px;padding-bottom: 10px;outline: 0px;border-width: initial;border-style: none;border-color: initial;color: rgb(14, 136, 235);font-size: 0.9em;max-width: 100%;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;text-align: left;white-space: normal;overflow: auto;line-height: 1.8;border-radius: 0px 0px 10px 10px;background-image: initial;background-position: initial;background-size: initial;background-repeat: initial;background-attachment: initial;background-origin: initial;background-clip: initial;box-shadow: rgb(132, 161, 168) 0px 10px 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <p style="padding-top: 8px;padding-bottom: 8px;outline: 0px;max-width: 100%;letter-spacing: 0.2em;word-spacing: 0.1em;line-height: 26px;font-size: 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;">基于微服务的思想,构建在 B2C 电商场景下的项目实战。核心技术栈,是 Spring Boot + Dubbo 。未来,会重构成 Spring Cloud Alibaba 。</p> <p style="padding-top: 8px;padding-bottom: 8px;outline: 0px;max-width: 100%;letter-spacing: 0.2em;word-spacing: 0.1em;line-height: 26px;font-size: 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;">项目地址:https://github.com/YunaiV/onemall</p> </blockquote> <h2 data-tool="mdnice编辑器"><a href="https://mp.weixin.qq.com/s?__biz=MzUzMTA2NTU2Ng==&mid=2247487551&idx=1&sn=18f64ba49f3f0f9d8be9d1fdef8857d9&scene=21#wechat_redirect" data-linktype="2" wah-hotarea="click"><span style="color: rgb(123, 12, 0);"><strong>confirm消息确认机制</strong></span></a></h2> <p data-tool="mdnice编辑器" style="margin: 10px;padding-top: 8px;padding-bottom: 8px;outline: 0px;max-width: 100%;color: rgb(0, 0, 0);font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;text-align: left;white-space: normal;line-height: 1.75;letter-spacing: 0.2em;font-size: 15px;word-spacing: 0.1em;box-sizing: border-box !important;overflow-wrap: break-word !important;">什么是confirm消息确认机制?顾名思义,就是生产端投递的消息一旦投递到RabbitMQ后,RabbitMQ就会发送一个确认消息给生产端,让生产端知道我已经收到消息了,否则这条消息就可能已经丢失了,需要生产端重新发送消息了。</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;outline: 0px;max-width: 100%;color: rgb(0, 0, 0);font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;font-size: 16px;text-align: left;white-space: normal;display: flex;flex-direction: column;justify-content: center;align-items: center;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <a target="_blank" href="https://mp.weixin.qq.com/s?__biz=MzUzMTA2NTU2Ng==&mid=2247487551&idx=1&sn=18f64ba49f3f0f9d8be9d1fdef8857d9&scene=21#wechat_redirect" textvalue="你已选中了添加链接的内容" linktype="text" imgurl="" imgdata="null" tab="innerlink" data-linktype="1" wah-hotarea="click" style="outline: 0px;-webkit-tap-highlight-color: rgba(0, 0, 0, 0);cursor: pointer;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span class="js_jump_icon h5_image_link" data-positionback="static" style="margin: 20px 34.9583px 20px 34.9479px;outline: 0px;max-width: 100%;line-height: 0;vertical-align: bottom;user-select: none;width: 535.885px;box-sizing: border-box !important;overflow-wrap: break-word !important;"><img class="rich_pages wxw-img" data-ratio="0.6330472103004292" src="/upload/81db13c60ddfbbfa004bc5a31ad06c82.png" data-type="png" data-w="466" style="outline: 0px;border-width: 0px;border-style: initial;border-color: initial;border-radius: 0px 0px 5px 5px;display: block;object-fit: contain;box-shadow: rgb(132, 161, 168) 0px 10px 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;visibility: visible !important;width: 455.5px !important;"></span></a> </figure> <p data-tool="mdnice编辑器" style="margin: 10px;padding-top: 8px;padding-bottom: 8px;outline: 0px;max-width: 100%;color: rgb(0, 0, 0);font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;text-align: left;white-space: normal;line-height: 1.75;letter-spacing: 0.2em;font-size: 15px;word-spacing: 0.1em;box-sizing: border-box !important;overflow-wrap: break-word !important;">通过下面这句代码来开启确认模式:</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;outline: 0px;max-width: 100%;color: rgb(0, 0, 0);font-size: 16px;text-align: left;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="margin-bottom: -7px;outline: 0px;max-width: 100%;display: block;background: url("https://mmbiz.qpic.cn/mmbiz_svg/wXJ5kSJT6ONMdYq9ExLicAvGeAJqWGOCkuicJXZAgsSbOX4ULHIwDThlVECxKnMT8kwIiapGTwZa8zDsnOlacRaDy08ZicyGgkKA/640?wx_fmt=svg") 10px 10px / 40px no-repeat rgb(30, 30, 30);height: 30px;width: 657px;border-radius: 5px;box-sizing: border-box !important;overflow-wrap: break-word !important;"></span><code style="padding: 15px 16px 16px;outline: 0px;max-width: 100%;overflow-x: auto;color: rgb(220, 220, 220);display: -webkit-box;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;font-size: 12px;background: rgb(30, 30, 30);border-radius: 5px;box-sizing: border-box !important;overflow-wrap: break-word !important;">channel.confirmSelect();<span style="outline: 0px;max-width: 100%;color: rgb(87, 166, 74);font-style: italic;line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">// 开启发送方确认模式</span><br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"></code></pre> <p data-tool="mdnice编辑器" style="margin: 10px;padding-top: 8px;padding-bottom: 8px;outline: 0px;max-width: 100%;color: rgb(0, 0, 0);font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;text-align: left;white-space: normal;line-height: 1.75;letter-spacing: 0.2em;font-size: 15px;word-spacing: 0.1em;box-sizing: border-box !important;overflow-wrap: break-word !important;">然后异步监听确认和未确认的消息:</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;outline: 0px;max-width: 100%;color: rgb(0, 0, 0);font-size: 16px;text-align: left;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="margin-bottom: -7px;outline: 0px;max-width: 100%;display: block;background: url("https://mmbiz.qpic.cn/mmbiz_svg/wXJ5kSJT6ONMdYq9ExLicAvGeAJqWGOCkuicJXZAgsSbOX4ULHIwDThlVECxKnMT8kwIiapGTwZa8zDsnOlacRaDy08ZicyGgkKA/640?wx_fmt=svg") 10px 10px / 40px no-repeat rgb(30, 30, 30);height: 30px;width: 657px;border-radius: 5px;box-sizing: border-box !important;overflow-wrap: break-word !important;"></span><code style="padding: 15px 16px 16px;outline: 0px;max-width: 100%;overflow-x: auto;color: rgb(220, 220, 220);display: -webkit-box;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;font-size: 12px;background: rgb(30, 30, 30);border-radius: 5px;box-sizing: border-box !important;overflow-wrap: break-word !important;">channel.addConfirmListener(<span style="outline: 0px;max-width: 100%;color: rgb(86, 156, 214);line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">new</span> ConfirmListener() {<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <span style="outline: 0px;max-width: 100%;color: rgb(87, 166, 74);font-style: italic;line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">//消息正确到达broker</span><br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <span style="outline: 0px;max-width: 100%;color: rgb(155, 155, 155);line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">@Override</span><br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <span style="outline: 0px;max-width: 100%;line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="outline: 0px;max-width: 100%;color: rgb(86, 156, 214);line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">public</span> <span style="outline: 0px;max-width: 100%;color: rgb(86, 156, 214);line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">void</span> <span style="outline: 0px;max-width: 100%;line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">handleAck</span><span style="outline: 0px;max-width: 100%;line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">(<span style="outline: 0px;max-width: 100%;color: rgb(86, 156, 214);line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">long</span> deliveryTag, <span style="outline: 0px;max-width: 100%;color: rgb(86, 156, 214);line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">boolean</span> multiple)</span> <span style="outline: 0px;max-width: 100%;color: rgb(86, 156, 214);line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">throws</span> IOException </span>{<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"> System.out.println(<span style="outline: 0px;max-width: 100%;color: rgb(214, 157, 133);line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">"已收到消息"</span>);<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <span style="outline: 0px;max-width: 100%;color: rgb(87, 166, 74);font-style: italic;line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">//做一些其他处理</span><br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"> }<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"><br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <span style="outline: 0px;max-width: 100%;color: rgb(87, 166, 74);font-style: italic;line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">//RabbitMQ因为自身内部错误导致消息丢失,就会发送一条nack消息</span><br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <span style="outline: 0px;max-width: 100%;color: rgb(155, 155, 155);line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">@Override</span><br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <span style="outline: 0px;max-width: 100%;line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="outline: 0px;max-width: 100%;color: rgb(86, 156, 214);line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">public</span> <span style="outline: 0px;max-width: 100%;color: rgb(86, 156, 214);line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">void</span> <span style="outline: 0px;max-width: 100%;line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">handleNack</span><span style="outline: 0px;max-width: 100%;line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">(<span style="outline: 0px;max-width: 100%;color: rgb(86, 156, 214);line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">long</span> deliveryTag, <span style="outline: 0px;max-width: 100%;color: rgb(86, 156, 214);line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">boolean</span> multiple)</span> <span style="outline: 0px;max-width: 100%;color: rgb(86, 156, 214);line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">throws</span> IOException </span>{<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"> System.out.println(<span style="outline: 0px;max-width: 100%;color: rgb(214, 157, 133);line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">"未确认消息,标识:"</span> + deliveryTag);<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <span style="outline: 0px;max-width: 100%;color: rgb(87, 166, 74);font-style: italic;line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">//做一些其他处理,比如消息重发等</span><br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"> }<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;">});<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"></code></pre> <p data-tool="mdnice编辑器" style="margin: 10px;padding-top: 8px;padding-bottom: 8px;outline: 0px;max-width: 100%;color: rgb(0, 0, 0);font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;text-align: left;white-space: normal;line-height: 1.75;letter-spacing: 0.2em;font-size: 15px;word-spacing: 0.1em;box-sizing: border-box !important;overflow-wrap: break-word !important;">这样就可以让生产端感知到消息是否投递到RabbitMQ中了,当然这样还不够,稍后我会说一下极端情况。</p> <h2 data-tool="mdnice编辑器"><a href="https://mp.weixin.qq.com/s?__biz=MzUzMTA2NTU2Ng==&mid=2247487551&idx=1&sn=18f64ba49f3f0f9d8be9d1fdef8857d9&scene=21#wechat_redirect" data-linktype="2" wah-hotarea="click"><span style="color: rgb(123, 12, 0);"><strong>消息持久化</strong></span></a></h2> <p data-tool="mdnice编辑器" style="margin: 10px;padding-top: 8px;padding-bottom: 8px;outline: 0px;max-width: 100%;color: rgb(0, 0, 0);font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;text-align: left;white-space: normal;line-height: 1.75;letter-spacing: 0.2em;font-size: 15px;word-spacing: 0.1em;box-sizing: border-box !important;overflow-wrap: break-word !important;">那消息持久化呢?我们知道,RabbitMQ收到消息后将这个消息暂时存在了内存中,那这就会有个问题,如果RabbitMQ挂了,那重启后数据就丢失了,所以相关的数据应该持久化到硬盘中,这样就算RabbitMQ重启后也可以到硬盘中取数据恢复。那如何持久化呢?</p> <p data-tool="mdnice编辑器" style="margin: 10px;padding-top: 8px;padding-bottom: 8px;outline: 0px;max-width: 100%;color: rgb(0, 0, 0);font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;text-align: left;white-space: normal;line-height: 1.75;letter-spacing: 0.2em;font-size: 15px;word-spacing: 0.1em;box-sizing: border-box !important;overflow-wrap: break-word !important;">message消息到达RabbitMQ后先是到exchange交换机中,然后路由给queue队列,最后发送给消费端。</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;outline: 0px;max-width: 100%;color: rgb(0, 0, 0);font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;font-size: 16px;text-align: left;white-space: normal;display: flex;flex-direction: column;justify-content: center;align-items: center;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <a target="_blank" href="https://mp.weixin.qq.com/s?__biz=MzUzMTA2NTU2Ng==&mid=2247487551&idx=1&sn=18f64ba49f3f0f9d8be9d1fdef8857d9&scene=21#wechat_redirect" textvalue="你已选中了添加链接的内容" linktype="text" imgurl="" imgdata="null" tab="innerlink" data-linktype="1" wah-hotarea="click" style="outline: 0px;-webkit-tap-highlight-color: rgba(0, 0, 0, 0);cursor: pointer;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span class="js_jump_icon h5_image_link" data-positionback="static" style="margin: 20px 41.8542px;outline: 0px;max-width: 100%;line-height: 0;vertical-align: bottom;user-select: none;width: 657px;box-sizing: border-box !important;overflow-wrap: break-word !important;"><img class="rich_pages wxw-img" data-ratio="0.4131386861313869" src="/upload/480eaf2ad70ea2e60613a96584b81908.png" data-type="png" data-w="685" style="outline: 0px;border-width: 0px;border-style: initial;border-color: initial;border-radius: 0px 0px 5px 5px;display: block;object-fit: contain;box-shadow: rgb(132, 161, 168) 0px 10px 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;visibility: visible !important;width: 558.448px !important;"></span></a> </figure> <p data-tool="mdnice编辑器" style="margin: 10px;padding-top: 8px;padding-bottom: 8px;outline: 0px;max-width: 100%;color: rgb(0, 0, 0);font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;text-align: left;white-space: normal;line-height: 1.75;letter-spacing: 0.2em;font-size: 15px;word-spacing: 0.1em;box-sizing: border-box !important;overflow-wrap: break-word !important;">所有需要给exchange、queue和message都进行持久化:</p> <p data-tool="mdnice编辑器" style="margin: 10px;padding-top: 8px;padding-bottom: 8px;outline: 0px;max-width: 100%;color: rgb(0, 0, 0);font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;text-align: left;white-space: normal;line-height: 1.75;letter-spacing: 0.2em;font-size: 15px;word-spacing: 0.1em;box-sizing: border-box !important;overflow-wrap: break-word !important;">exchange持久化:</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;outline: 0px;max-width: 100%;color: rgb(0, 0, 0);font-size: 16px;text-align: left;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="margin-bottom: -7px;outline: 0px;max-width: 100%;display: block;background: url("https://mmbiz.qpic.cn/mmbiz_svg/wXJ5kSJT6ONMdYq9ExLicAvGeAJqWGOCkuicJXZAgsSbOX4ULHIwDThlVECxKnMT8kwIiapGTwZa8zDsnOlacRaDy08ZicyGgkKA/640?wx_fmt=svg") 10px 10px / 40px no-repeat rgb(30, 30, 30);height: 30px;width: 657px;border-radius: 5px;box-sizing: border-box !important;overflow-wrap: break-word !important;"></span><code style="padding: 15px 16px 16px;outline: 0px;max-width: 100%;overflow-x: auto;color: rgb(220, 220, 220);display: -webkit-box;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;font-size: 12px;background: rgb(30, 30, 30);border-radius: 5px;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="outline: 0px;max-width: 100%;color: rgb(87, 166, 74);font-style: italic;line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">//第三个参数true表示这个exchange持久化</span><br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;">channel.exchangeDeclare(EXCHANGE_NAME, <span style="outline: 0px;max-width: 100%;color: rgb(214, 157, 133);line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">"direct"</span>, <span style="outline: 0px;max-width: 100%;color: rgb(86, 156, 214);line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">true</span>);<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"></code></pre> <p data-tool="mdnice编辑器" style="margin: 10px;padding-top: 8px;padding-bottom: 8px;outline: 0px;max-width: 100%;color: rgb(0, 0, 0);font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;text-align: left;white-space: normal;line-height: 1.75;letter-spacing: 0.2em;font-size: 15px;word-spacing: 0.1em;box-sizing: border-box !important;overflow-wrap: break-word !important;">queue持久化:</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;outline: 0px;max-width: 100%;color: rgb(0, 0, 0);font-size: 16px;text-align: left;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="margin-bottom: -7px;outline: 0px;max-width: 100%;display: block;background: url("https://mmbiz.qpic.cn/mmbiz_svg/wXJ5kSJT6ONMdYq9ExLicAvGeAJqWGOCkuicJXZAgsSbOX4ULHIwDThlVECxKnMT8kwIiapGTwZa8zDsnOlacRaDy08ZicyGgkKA/640?wx_fmt=svg") 10px 10px / 40px no-repeat rgb(30, 30, 30);height: 30px;width: 657px;border-radius: 5px;box-sizing: border-box !important;overflow-wrap: break-word !important;"></span><code style="padding: 15px 16px 16px;outline: 0px;max-width: 100%;overflow-x: auto;color: rgb(220, 220, 220);display: -webkit-box;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;font-size: 12px;background: rgb(30, 30, 30);border-radius: 5px;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="outline: 0px;max-width: 100%;color: rgb(87, 166, 74);font-style: italic;line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">//第二个参数true表示这个queue持久化</span><br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;">channel.queueDeclare(QUEUE_NAME, <span style="outline: 0px;max-width: 100%;color: rgb(86, 156, 214);line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">true</span>, <span style="outline: 0px;max-width: 100%;color: rgb(86, 156, 214);line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">false</span>, <span style="outline: 0px;max-width: 100%;color: rgb(86, 156, 214);line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">false</span>, <span style="outline: 0px;max-width: 100%;color: rgb(86, 156, 214);line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">null</span>);<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"></code></pre> <p data-tool="mdnice编辑器" style="margin: 10px;padding-top: 8px;padding-bottom: 8px;outline: 0px;max-width: 100%;color: rgb(0, 0, 0);font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;text-align: left;white-space: normal;line-height: 1.75;letter-spacing: 0.2em;font-size: 15px;word-spacing: 0.1em;box-sizing: border-box !important;overflow-wrap: break-word !important;">message持久化:</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;outline: 0px;max-width: 100%;color: rgb(0, 0, 0);font-size: 16px;text-align: left;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="margin-bottom: -7px;outline: 0px;max-width: 100%;display: block;background: url("https://mmbiz.qpic.cn/mmbiz_svg/wXJ5kSJT6ONMdYq9ExLicAvGeAJqWGOCkuicJXZAgsSbOX4ULHIwDThlVECxKnMT8kwIiapGTwZa8zDsnOlacRaDy08ZicyGgkKA/640?wx_fmt=svg") 10px 10px / 40px no-repeat rgb(30, 30, 30);height: 30px;width: 657px;border-radius: 5px;box-sizing: border-box !important;overflow-wrap: break-word !important;"></span><code style="padding: 15px 16px 16px;outline: 0px;max-width: 100%;overflow-x: auto;color: rgb(220, 220, 220);display: -webkit-box;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;font-size: 12px;background: rgb(30, 30, 30);border-radius: 5px;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="outline: 0px;max-width: 100%;color: rgb(87, 166, 74);font-style: italic;line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">//第三个参数MessageProperties.PERSISTENT_TEXT_PLAIN表示这条消息持久化</span><br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;">channel.basicPublish(EXCHANGE_NAME, ROUTING_KEY, MessageProperties.PERSISTENT_TEXT_PLAIN, message.getBytes(StandardCharsets.UTF_8));<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"></code></pre> <p data-tool="mdnice编辑器" style="margin: 10px;padding-top: 8px;padding-bottom: 8px;outline: 0px;max-width: 100%;color: rgb(0, 0, 0);font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;text-align: left;white-space: normal;line-height: 1.75;letter-spacing: 0.2em;font-size: 15px;word-spacing: 0.1em;box-sizing: border-box !important;overflow-wrap: break-word !important;">这样,如果RabbitMQ收到消息后挂了,重启后会自行恢复消息。</p> <p data-tool="mdnice编辑器" style="margin: 10px;padding-top: 8px;padding-bottom: 8px;outline: 0px;max-width: 100%;color: rgb(0, 0, 0);font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;text-align: left;white-space: normal;line-height: 1.75;letter-spacing: 0.2em;font-size: 15px;word-spacing: 0.1em;box-sizing: border-box !important;overflow-wrap: break-word !important;">到此,RabbitMQ提供的几种机制都介绍完了,但这样还不足以保证消息可靠性投递RabbitMQ中,上面我也提到了会有极端情况,比如RabbitMQ收到消息还没来得及将消息持久化到硬盘时,RabbitMQ挂了,这样消息还是丢失了,或者RabbitMQ在发送确认消息给生产端的过程中,由于网络故障而导致生产端没有收到确认消息,这样生产端就不知道RabbitMQ到底有没有收到消息,就不好做接下来的处理。</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;outline: 0px;max-width: 100%;color: rgb(0, 0, 0);font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;font-size: 16px;text-align: left;white-space: normal;display: flex;flex-direction: column;justify-content: center;align-items: center;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <a target="_blank" href="https://mp.weixin.qq.com/s?__biz=MzUzMTA2NTU2Ng==&mid=2247487551&idx=1&sn=18f64ba49f3f0f9d8be9d1fdef8857d9&scene=21#wechat_redirect" textvalue="你已选中了添加链接的内容" linktype="text" imgurl="" imgdata="null" tab="innerlink" data-linktype="1" wah-hotarea="click" style="outline: 0px;-webkit-tap-highlight-color: rgba(0, 0, 0, 0);cursor: pointer;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span class="js_jump_icon h5_image_link" data-positionback="static" style="margin: 20px 41.8542px;outline: 0px;max-width: 100%;line-height: 0;vertical-align: bottom;user-select: none;width: 651.708px;box-sizing: border-box !important;overflow-wrap: break-word !important;"><img class="rich_pages wxw-img" data-ratio="0.47007042253521125" src="/upload/e5304448c26747a0a5a9478d556f825d.png" data-type="png" data-w="568" style="outline: 0px;border-width: 0px;border-style: initial;border-color: initial;border-radius: 0px 0px 5px 5px;display: block;object-fit: contain;box-shadow: rgb(132, 161, 168) 0px 10px 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;visibility: visible !important;width: 553.948px !important;"></span></a> </figure> <p data-tool="mdnice编辑器" style="margin: 10px;padding-top: 8px;padding-bottom: 8px;outline: 0px;max-width: 100%;color: rgb(0, 0, 0);font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;text-align: left;white-space: normal;line-height: 1.75;letter-spacing: 0.2em;font-size: 15px;word-spacing: 0.1em;box-sizing: border-box !important;overflow-wrap: break-word !important;">所以除了RabbitMQ提供的一些机制外,我们自己也要做一些消息补偿机制,以应对一些极端情况。接下来我就介绍其中的一种解决方案——消息入库。</p> <h2 data-tool="mdnice编辑器"><a href="https://mp.weixin.qq.com/s?__biz=MzUzMTA2NTU2Ng==&mid=2247487551&idx=1&sn=18f64ba49f3f0f9d8be9d1fdef8857d9&scene=21#wechat_redirect" data-linktype="2" wah-hotarea="click"><span style="color: rgb(123, 12, 0);"><strong>消息入库</strong></span></a></h2> <p data-tool="mdnice编辑器" style="margin: 10px;padding-top: 8px;padding-bottom: 8px;outline: 0px;max-width: 100%;color: rgb(0, 0, 0);font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;text-align: left;white-space: normal;line-height: 1.75;letter-spacing: 0.2em;font-size: 15px;word-spacing: 0.1em;box-sizing: border-box !important;overflow-wrap: break-word !important;">消息入库,顾名思义就是将要发送的消息保存到数据库中。</p> <p data-tool="mdnice编辑器" style="margin: 10px;padding-top: 8px;padding-bottom: 8px;outline: 0px;max-width: 100%;color: rgb(0, 0, 0);font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;text-align: left;white-space: normal;line-height: 1.75;letter-spacing: 0.2em;font-size: 15px;word-spacing: 0.1em;box-sizing: border-box !important;overflow-wrap: break-word !important;">首先发送消息前先将消息保存到数据库中,有一个状态字段status=0,表示生产端将消息发送给了RabbitMQ但还没收到确认;在生产端收到确认后将status设为1,表示RabbitMQ已收到消息。这里有可能会出现上面说的两种情况,所以生产端这边开一个定时器,定时检索消息表,将status=0并且超过固定时间后(可能消息刚发出去还没来得及确认这边定时器刚好检索到这条status=0的消息,所以给个时间)还没收到确认的消息取出重发(第二种情况下这里会造成消息重复,消费者端要做幂等性),可能重发还会失败,所以可以做一个最大重发次数,超过就做另外的处理。</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;outline: 0px;max-width: 100%;color: rgb(0, 0, 0);font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;font-size: 16px;text-align: left;white-space: normal;display: flex;flex-direction: column;justify-content: center;align-items: center;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <a target="_blank" href="https://mp.weixin.qq.com/s?__biz=MzUzMTA2NTU2Ng==&mid=2247487551&idx=1&sn=18f64ba49f3f0f9d8be9d1fdef8857d9&scene=21#wechat_redirect" textvalue="你已选中了添加链接的内容" linktype="text" imgurl="" imgdata="null" tab="innerlink" data-linktype="1" wah-hotarea="click" style="outline: 0px;-webkit-tap-highlight-color: rgba(0, 0, 0, 0);cursor: pointer;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span class="js_jump_icon h5_image_link" data-positionback="static" style="margin: 20px 41.8542px;outline: 0px;max-width: 100%;line-height: 0;vertical-align: bottom;user-select: none;width: 657px;box-sizing: border-box !important;overflow-wrap: break-word !important;"><img class="rich_pages wxw-img" data-ratio="0.5558343789209536" src="/upload/29e007fee85a77b2242c75a93e435fe.png" data-type="png" data-w="797" style="outline: 0px;border-width: 0px;border-style: initial;border-color: initial;border-radius: 0px 0px 5px 5px;display: block;object-fit: contain;box-shadow: rgb(132, 161, 168) 0px 10px 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;visibility: visible !important;width: 558.448px !important;"></span></a> </figure> <p data-tool="mdnice编辑器" style="margin: 10px;padding-top: 8px;padding-bottom: 8px;outline: 0px;max-width: 100%;color: rgb(0, 0, 0);font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;text-align: left;white-space: normal;line-height: 1.75;letter-spacing: 0.2em;font-size: 15px;word-spacing: 0.1em;box-sizing: border-box !important;overflow-wrap: break-word !important;">这样消息就可以可靠性投递到RabbitMQ中了,而生产端也可以感知到了。</p> <h2 data-tool="mdnice编辑器"><a href="https://mp.weixin.qq.com/s?__biz=MzUzMTA2NTU2Ng==&mid=2247487551&idx=1&sn=18f64ba49f3f0f9d8be9d1fdef8857d9&scene=21#wechat_redirect" data-linktype="2" wah-hotarea="click"><span style="color: rgb(123, 12, 0);"><strong>消费端消息不丢失</strong></span></a></h2> <p data-tool="mdnice编辑器" style="margin: 10px;padding-top: 8px;padding-bottom: 8px;outline: 0px;max-width: 100%;color: rgb(0, 0, 0);font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;text-align: left;white-space: normal;line-height: 1.75;letter-spacing: 0.2em;font-size: 15px;word-spacing: 0.1em;box-sizing: border-box !important;overflow-wrap: break-word !important;">既然已经可以让生产端100%可靠性投递到RabbitMQ了,那接下来就改看看消费端的了,如何让消费端不丢失消息。</p> <p data-tool="mdnice编辑器" style="margin: 10px;padding-top: 8px;padding-bottom: 8px;outline: 0px;max-width: 100%;color: rgb(0, 0, 0);font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;text-align: left;white-space: normal;line-height: 1.75;letter-spacing: 0.2em;font-size: 15px;word-spacing: 0.1em;box-sizing: border-box !important;overflow-wrap: break-word !important;">默认情况下,以下3种情况会导致消息丢失:</p> <ul data-tool="mdnice编辑器" class="list-paddingleft-1" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;outline: 0px;max-width: 100%;color: rgb(0, 0, 0);font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;font-size: 16px;text-align: left;white-space: normal;overflow-wrap: break-word !important;"> <li style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <section style="margin-top: 5px;margin-bottom: 5px;outline: 0px;max-width: 100%;line-height: 26px;color: rgb(1, 1, 1);font-size: 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;"> 在RabbitMQ将消息发出后,消费端还没接收到消息之前,发生网络故障,消费端与RabbitMQ断开连接,此时消息会丢失; </section></li> <li style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <section style="margin-top: 5px;margin-bottom: 5px;outline: 0px;max-width: 100%;line-height: 26px;color: rgb(1, 1, 1);font-size: 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;"> 在RabbitMQ将消息发出后,消费端还没接收到消息之前,消费端挂了,此时消息会丢失; </section></li> <li style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <section style="margin-top: 5px;margin-bottom: 5px;outline: 0px;max-width: 100%;line-height: 26px;color: rgb(1, 1, 1);font-size: 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;"> 消费端正确接收到消息,但在处理消息的过程中发生异常或宕机了,消息也会丢失。 </section></li> </ul> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;outline: 0px;max-width: 100%;color: rgb(0, 0, 0);font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;font-size: 16px;text-align: left;white-space: normal;display: flex;flex-direction: column;justify-content: center;align-items: center;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <a target="_blank" href="https://mp.weixin.qq.com/s?__biz=MzUzMTA2NTU2Ng==&mid=2247487551&idx=1&sn=18f64ba49f3f0f9d8be9d1fdef8857d9&scene=21#wechat_redirect" textvalue="你已选中了添加链接的内容" linktype="text" imgurl="" imgdata="null" tab="innerlink" data-linktype="1" wah-hotarea="click" style="outline: 0px;-webkit-tap-highlight-color: rgba(0, 0, 0, 0);cursor: pointer;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span class="js_jump_icon h5_image_link" data-positionback="static" style="margin: 20px 41.8542px;outline: 0px;max-width: 100%;line-height: 0;vertical-align: bottom;user-select: none;width: 657px;box-sizing: border-box !important;overflow-wrap: break-word !important;"><img class="rich_pages wxw-img" data-ratio="0.4109985528219971" src="/upload/fa576fc061a5b8ef523fab65637ddabf.png" data-type="png" data-w="691" style="outline: 0px;border-width: 0px;border-style: initial;border-color: initial;border-radius: 0px 0px 5px 5px;display: block;object-fit: contain;box-shadow: rgb(132, 161, 168) 0px 10px 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;visibility: visible !important;width: 558.448px !important;"></span></a> </figure> <p data-tool="mdnice编辑器" style="margin: 10px;padding-top: 8px;padding-bottom: 8px;outline: 0px;max-width: 100%;color: rgb(0, 0, 0);font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;text-align: left;white-space: normal;line-height: 1.75;letter-spacing: 0.2em;font-size: 15px;word-spacing: 0.1em;box-sizing: border-box !important;overflow-wrap: break-word !important;">其实,上述3中情况导致消息丢失归根结底是因为RabbitMQ的自动ack机制,即默认RabbitMQ在消息发出后就立即将这条消息删除,而不管消费端是否接收到,是否处理完,导致消费端消息丢失时RabbitMQ自己又没有这条消息了。</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;outline: 0px;max-width: 100%;color: rgb(0, 0, 0);font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;font-size: 16px;text-align: left;white-space: normal;display: flex;flex-direction: column;justify-content: center;align-items: center;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <a target="_blank" href="https://mp.weixin.qq.com/s?__biz=MzUzMTA2NTU2Ng==&mid=2247487551&idx=1&sn=18f64ba49f3f0f9d8be9d1fdef8857d9&scene=21#wechat_redirect" textvalue="你已选中了添加链接的内容" linktype="text" imgurl="" imgdata="null" tab="innerlink" data-linktype="1" wah-hotarea="click" style="outline: 0px;-webkit-tap-highlight-color: rgba(0, 0, 0, 0);cursor: pointer;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span class="js_jump_icon h5_image_link" data-positionback="static" style="margin: 20px 37.125px;outline: 0px;max-width: 100%;line-height: 0;vertical-align: bottom;user-select: none;width: 569.25px;box-sizing: border-box !important;overflow-wrap: break-word !important;"><img class="rich_pages wxw-img" data-ratio="0.6323232323232323" src="/upload/7e61e4b063bf72b5b076fc595e6a705e.png" data-type="png" data-w="495" style="outline: 0px;border-width: 0px;border-style: initial;border-color: initial;border-radius: 0px 0px 5px 5px;display: block;object-fit: contain;box-shadow: rgb(132, 161, 168) 0px 10px 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;visibility: visible !important;width: 483.854px !important;"></span></a> </figure> <p data-tool="mdnice编辑器" style="margin: 10px;padding-top: 8px;padding-bottom: 8px;outline: 0px;max-width: 100%;color: rgb(0, 0, 0);font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;text-align: left;white-space: normal;line-height: 1.75;letter-spacing: 0.2em;font-size: 15px;word-spacing: 0.1em;box-sizing: border-box !important;overflow-wrap: break-word !important;">所以就需要将自动ack机制改为手动ack机制。</p> <p data-tool="mdnice编辑器" style="margin: 10px;padding-top: 8px;padding-bottom: 8px;outline: 0px;max-width: 100%;color: rgb(0, 0, 0);font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;text-align: left;white-space: normal;line-height: 1.75;letter-spacing: 0.2em;font-size: 15px;word-spacing: 0.1em;box-sizing: border-box !important;overflow-wrap: break-word !important;">消费端手动确认消息:</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;outline: 0px;max-width: 100%;color: rgb(0, 0, 0);font-size: 16px;text-align: left;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="margin-bottom: -7px;outline: 0px;max-width: 100%;display: block;background: url("https://mmbiz.qpic.cn/mmbiz_svg/wXJ5kSJT6ONMdYq9ExLicAvGeAJqWGOCkuicJXZAgsSbOX4ULHIwDThlVECxKnMT8kwIiapGTwZa8zDsnOlacRaDy08ZicyGgkKA/640?wx_fmt=svg") 10px 10px / 40px no-repeat rgb(30, 30, 30);height: 30px;width: 657px;border-radius: 5px;box-sizing: border-box !important;overflow-wrap: break-word !important;"></span><code style="padding: 15px 16px 16px;outline: 0px;max-width: 100%;overflow-x: auto;color: rgb(220, 220, 220);display: -webkit-box;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;font-size: 12px;background: rgb(30, 30, 30);border-radius: 5px;box-sizing: border-box !important;overflow-wrap: break-word !important;">DeliverCallback deliverCallback = (consumerTag, delivery) -> {<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <span style="outline: 0px;max-width: 100%;color: rgb(86, 156, 214);line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">try</span> {<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <span style="outline: 0px;max-width: 100%;color: rgb(87, 166, 74);font-style: italic;line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">//接收到消息,做处理</span><br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <span style="outline: 0px;max-width: 100%;color: rgb(87, 166, 74);font-style: italic;line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">//手动确认</span><br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"> channel.basicAck(delivery.getEnvelope().getDeliveryTag(), <span style="outline: 0px;max-width: 100%;color: rgb(86, 156, 214);line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">false</span>);<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"> } <span style="outline: 0px;max-width: 100%;color: rgb(86, 156, 214);line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">catch</span> (Exception e) {<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <span style="outline: 0px;max-width: 100%;color: rgb(87, 166, 74);font-style: italic;line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">//出错处理,这里可以让消息重回队列重新发送或直接丢弃消息</span><br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"> }<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;">};<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="outline: 0px;max-width: 100%;color: rgb(87, 166, 74);font-style: italic;line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">//第二个参数autoAck设为false表示关闭自动确认机制,需手动确认</span><br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;">channel.basicConsume(QUEUE_NAME, <span style="outline: 0px;max-width: 100%;color: rgb(86, 156, 214);line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">false</span>, deliverCallback, consumerTag -> {});<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"></code></pre> <p data-tool="mdnice编辑器" style="margin: 10px;padding-top: 8px;padding-bottom: 8px;outline: 0px;max-width: 100%;color: rgb(0, 0, 0);font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;text-align: left;white-space: normal;line-height: 1.75;letter-spacing: 0.2em;font-size: 15px;word-spacing: 0.1em;box-sizing: border-box !important;overflow-wrap: break-word !important;">这样,当autoAck参数置为false,对于RabbitMQ服务端而言,队列中的消息分成了两个部分:一部分是等待投递给消费端的消息;一部分是已经投递给消费端,但是还没有收到消费端确认信号的消息。如果RabbitMQ一直没有收到消费端的确认信号,并且消费此消息的消费端已经断开连接或宕机(RabbitMQ会自己感知到),则RabbitMQ会安排该消息重新进入队列(放在队列头部),等待投递给下一个消费者,当然也有能还是原来的那个消费端,当然消费端也需要确保幂等性。</p> <p data-tool="mdnice编辑器" style="margin: 10px;padding-top: 8px;padding-bottom: 8px;outline: 0px;max-width: 100%;color: rgb(0, 0, 0);font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;text-align: left;white-space: normal;line-height: 1.75;letter-spacing: 0.2em;font-size: 15px;word-spacing: 0.1em;box-sizing: border-box !important;overflow-wrap: break-word !important;">好了,到此从生产端到RabbitMQ再到消费端的全链路,就可以保证数据的不丢失。</p> <p data-tool="mdnice编辑器" style="margin: 10px;padding-top: 8px;padding-bottom: 8px;outline: 0px;max-width: 100%;color: rgb(0, 0, 0);font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;text-align: left;white-space: normal;line-height: 1.75;letter-spacing: 0.2em;font-size: 15px;word-spacing: 0.1em;box-sizing: border-box !important;overflow-wrap: break-word !important;">由于个人水平有限,有些地方可能理解错了或理解不到位的,请大家多多指出!Thanks</p> <blockquote style="white-space: normal;"> <p><span style="font-size: 14px;">转自:指尖凉</span></p> <p><span style="font-size: 14px;">链接<span style="color: rgb(154, 154, 154);font-size: 14px;">:https://blog.csdn.net/hsz2568952354/article/details/86559470</span></span></p> </blockquote>