<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>小小冒险家的博客</title>
  
  
  <link href="/atom.xml" rel="self"/>
  
  <link href="https://www.cicoding.cn/"/>
  <updated>2026-03-16T14:39:00.446Z</updated>
  <id>https://www.cicoding.cn/</id>
  
  <author>
    <name>zhaokejin</name>
    
  </author>
  
  <generator uri="http://hexo.io/">Hexo</generator>
  
  <entry>
    <title>Claude Code高级之多层记忆系统</title>
    <link href="https://www.cicoding.cn/ai/claude-code-advanced-multi-layer-memory-system/"/>
    <id>https://www.cicoding.cn/ai/claude-code-advanced-multi-layer-memory-system/</id>
    <published>2025-07-20T13:12:15.000Z</published>
    <updated>2026-03-16T14:39:00.446Z</updated>
    
    <content type="html"><![CDATA[<h1 id="Claude-Code-高级技巧：构建多层记忆系统，打造真正懂你的-AI-编程搭档"><a href="#Claude-Code-高级技巧：构建多层记忆系统，打造真正懂你的-AI-编程搭档" class="headerlink" title="Claude Code 高级技巧：构建多层记忆系统，打造真正懂你的 AI 编程搭档"></a>Claude Code 高级技巧：构建多层记忆系统，打造真正懂你的 AI 编程搭档</h1><p>在 AI 编程助手的演进历程中，一个核心痛点始终存在：<strong>“金鱼记忆”</strong>。早期模型如 ChatGPT，在每次新对话中都仿佛“失忆”，开发者不得不反复重申项目背景、编码规范和特定需求，效率极低。</p><p>Claude Code 通过其革命性的 <strong>多层记忆系统</strong>，彻底解决了这一问题。它不再只是一个代码生成器，而是能跨会话、跨目录、甚至跨平台理解并记住你工作上下文的<strong>智能协作者</strong>。本文将深入解析这套系统，并通过一个完整的 Flutter 跨平台开发案例，手把手教你如何构建属于自己的高效记忆体系。</p><hr><h2 id="一、为什么需要多层记忆？从“金鱼”到“专家”"><a href="#一、为什么需要多层记忆？从“金鱼”到“专家”" class="headerlink" title="一、为什么需要多层记忆？从“金鱼”到“专家”"></a>一、为什么需要多层记忆？从“金鱼”到“专家”</h2><p>想象一下这个场景：</p><blockquote><p>你在 <code>lib/</code> 目录下用 Dart 写 Flutter 逻辑，需要遵循函数式编程和 Provider 状态管理；但当你进入 <code>android/</code> 目录修改原生 Kotlin 代码时，又必须遵守 Android 的 <code>minSdkVersion</code> 和 <code>strings.xml</code> 规范。</p></blockquote><p>如果 AI 助手只有一套全局记忆，它就无法在这两种截然不同的上下文中无缝切换。这就是多层记忆的价值所在——<strong>让 AI 在正确的地点，加载正确的知识</strong>。</p><p>Claude Code 提供三种记忆位置，每种都有不同用途：</p><table><thead><tr><th>记忆类型</th><th>文件位置</th><th>用途说明</th><th>使用示例</th></tr></thead><tbody><tr><td>项目记忆（共享）</td><td><code>./CLAUDE.md</code></td><td>项目团队共享的指令</td><td>项目架构、编码规范、常用工作流程</td></tr><tr><td>用户记忆（全局）</td><td><code>~/.claude/CLAUDE.md</code></td><td>用于所有项目的个人偏好设置</td><td>代码风格偏好、个人工具快捷方式</td></tr><tr><td>项目记忆（本地）</td><td><code>./CLAUDE.local.md</code></td><td>项目的个人偏好设置（已废弃）</td><td>你的沙箱地址、测试数据偏好等</td></tr></tbody></table><p>其中， <code>CLAUDE.md</code> 文件是 Claude Code 自动读取的记忆文件，类似于 Cursor 中 <code>rules</code> 规则文件，但比它要更强大，它可以为 Claude 提供更多项目相关的上下文信息，如：</p><ul><li>常用的 bash 命令</li><li>核心文件和工具函数</li><li>代码风格指南</li><li>测试说明</li><li>代码库规范</li><li>开发环境设置</li><li>更多希望 Claude 记住的信息等等</li></ul><p>当 Claude Code 启动时，以上所有记忆文件会自动加载到运行环境中。</p><p>可以在多个位置放置 <code>CLAUDE.md</code> 文件，Claude Code 会递归读取这些文件，从当前工作目录开始，向上递归到根目录，读取找到的任何 <code>CLAUDE.md</code> 文件。</p><h3 id="Claude-Code-的四层记忆架构"><a href="#Claude-Code-的四层记忆架构" class="headerlink" title="Claude Code 的四层记忆架构"></a>Claude Code 的四层记忆架构</h3><p>Claude Code 的记忆系统采用分层设计，优先级从高到低依次为：</p><ol><li><strong>目录级记忆 (<code>./components/CLAUDE.md</code>)</strong>：最精细的控制，适用于特定模块（如 React 组件库、Android 原生模块）。</li><li><strong>项目级记忆 (<code>./CLAUDE.md</code>)</strong>：最常用层级，包含整个项目的架构、技术栈、测试命令等核心信息。</li><li><strong>个人全局记忆 (<code>~/.claude/CLAUDE.md</code>)</strong>：你的个人编码偏好，适用于所有项目。</li><li><strong>企业级记忆 (<code>/Library/.../CLAUDE.md</code>)</strong>：团队统一的安全策略和 CI/CD 流程（由管理员维护）。</li></ol><blockquote><p><strong>关键原则</strong>：越具体，优先级越高。当 Claude 在 <code>android/</code> 目录工作时，它会自动加载 <code>项目根目录/CLAUDE.md</code> + <code>android/CLAUDE.md</code>，而忽略 <code>ios/</code> 目录下的规则。</p></blockquote><p><img src="/images/ai/image-20260123142422668.png" alt="Claude Code 四层记忆架构图"></p><p><em>图：Claude Code 的四层记忆加载策略。当处理 <code>Button.tsx</code> 文件时，系统只会加载与之相关的记忆文件，确保上下文精准且高效。</em></p><hr><h2 id="二、核心载体：CLAUDE-md-文件详解"><a href="#二、核心载体：CLAUDE-md-文件详解" class="headerlink" title="二、核心载体：CLAUDE.md 文件详解"></a>二、核心载体：<code>CLAUDE.md</code> 文件详解</h2><p><code>CLAUDE.md</code> 是 Claude Code 记忆系统的基石。它是一个纯文本 Markdown 文件，格式自由，但内容至关重要。以下是你可以写入的关键信息：</p><ul><li><strong>代码风格指南</strong>：缩进、模块语法、命名规范。</li><li><strong>常用命令</strong>：<code>npm run test:unit</code>, <code>flutter build</code>。</li><li><strong>核心文件路径</strong>：日志工具、配置文件位置。</li><li><strong>Git 工作流</strong>：分支命名、提交信息格式。</li><li><strong>开发环境</strong>：Node.js/Python 版本、必需工具。</li><li><strong>避坑指南</strong>：已知问题、性能陷阱、安全警告。</li></ul><h3 id="实战技巧：动态管理记忆"><a href="#实战技巧：动态管理记忆" class="headerlink" title="实战技巧：动态管理记忆"></a>实战技巧：动态管理记忆</h3><ul><li><strong><code>#</code> 符号快速记录</strong>：在聊天中随时输入 <code># 使用 TypeScript strict mode</code>，Claude 会提示你将其保存到 <code>CLAUDE.md</code> 中。</li><li><strong><code>/memory</code> 查看上下文</strong>：随时检查当前加载了哪些记忆文件。</li><li><strong><code>@</code> 模块化导入</strong>：避免文件臃肿，用 <code>@docs/testing.md</code> 引用外部详细文档。</li></ul><hr><h2 id="三、实战案例：为-Flutter-跨平台项目构建记忆系统"><a href="#三、实战案例：为-Flutter-跨平台项目构建记忆系统" class="headerlink" title="三、实战案例：为 Flutter 跨平台项目构建记忆系统"></a>三、实战案例：为 Flutter 跨平台项目构建记忆系统</h2><p>让我们以一个典型的 Flutter 项目为例，演示如何搭建一个多层记忆系统。</p><h3 id="项目结构"><a href="#项目结构" class="headerlink" title="项目结构"></a>项目结构</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">my_flutter_app/</span><br><span class="line">├── android/          # Android 原生代码</span><br><span class="line">├── ios/              # iOS 原生代码</span><br><span class="line">├── lib/              # Dart 核心逻辑</span><br><span class="line">└── CLAUDE.md         # 项目级记忆</span><br></pre></td></tr></table></figure><h3 id="第一步：初始化项目记忆"><a href="#第一步：初始化项目记忆" class="headerlink" title="第一步：初始化项目记忆"></a>第一步：初始化项目记忆</h3><p>在项目根目录运行 <code>/init</code>，Claude 会自动分析代码库，生成一个基础的 <code>CLAUDE.md</code>。</p><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="section"># my_flutter_app 项目指南</span></span><br><span class="line"></span><br><span class="line"><span class="section">## 架构</span></span><br><span class="line"><span class="bullet">- </span>状态管理：所有新功能都使用 <span class="strong">**Provider**</span> 包。</span><br><span class="line"><span class="bullet">- </span>文件结构：按功能分组，放在 <span class="code">`lib/features/`</span> 下。</span><br><span class="line"></span><br><span class="line"><span class="section">## 编码风格</span></span><br><span class="line"><span class="bullet">- </span>遵循 "Effective Dart" 风格指南。</span><br><span class="line"><span class="bullet">- </span>所有 widget 必须放在单独的文件中。</span><br><span class="line"></span><br><span class="line"><span class="section">## 资源</span></span><br><span class="line"><span class="bullet">- </span>图片资源路径：<span class="code">`assets/images/`</span></span><br><span class="line"><span class="bullet">- </span>新增资源必须在 <span class="code">`pubspec.yaml`</span> 中声明。</span><br></pre></td></tr></table></figure><h3 id="第二步：添加平台特定规则"><a href="#第二步：添加平台特定规则" class="headerlink" title="第二步：添加平台特定规则"></a>第二步：添加平台特定规则</h3><p><strong>1. Android 平台 (<code>android/CLAUDE.md</code>)</strong></p><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="section"># Android 平台规范</span></span><br><span class="line"></span><br><span class="line"><span class="bullet">- </span><span class="code">`minSdkVersion`</span> 必须是 <span class="strong">**21**</span>。</span><br><span class="line"><span class="bullet">- </span>所有用户可见字符串必须放在 <span class="code">`res/values/strings.xml`</span>。</span><br><span class="line"><span class="bullet">- </span>添加新权限需在 <span class="code">`AndroidManifest.xml`</span> 中声明。</span><br></pre></td></tr></table></figure><p><strong>2. iOS 平台 (<code>ios/CLAUDE.md</code>)</strong></p><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="section"># iOS 平台规范</span></span><br><span class="line"></span><br><span class="line"><span class="bullet">- </span>部署目标版本：<span class="strong">**iOS 12.0**</span>。</span><br><span class="line"><span class="bullet">- </span>新的隐私权限需在 <span class="code">`Info.plist`</span> 中添加用途描述。</span><br><span class="line"><span class="bullet">- </span>依赖管理：使用 <span class="strong">**CocoaPods**</span>。</span><br></pre></td></tr></table></figure><h3 id="第三步：验证记忆加载"><a href="#第三步：验证记忆加载" class="headerlink" title="第三步：验证记忆加载"></a>第三步：验证记忆加载</h3><ul><li>在项目根目录输入 <code>/memory</code>，你会看到加载了 <code>项目级</code> + <code>个人级</code> 记忆。</li><li>进入 <code>android/</code> 目录再输入 <code>/memory</code>，输出会<strong>额外包含</strong> <code>android/CLAUDE.md</code> 的内容。</li></ul><p><img src="/images/ai/image-20260123142522622.png" alt="在不同目录下 /memory 命令的输出对比"></p><p><em>图：在 <code>android</code> 子目录下，Claude 自动加载了平台特定的记忆，使其能精准地处理原生代码。</em></p><h3 id="第四步：沉淀可复用的工作流"><a href="#第四步：沉淀可复用的工作流" class="headerlink" title="第四步：沉淀可复用的工作流"></a>第四步：沉淀可复用的工作流</h3><p>假设你刚完成一个“获取电池电量”的平台通道（Platform Channel）功能，涉及 Dart、Kotlin、Swift 三端代码。你可以使用 <code>/compact</code> 命令将其提炼为标准操作流程（SOP）。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">/compact 将刚才实现“获取设备电池电量”功能的完整流程总结成一个标准的平台通道 (Platform Channel)实现指南。</span><br></pre></td></tr></table></figure><p>Claude 会生成一份结构化摘要。你可以将其保存为 <code>docs/workflows/platform-channel-guide.md</code>，并在主 <code>CLAUDE.md</code> 中引用：</p><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="section"># 工作流</span></span><br><span class="line"><span class="bullet">- </span>创建新平台通道的指南：@docs/workflows/platform-channel-guide.md</span><br></pre></td></tr></table></figure><p>现在，任何团队成员在需要创建类似功能时，只需一句指令，Claude 就能基于这份权威指南生成高质量代码。</p><hr><h2 id="四、高级技巧与最佳实践"><a href="#四、高级技巧与最佳实践" class="headerlink" title="四、高级技巧与最佳实践"></a>四、高级技巧与最佳实践</h2><ol><li><strong>主动压缩上下文</strong>：不要等到 token 用尽。在对话达到 70% 时主动使用 <code>/compact</code>，清理噪音，聚焦核心。</li><li><strong>成本控制</strong>：对于复杂规划，使用 Opus 模型；对于日常编码和测试，切换到更快更便宜的 Sonnet 或 Haiku。</li><li><strong>安全护栏</strong>：在 <code>.claude/settings.json</code> 中设置 <code>deny</code> 规则，如 <code>Read(./.env)</code>，防止敏感信息泄露。</li><li><strong>团队共享</strong>：将 <code>项目级 CLAUDE.md</code> 提交到 Git 仓库，使其成为团队的“活文档”和知识库。</li></ol><hr><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>Claude Code 的多层记忆系统，本质上是将 <strong>人、项目、智能体</strong> 三者协同的工程方法论。它通过 <code>CLAUDE.md</code> 文件，将模糊的口头约定转化为 AI 可执行的精确指令。</p><p>当你精心构建起这套记忆体系后，Claude Code 就不再是那个需要你反复解释的“新手”，而是一个真正<strong>懂你的项目、懂你的风格、懂你的流程</strong>的资深开发搭档。它能显著减少沟通成本，提升代码一致性，并将你的开发体验从“反复试错”升级为“高效协作”。</p><blockquote><p><strong>行动建议</strong>：立即在你的下一个项目中创建 <code>CLAUDE.md</code> 文件，从记录一条简单的编码规范开始。你会发现，一个拥有“好记性”的 AI，才是你通往高效开发的终极秘密武器。</p></blockquote><blockquote><p>参考:</p><p><a href="https://aicoding.csdn.net/68b38299080e555a88dfba38.html#devmenu2" target="_blank" rel="noopener">https://aicoding.csdn.net/68b38299080e555a88dfba38.html#devmenu2</a> </p><p><a href="https://tianpan.co/zh/blog/2025-08-21-claude-code-tips" target="_blank" rel="noopener">https://tianpan.co/zh/blog/2025-08-21-claude-code-tips</a> </p><p><a href="https://blog.csdn.net/yangshangwei/article/details/153482628" target="_blank" rel="noopener">https://blog.csdn.net/yangshangwei/article/details/153482628</a></p></blockquote>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h1 id=&quot;Claude-Code-高级技巧：构建多层记忆系统，打造真正懂你的-AI-编程搭档&quot;&gt;&lt;a href=&quot;#Claude-Code-高级技巧：构建多层记忆系统，打造真正懂你的-AI-编程搭档&quot; class=&quot;headerlink&quot; title=&quot;Claude Cod
      
    
    </summary>
    
    
      <category term="Claude Code" scheme="https://www.cicoding.cn/categories/Claude-Code/"/>
    
    
      <category term="Claude Code" scheme="https://www.cicoding.cn/tags/Claude-Code/"/>
    
  </entry>
  
  <entry>
    <title>Claude Code配置</title>
    <link href="https://www.cicoding.cn/ai/claude-code-setting/"/>
    <id>https://www.cicoding.cn/ai/claude-code-setting/</id>
    <published>2025-07-19T14:12:15.000Z</published>
    <updated>2026-03-16T14:35:00.538Z</updated>
    
    <content type="html"><![CDATA[<h1 id="Claude-Code配置"><a href="#Claude-Code配置" class="headerlink" title="Claude Code配置"></a>Claude Code配置</h1><ol><li>打开你下载的CC Switch软件，你会看到如下图的初始界面</li></ol><p><img src="/images/ai/image-20260123095907778.png" alt="image-20260123102312096"></p><ol start="2"><li>在分组条中，将分组选择至“Claude”</li></ol><p><img src="/images/ai/image-20260123102312096.png" alt="image-20260123102312096"></p><ol start="3"><li>在供应商分组中，选择如图的“DeepSeek”</li></ol><p><img src="/images/ai/image-20260123102913923.png" alt="image-20260123102913923"></p><ol start="4"><li><a href="https://platform.deepseek.com/api_keys" target="_blank" rel="noopener">创建API令牌</a>，在 DeepSeek中创建 <strong>Claude</strong> 分组的令牌，点击复制按钮，复制ApiKey到剪切板</li></ol><p><img src="/images/ai/image-20260123103045215.png" alt="image-20260123103045215"></p><ol start="5"><li>下拉模态框，找到“API Key”配置项，填入你刚才复制的ApiKey，再点击右下角“添加”按钮</li></ol><p><img src="/images/ai/image-20260123103148486.png" alt="image-20260123103148486"></p><ol start="6"><li>添加成功后，在主界面会看到我们配置的分组，在右侧点击“启用”按钮，显示“使用中”，则配置完成</li></ol><p><img src="/images/ai/image-20260123103247011.png" alt="image-20260123103247011"></p><ol start="7"><li>在终端运行 <code>claude</code>，看到对话界面并能正常回复即表示配置完成</li></ol><p><img src="/images/ai/image-20260123103530668.png" alt="image-20260123103530668"></p><blockquote><p>[!CAUTION]</p><p>先要充值使用</p></blockquote><blockquote><p>[!IMPORTANT]</p><p>Codex、Gemini、OpenCode配置同理！</p></blockquote>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h1 id=&quot;Claude-Code配置&quot;&gt;&lt;a href=&quot;#Claude-Code配置&quot; class=&quot;headerlink&quot; title=&quot;Claude Code配置&quot;&gt;&lt;/a&gt;Claude Code配置&lt;/h1&gt;&lt;ol&gt;
&lt;li&gt;打开你下载的CC Switch软件，你会
      
    
    </summary>
    
    
      <category term="Claude Code" scheme="https://www.cicoding.cn/categories/Claude-Code/"/>
    
    
      <category term="Claude Code" scheme="https://www.cicoding.cn/tags/Claude-Code/"/>
    
  </entry>
  
  <entry>
    <title>CC-Switch介绍</title>
    <link href="https://www.cicoding.cn/ai/cc-switch-use/"/>
    <id>https://www.cicoding.cn/ai/cc-switch-use/</id>
    <published>2025-07-18T14:12:15.000Z</published>
    <updated>2026-03-16T14:33:47.604Z</updated>
    
    <content type="html"><![CDATA[<h3 id="CC-Switch介绍"><a href="#CC-Switch介绍" class="headerlink" title="CC-Switch介绍"></a>CC-Switch介绍</h3><h3 id="Claude-Code-Codex-Gemini-CLI-全方位辅助工具"><a href="#Claude-Code-Codex-Gemini-CLI-全方位辅助工具" class="headerlink" title="Claude Code / Codex / Gemini CLI 全方位辅助工具"></a>Claude Code / Codex / Gemini CLI 全方位辅助工具</h3><p><a href="https://github.com/farion1231/cc-switch/releases" target="_blank" rel="noopener"><img src="https://img.shields.io/badge/version-3.7.1-blue.svg" alt="Version"></a><br><a href="https://github.com/trending/typescript" target="_blank" rel="noopener"><img src="https://img.shields.io/badge/%F0%9F%94%A5_TypeScript_Trending-Daily%20%7C%20Weekly%20%7C%20Monthly-ff6b6b.svg" alt="Trending"></a><br><a href="https://github.com/farion1231/cc-switch/releases" target="_blank" rel="noopener"><img src="https://img.shields.io/badge/platform-Windows%20%7C%20macOS%20%7C%20Linux-lightgrey.svg" alt="Platform"></a><br><a href="https://tauri.app/" target="_blank" rel="noopener"><img src="https://img.shields.io/badge/built%20with-Tauri%202-orange.svg" alt="Built with Tauri"></a><br><a href="https://github.com/farion1231/cc-switch/releases/latest" target="_blank" rel="noopener"><img src="https://img.shields.io/endpoint?url=https://api.pinstudios.net/api/badges/downloads/farion1231/cc-switch/total" alt="Downloads"></a></p><p><a href="https://trendshift.io/repositories/15372" target="_blank" rel="noopener"><img src="https://trendshift.io/api/badge/repositories/15372" alt="farion1231%2Fcc-switch | Trendshift"></a></p><p><a href="https://github.com/farion1231/cc-switch/releases" target="_blank" rel="noopener">更新日志</a> | <a href="https://github.com/farion1231/cc-switch/releases/latest" target="_blank" rel="noopener">下载地址</a></p><p><strong>从供应商切换器到 AI CLI 一体化管理平台</strong></p><p><strong>统一管理 Claude Code、Codex 与 Gemini CLI 的供应商配置、MCP 服务器、Skills 扩展和系统提示词。</strong></p><p>使用 CC-Switch，您可以：</p><ul><li>✅ 一键切换 API 配置 - 在多个 API 提供商之间快速切换</li><li>✅ 可视化配置管理 - 通过图形界面轻松管理所有配置</li><li>✅ 内置 PackyAPI 模板 - 预设了 PackyAPI 的配置模板</li><li>✅ MCP 服务器管理 - 管理 Model Context Protocol 服务器</li><li>✅ 系统托盘快捷操作 - 通过托盘菜单快速切换</li></ul><p>温馨提示</p><p>CC-Switch 已经内置了 PackyAPI 的快捷配置模板，无需手动编辑配置文件！</p><h3 id="软件下载"><a href="#软件下载" class="headerlink" title="软件下载"></a>软件下载</h3><h4 id="Windows"><a href="#Windows" class="headerlink" title="Windows"></a>Windows</h4><ol><li>点击下载链接→<a href="https://github.com/farion1231/cc-switch/releases/latest" target="_blank" rel="noopener">传送门</a>←，进入CC-Switch的Github Release页面</li><li>鼠标滚动到最下方选择适合自己版本的安装包，windows系统推荐下载普通msi后缀的安装包进行安装</li></ol><p><img src="/images/ai/image-20260123095750632.png" alt="image-20260123095750632"></p><ol start="3"><li>安装后运行CC-Switch主程序，界面如下。</li></ol><p><img src="/images/ai/image-20260123095907778.png" alt="image-20260123095907778"></p><h4 id="MacOS"><a href="#MacOS" class="headerlink" title="MacOS"></a>MacOS</h4><ul><li>MacOS安装推荐使用HomeBrew</li><li>开启终端后，分别运行以下命令：</li></ul><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 添加 tap 源</span></span><br><span class="line">brew tap farion1231/ccswitch</span><br><span class="line"></span><br><span class="line"><span class="comment"># 安装 CC-Switch</span></span><br><span class="line">brew install --cask cc-switch</span><br></pre></td></tr></table></figure><ul><li>安装完成后，在“启动台”或“应用程序”文件夹中找到 CC-Switch 并启动。</li></ul><h4 id="Linux"><a href="#Linux" class="headerlink" title="Linux"></a>Linux</h4><blockquote><p>[!CAUTION]</p><p>以下命令中的文件名包含占位符版本号 x.x.x，请访问<a href="https://github.com/farion1231/cc-switch/releases/latest" target="_blank" rel="noopener">GitHub Releases</a> 页面查看最新版本，并替换为实际的版本号和完整文件名</p></blockquote><p>Debian/Ubuntu 系统：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 下载 .deb 包</span></span><br><span class="line">wget https://github.com/farion1231/cc-switch/releases/latest/download/cc-switch_x.x.x_amd64.deb</span><br><span class="line"></span><br><span class="line"><span class="comment"># 安装</span></span><br><span class="line">sudo dpkg -i cc-switch_x.x.x_amd64.deb</span><br></pre></td></tr></table></figure><h3 id="环境检查"><a href="#环境检查" class="headerlink" title="环境检查"></a>环境检查</h3><p>注意</p><p><strong>请你最好进行此步的环境检查步骤！！！</strong><br>如果你有经验，能确认你的Nodejs环境以及cc、codex、gemini的cli安装没问题，配置目录也都存在，可以忽略这一步，直接进入后续的CC Switch配置</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h3 id=&quot;CC-Switch介绍&quot;&gt;&lt;a href=&quot;#CC-Switch介绍&quot; class=&quot;headerlink&quot; title=&quot;CC-Switch介绍&quot;&gt;&lt;/a&gt;CC-Switch介绍&lt;/h3&gt;&lt;h3 id=&quot;Claude-Code-Codex-Gemini-CLI-
      
    
    </summary>
    
    
      <category term="Claude Code" scheme="https://www.cicoding.cn/categories/Claude-Code/"/>
    
    
      <category term="Claude Code" scheme="https://www.cicoding.cn/tags/Claude-Code/"/>
    
  </entry>
  
  <entry>
    <title>Claude Code API 配置</title>
    <link href="https://www.cicoding.cn/ai/claude-code-api/"/>
    <id>https://www.cicoding.cn/ai/claude-code-api/</id>
    <published>2025-07-17T11:12:15.000Z</published>
    <updated>2026-03-16T03:08:21.356Z</updated>
    
    <content type="html"><![CDATA[<h2 id="Claude-Code-API-配置"><a href="#Claude-Code-API-配置" class="headerlink" title="Claude Code API 配置"></a>Claude Code API 配置</h2><p>Claude 在国内用，API 其实不是很友好，除了 Claude 官方的模型，我能用其他 AI 模型吗？</p><p>本章节会详细教你如何配置多个国内主流 AI 模型的 API，让 Claude Code 支持：</p><table><thead><tr><th align="left">厂商/品牌</th><th align="left">简介</th><th align="left">API 申请入口（点击即达）</th></tr></thead><tbody><tr><td align="left">DeepSeek（国产高性价比）</td><td align="left">官方模型：deepseek-chat / deepseek-reasoner</td><td align="left"><a href="https://platform.deepseek.com/api_keys" target="_blank" rel="noopener">https://platform.deepseek.com/api_keys</a></td></tr><tr><td align="left">阿里百炼（通义千问）</td><td align="left">阿里云大模型统一入口，支持 qwen-max、qwen-turbo 等</td><td align="left"><a href="https://bailian.console.aliyun.com/" target="_blank" rel="noopener">https://bailian.console.aliyun.com</a></td></tr><tr><td align="left">GLM（智谱清言）</td><td align="left">清华系 ChatGLM 系列，支持 GLM-4、GLM-3-Turbo 等</td><td align="left"><a href="https://www.bigmodel.cn/glm-coding?ic=EMWK7IPUCE" target="_blank" rel="noopener">https://open.bigmodel.cn</a></td></tr><tr><td align="left">MiniMax</td><td align="left">国产多模态，支持文本、语音、图像混合调用</td><td align="left"><a href="https://platform.minimaxi.com/" target="_blank" rel="noopener">https://platform.minimaxi.com</a></td></tr></tbody></table><p>进入对应控制台后，注册/登录 → 完成实名认证 → 创建 API Key 即可开始调用。</p><h3 id="API-管理工具"><a href="#API-管理工具" class="headerlink" title="API 管理工具"></a>API 管理工具</h3><p>平台一多，配置起来就麻烦，我们可以使用第三方工具 CC Switch 可以帮我们轻松管理这几个热门工具的 API 配置：<a href="https://github.com/farion1231/cc-switch/，Windows" target="_blank" rel="noopener">https://github.com/farion1231/cc-switch/，Windows</a> / macOS / Linux 全支持。</p><p>CC Switch 是一个 Claude Code / Codex / Gemini CLI 的全方位辅助工具。</p><p>CC Switch 可以帮我们轻松管理这几个热门工具的 API 配置，就好比给你的开发工具箱来了个智能整理助手，所有工具的配置都能在它这有序管理。</p><p><img src="/images/ai/ScreenShot_2026-03-16_105815_104.png" alt="Claude Code API 配置"></p><p>各平台安装包下载地址：<a href="https://github.com/farion1231/cc-switch/releases。" target="_blank" rel="noopener">https://github.com/farion1231/cc-switch/releases。</a></p><p><img src="/images/ai/claude-code-runoob2.png" alt="Claude Code API 配置"></p><p>具体的操作设置参考文章：<a href="https://mp.weixin.qq.com/s/pa9Gpim7jVXsUtEM8JwnSg" target="_blank" rel="noopener">https://mp.weixin.qq.com/s/pa9Gpim7jVXsUtEM8JwnSg</a></p><p>如果你不闲麻烦，可以参照下文，自行配置。</p><h2 id="DeepSeek-接入-Claude-Code"><a href="#DeepSeek-接入-Claude-Code" class="headerlink" title="DeepSeek 接入 Claude Code"></a>DeepSeek 接入 Claude Code</h2><h3 id="环境变量中配置"><a href="#环境变量中配置" class="headerlink" title="环境变量中配置"></a>环境变量中配置</h3><p>安装 Claude Code 后，我们在终端中设置以下环境变量：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">export ANTHROPIC_BASE_URL=https://api.deepseek.com/anthropic</span><br><span class="line">export ANTHROPIC_AUTH_TOKEN=$&#123;DEEPSEEK_API_KEY&#125; # 这里记得设置你申请的 API key</span><br><span class="line">export API_TIMEOUT_MS=600000</span><br><span class="line">export ANTHROPIC_MODEL=deepseek-chat</span><br><span class="line">export ANTHROPIC_SMALL_FAST_MODEL=deepseek-chat</span><br><span class="line">export CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1</span><br></pre></td></tr></table></figure><p><strong>参数说明：</strong></p><ul><li><code>API_TIMEOUT_MS=600000</code>：设置 10 分钟超时，防止输出过长触发客户端超时</li><li><code>ANTHROPIC_MODEL</code>：指定使用 deepseek-chat 模型</li><li><code>CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1</code>：禁用非必要流量</li></ul><p>然后进入项目目录，执行 claude 命令，即可开始使用了。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">cd my-project</span><br><span class="line">claude</span><br></pre></td></tr></table></figure><p><img src="/images/ai/aebe4b0f-78f1-40fc-87ce-05f889af4c8e.png" alt="Claude Code API 配置"></p><p>启动 Claude Code 后，指定使用 DeepSeek：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">claude --model deepseek-chat</span><br></pre></td></tr></table></figure><p>或在交互模式中切换：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&gt; /model deepseek-chat</span><br></pre></td></tr></table></figure><p>输入 /model 可以查看支持的模型：</p><p><img src="/images/ai/ea32a2cb-aa50-4f3b-870a-1aa7a1f47211.png" alt="Claude Code API 配置"></p><p>参考文档：<a href="https://api-docs.deepseek.com/zh-cn/guides/anthropic_api" target="_blank" rel="noopener">https://api-docs.deepseek.com/zh-cn/guides/anthropic_api</a></p><hr><h2 id="阿里百炼接入-Claude-Code"><a href="#阿里百炼接入-Claude-Code" class="headerlink" title="阿里百炼接入 Claude Code"></a>阿里百炼接入 Claude Code</h2><p>阿里云百炼的通义千问系列模型支持 Anthropic API 兼容接口，通过修改以下参数，即可在 Claude Code 中调用通义千问系列模型。</p><ul><li>ANTHROPIC_API_KEY（或 ANTHROPIC_AUTH_TOKEN）：替换为百炼 API Key，申请地址：<a href="https://bailian.console.aliyun.com/cn-beijing/?tab=model#/api-key。" target="_blank" rel="noopener">https://bailian.console.aliyun.com/cn-beijing/?tab=model#/api-key。</a></li><li>ANTHROPIC_BASE_URL：替换为百炼的兼容端点地址 <strong><a href="https://dashscope.aliyuncs.com/apps/anthropic" target="_blank" rel="noopener">https://dashscope.aliyuncs.com/apps/anthropic</a></strong>。</li><li>模型名称：替换为百炼支持的模型名称（例如 qwen3-max、qwen3-coder-plus等）</li></ul><p><strong>macOS：</strong></p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"># 用百炼 API KEY 替换 YOUR_DASHSCOPE_API_KEY</span><br><span class="line">echo &apos;export ANTHROPIC_BASE_URL=&quot;https://dashscope.aliyuncs.com/apps/anthropic&quot;&apos; &gt;&gt; ~/.zshrc</span><br><span class="line">echo &apos;export ANTHROPIC_API_KEY=&quot;YOUR_DASHSCOPE_API_KEY&quot;&apos; &gt;&gt; ~/.zshrc</span><br></pre></td></tr></table></figure><p>配置完成后需要重新加载配置文件：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">source ~/.zshrc</span><br></pre></td></tr></table></figure><p><strong>Windows：</strong></p><p>在 CMD 中运行以下命令，设置环境变量：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"># 用百炼 API Key 替换 YOUR_DASHSCOPE_API_KEY</span><br><span class="line">setx ANTHROPIC_API_KEY &quot;YOUR_DASHSCOPE_API_KEY&quot;</span><br><span class="line">setx ANTHROPIC_BASE_URL &quot;https://dashscope.aliyuncs.com/apps/anthropic&quot;</span><br></pre></td></tr></table></figure><p>在 PowerShell 中运行以下命令，设置环境变量：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"># 用百炼 API Key 替换 YOUR_DASHSCOPE_API_KEY</span><br><span class="line">[Environment]::SetEnvironmentVariable(&quot;ANTHROPIC_API_KEY&quot;, &quot;YOUR_DASHSCOPE_API_KEY&quot;, [EnvironmentVariableTarget]::User)</span><br><span class="line">[Environment]::SetEnvironmentVariable(&quot;ANTHROPIC_BASE_URL&quot;, &quot;https://dashscope.aliyuncs.com/apps/anthropic&quot;, [EnvironmentVariableTarget]::User)</span><br></pre></td></tr></table></figure><h3 id="在-Claude-Code-中接入通义千问系列模型"><a href="#在-Claude-Code-中接入通义千问系列模型" class="headerlink" title="在 Claude Code 中接入通义千问系列模型"></a>在 Claude Code 中接入通义千问系列模型</h3><p>对话期间，执行 <strong>/model &lt;模型名称&gt;</strong> 命令切换模型。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">/model qwen3-coder-plus</span><br></pre></td></tr></table></figure><p>也可以在项目根目录创建 <strong>.claude/settings.json</strong> 文件中，并写入模型配置信息永久配置。</p><p><img src="/images/ai/6a97af64-c4a5-425b-99a0-3aaf9dc59dbd.png" alt="Claude Code API 配置"></p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  &quot;env&quot;: &#123;</span><br><span class="line">    &quot;ANTHROPIC_MODEL&quot;: &quot;qwen3-coder-plus&quot;,</span><br><span class="line">    &quot;ANTHROPIC_SMALL_FAST_MODEL&quot;: &quot;qwen-flash&quot;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>启动 Claude，可以看到配置信息：</p><p><img src="/images/ai/d811c5e3-f30f-4c1b-9107-774db34b330c.png" alt="Claude Code API 配置"></p><hr><h2 id="智谱大模型接入-Claude-Code"><a href="#智谱大模型接入-Claude-Code" class="headerlink" title="智谱大模型接入 Claude Code"></a>智谱大模型接入 Claude Code</h2><p>这部分我们使用 ~/.claude/settings.json 文件来配置大模型，开始前需要到官方平台获取 API key：<a href="https://www.bigmodel.cn/claude-code?ic=EMWK7IPUCE" target="_blank" rel="noopener">GLM Coding Plan</a>。</p><p>编辑或新增 Claude Code 配置文件 ~/.claude/settings.json ，新增或修改里面的 env 字段</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"># 注意替换里面的your_zhipu_api_key 为您上一步获取到的 API Key</span><br><span class="line">&#123;</span><br><span class="line">    &quot;env&quot;: &#123;</span><br><span class="line">        &quot;ANTHROPIC_AUTH_TOKEN&quot;: &quot;your_zhipu_api_key&quot;,</span><br><span class="line">        &quot;ANTHROPIC_BASE_URL&quot;: &quot;https://open.bigmodel.cn/api/anthropic&quot;,</span><br><span class="line">        &quot;API_TIMEOUT_MS&quot;: &quot;3000000&quot;,</span><br><span class="line">        &quot;CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC&quot;: 1</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>运行 claude 启动 Claude Code，输入 <strong>/status</strong> 确认模型状态：</p><p><img src="/images/ai/5617ff6d-7997-4819-a2eb-300346548c61.png" alt="Claude Code API 配置"></p><p>如果不是可以输入 <strong>/config</strong> 来切换模型。</p><p><img src="/images/ai/98fa9dc0-efab-4afc-a100-350ad114937c.png" alt="Claude Code API 配置"></p><hr><h2 id="VS-Code-安装-Claude-Code"><a href="#VS-Code-安装-Claude-Code" class="headerlink" title="VS Code 安装 Claude Code"></a>VS Code 安装 Claude Code</h2><p>如果不喜欢使用 Claude Code 的命令行模型，我们可以在 VS Code 编辑器中安装 Claude Code。</p><p>打开 VS Code，进入扩展市场，搜索 <strong>Claude Code</strong> 安装：</p><p><img src="/images/ai/cc-cicoding-1.png" alt="Claude Code API 配置"></p><p>安装完成后，点击右上角 Claude Code 图标，即可进入 Claude Code 页面：</p><p><img src="/images/ai/cc-cicoding-2.png" alt="Claude Code API 配置"></p><p>这样有账号的可以使用 /login 登录：</p><p><img src="/images/ai/cc-cicoding-3.png" alt="Claude Code API 配置"></p><p>也可以在对话框输入 /config 进入设置，勾选 Disable Login Prompt 配置来关闭登录页面:</p><p><img src="/images/ai/cc-cicoding-4.png" alt="Claude Code API 配置"></p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h2 id=&quot;Claude-Code-API-配置&quot;&gt;&lt;a href=&quot;#Claude-Code-API-配置&quot; class=&quot;headerlink&quot; title=&quot;Claude Code API 配置&quot;&gt;&lt;/a&gt;Claude Code API 配置&lt;/h2&gt;&lt;p&gt;Claude
      
    
    </summary>
    
    
      <category term="Claude Code" scheme="https://www.cicoding.cn/categories/Claude-Code/"/>
    
    
      <category term="Claude Code" scheme="https://www.cicoding.cn/tags/Claude-Code/"/>
    
  </entry>
  
  <entry>
    <title>Claude Code安装及配置</title>
    <link href="https://www.cicoding.cn/ai/claude-code-install/"/>
    <id>https://www.cicoding.cn/ai/claude-code-install/</id>
    <published>2025-07-16T11:12:15.000Z</published>
    <updated>2026-03-16T02:54:27.463Z</updated>
    
    <content type="html"><![CDATA[<h2 id="Claude-Code安装及配置"><a href="#Claude-Code安装及配置" class="headerlink" title="Claude Code安装及配置"></a>Claude Code安装及配置</h2><p>在正式安装之前，我们先了解一下 Claude Code 可以怎么用。</p><p>三种使用方式对比：</p><table><thead><tr><th align="left">方式</th><th align="left">适合人群</th><th align="left">优点</th><th align="left">缺点</th></tr></thead><tbody><tr><td align="left"><strong>Web 端</strong></td><td align="left">完全新手</td><td align="left">无需安装,打开就用</td><td align="left">功能相对有限</td></tr><tr><td align="left"><strong>CLI(命令行)</strong></td><td align="left">有一定基础的开发者</td><td align="left">功能完整,集成度高</td><td align="left">需要熟悉命令行</td></tr><tr><td align="left"><strong>编辑器集成（VS Code / Cursor 等）</strong></td><td align="left">日常开发</td><td align="left">无缝融入工作流</td><td align="left">依赖插件和环境配置</td></tr></tbody></table><p><strong>我的建议:</strong></p><ul><li>如果你是<strong>完全新手</strong>，先用 <strong>Web 端 <a href="https://claude.ai/" target="_blank" rel="noopener">https://claude.ai/</a></strong> 试试手感</li><li>如果你想<strong>真正用于开发</strong>，直接学 <strong>CLI(命令行)</strong></li><li>如果你已经熟练使用，再考虑<strong>编辑器集成</strong></li></ul><p>目前 Claude Code 的桌面版也发布了，下载地址：<a href="https://claude.com/download。" target="_blank" rel="noopener">https://claude.com/download。</a></p><p><img src="/images/ai/QQ20260316-105357.png" alt="Claude Code安装及配置"></p><p>本教程将以 CLI 使用方式为主，因为它最稳定、最通用，也最接近 Claude Code 的设计初衷。</p><h2 id="安装-Claude-Code-CLI-命令行工具"><a href="#安装-Claude-Code-CLI-命令行工具" class="headerlink" title="安装 Claude Code CLI(命令行工具)"></a>安装 Claude Code CLI(命令行工具)</h2><h3 id="前置准备"><a href="#前置准备" class="headerlink" title="前置准备"></a>前置准备</h3><p>在安装之前,你需要确保:</p><h4 id="1、你有一个-Claude-账号，当然现在国内很多厂家（DeepSeek-Minimax-GLM）已经适配，后文会介绍怎么切换。"><a href="#1、你有一个-Claude-账号，当然现在国内很多厂家（DeepSeek-Minimax-GLM）已经适配，后文会介绍怎么切换。" class="headerlink" title="1、你有一个 Claude 账号，当然现在国内很多厂家（DeepSeek/Minimax/GLM）已经适配，后文会介绍怎么切换。"></a>1、你有一个 Claude 账号，当然现在国内很多厂家（DeepSeek/Minimax/GLM）已经适配，后文会介绍怎么切换。</h4><ul><li>访问 <a href="https://claude.ai/" target="_blank" rel="noopener">claude.ai</a> 注册账号</li><li>如果你已经在用 Claude 聊天，那就已经有账号了</li></ul><h4 id="你的电脑有命令行工具"><a href="#你的电脑有命令行工具" class="headerlink" title="你的电脑有命令行工具"></a>你的电脑有命令行工具</h4><ul><li><strong>Mac / Linux</strong>:打开 Terminal(终端)</li><li><strong>Windows</strong>:打开 PowerShell 或者安装 WSL</li></ul><h3 id="使用官方脚本安装"><a href="#使用官方脚本安装" class="headerlink" title="使用官方脚本安装"></a>使用官方脚本安装</h3><p>macOS, Linux, WSL:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">curl -fsSL https://claude.ai/install.sh | bash</span><br></pre></td></tr></table></figure><p>Windows PowerShell:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">irm https://claude.ai/install.ps1 | iex</span><br></pre></td></tr></table></figure><p>Windows CMD:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">curl -fsSL https://claude.ai/install.cmd -o install.cmd &amp;&amp; install.cmd &amp;&amp; del install.cmd</span><br></pre></td></tr></table></figure><p><strong>安装完成后,验证一下:</strong></p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">claude --version</span><br></pre></td></tr></table></figure><p>如果显示版本号，说明安装成功!</p><h3 id="使用-npm-安装"><a href="#使用-npm-安装" class="headerlink" title="使用 npm 安装"></a>使用 npm 安装</h3><p>你的电脑需要安装了 Node.js，在命令行输入：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">node --version</span><br></pre></td></tr></table></figure><p>如果显示版本号(比如 <code>v18.17.0</code>)，说明已安装。如果没有，去 <a href="https://nodejs.org/" target="_blank" rel="noopener">nodejs.org</a> 下载安装。</p><h3 id="安装-Claude-Code"><a href="#安装-Claude-Code" class="headerlink" title="安装 Claude Code"></a>安装 Claude Code</h3><p>打开命令行，输入以下命令:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm install -g @anthropic-ai/claude-code</span><br></pre></td></tr></table></figure><p>等待安装完成(可能需要几分钟)。</p><p><strong>安装完成后，验证一下:</strong></p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">claude --version</span><br></pre></td></tr></table></figure><p>如果显示版本号,说明安装成功!</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h2 id=&quot;Claude-Code安装及配置&quot;&gt;&lt;a href=&quot;#Claude-Code安装及配置&quot; class=&quot;headerlink&quot; title=&quot;Claude Code安装及配置&quot;&gt;&lt;/a&gt;Claude Code安装及配置&lt;/h2&gt;&lt;p&gt;在正式安装之前，我们先了解一
      
    
    </summary>
    
    
      <category term="Claude Code" scheme="https://www.cicoding.cn/categories/Claude-Code/"/>
    
    
      <category term="Claude Code" scheme="https://www.cicoding.cn/tags/Claude-Code/"/>
    
  </entry>
  
  <entry>
    <title>Claude Code介绍</title>
    <link href="https://www.cicoding.cn/ai/claude-code-Introduction/"/>
    <id>https://www.cicoding.cn/ai/claude-code-Introduction/</id>
    <published>2025-07-15T11:50:15.000Z</published>
    <updated>2026-03-16T02:49:03.508Z</updated>
    
    <content type="html"><![CDATA[<h2 id="Claude-Code介绍"><a href="#Claude-Code介绍" class="headerlink" title="Claude Code介绍"></a>Claude Code介绍</h2><p>Claude Code 是 Anthropic 推出的面向开发者的 AI 编程协作工具，与在聊天窗口里写几段代码不同，Claude Code 的核心目标是理解你的整个项目，并参与到真实的编码、修改和重构过程中。</p><p>Claude Code 不是一个代码生成器，而是一个能读项目、懂上下文、遵守约束的 AI 编程搭档。</p><p>简单说:<strong>Claude Code 是 Claude 的命令行版本,专门为编程场景设计</strong>。</p><p>它不是网页里的聊天框，而是直接在你的终端(Terminal)里运行,可以:</p><ul><li><strong>读取你整个项目的代码</strong></li><li><strong>理解文件之间的关系</strong></li><li><strong>直接修改代码文件</strong></li><li><strong>执行你的指令并给出建议</strong></li></ul><p><img src="/images/ai/Claude-Code-Screenshot.png" alt="Claude Code"></p><p>从能力角度看，Claude Code 主要具备三点特征：</p><ul><li><strong>上下文感知</strong>：不仅理解单个文件，而是理解整个项目结构</li><li><strong>工程化导向</strong>：关注可维护性、规范、测试，而不是一次性代码</li><li><strong>可定制行为</strong>：通过 Skills（技能包）让 AI 遵守你的规则</li></ul><p>打个比方:</p><ul><li><strong>Claude(网页版)</strong> 就像一个顾问，你把代码截图或复制给他，他给你建议，但你得自己动手改。</li><li><strong>Claude Code</strong> 就像一个坐在你旁边的搭档，他能看到你的整个项目，帮你改代码，甚至帮你写测试、重构函数。</li></ul><p><img src="/images/ai/1757591151865.jpg" alt="Claude Code"></p><hr><h2 id="Claude-Code-能做什么"><a href="#Claude-Code-能做什么" class="headerlink" title="Claude Code 能做什么?"></a>Claude Code 能做什么?</h2><p>Claude Code 的核心能力可以归纳为这几点:</p><h3 id="1、代码理解与解释"><a href="#1、代码理解与解释" class="headerlink" title="1、代码理解与解释"></a>1、代码理解与解释</h3><p>我们可以问它:</p><ul><li>这个函数是干什么的?</li><li>为什么这里会报错?</li><li>这段代码的性能瓶颈在哪?</li></ul><p>它会结合你的项目上下文给出解释。</p><h3 id="2、多文件上下文分析"><a href="#2、多文件上下文分析" class="headerlink" title="2、多文件上下文分析"></a>2、多文件上下文分析</h3><p>不同于简单的代码补全工具,Claude Code 能理解:</p><ul><li>这个函数在哪些地方被调用了</li><li>这个模块依赖了哪些其他文件</li><li>整个项目的架构是怎样的</li></ul><h3 id="3、工程级代码修改"><a href="#3、工程级代码修改" class="headerlink" title="3、工程级代码修改"></a>3、工程级代码修改</h3><p>我们可以说:</p><ul><li>把所有用 var 的地方改成 let</li><li>把这个函数拆成三个小函数</li><li>给所有接口加上错误处理</li></ul><p>它会帮你实际修改代码,而不只是给建议。</p><h3 id="4、通过-Skills-技能包-扩展能力"><a href="#4、通过-Skills-技能包-扩展能力" class="headerlink" title="4、通过 Skills(技能包)扩展能力"></a>4、通过 Skills(技能包)扩展能力</h3><p>Skills 是 Claude Code 最强大的地方——我们可以<strong>教它你的编码习惯和团队规范</strong>，让它按我们的方式工作。</p><p>比如:</p><ul><li>我们团队要求所有函数都加注释</li><li>API 响应必须符合特定格式</li><li>测试用例要覆盖边界情况</li></ul><p>你可以把这些要求写成 Skills，Claude Code 就会自动遵守。</p><h3 id="Claude-Code-不能做什么"><a href="#Claude-Code-不能做什么" class="headerlink" title="Claude Code 不能做什么?"></a>Claude Code <strong>不能</strong>做什么?</h3><p>Claude Code 不擅长的事情：</p><ul><li>不能替你做技术决策的最终判断</li><li>不能保证生成代码 100% 无 Bug</li><li>不能理解你没有明确说明的业务语义</li><li>不适合在你完全不理解的情况下全自动接管项目</li></ul><blockquote><p>一个成熟的使用心态是：让 Claude Code 提供高质量候选方案，而不是绝对正确答案。</p></blockquote><hr><h2 id="Claude-Code-的核心价值"><a href="#Claude-Code-的核心价值" class="headerlink" title="Claude Code 的核心价值"></a>Claude Code 的核心价值</h2><h3 id="Claude-Code-的核心理念：协作，而不是替代"><a href="#Claude-Code-的核心理念：协作，而不是替代" class="headerlink" title="Claude Code 的核心理念：协作，而不是替代"></a>Claude Code 的核心理念：协作，而不是替代</h3><p>Claude Code 并不是在追求把开发者踢出局，它的设计理念非常明确：</p><ul><li><strong>人负责：</strong>目标、约束、判断、审美</li><li><strong>AI 负责：</strong>执行、分析、对比、重复劳动</li></ul><h3 id="对编程新手-降低学习门槛"><a href="#对编程新手-降低学习门槛" class="headerlink" title="对编程新手: 降低学习门槛"></a>对编程新手: 降低学习门槛</h3><p>如果你是新手，最痛苦的可能是:</p><ul><li>看不懂别人的代码</li><li>不知道为什么报错</li><li>不知道好代码长什么样</li></ul><p>Claude Code 可以:</p><ul><li><strong>用人话解释代码</strong>:这段代码的意思是先检查用户是否登录，如果没登录就跳转到登录页</li><li><strong>帮你 Debug</strong>: 这里报错是因为变量名拼错了，应该是 <code>username</code> 不是 <code>usrname</code></li><li><strong>教你写更好的代码</strong>: 这个函数太长了，可以拆成三个小函数，分别负责验证、处理、返回</li></ul><hr><h2 id="对独立开发者-提升开发效率"><a href="#对独立开发者-提升开发效率" class="headerlink" title="对独立开发者: 提升开发效率"></a>对独立开发者: 提升开发效率</h2><p>如果你是独立开发者，你可能:</p><ul><li>一个人负责前后端</li><li>没人帮你 Code Review</li><li>需要快速试错和迭代</li></ul><p>Claude Code 可以:</p><ul><li><strong>帮你快速理解第三方库的用法</strong></li><li><strong>自动生成测试用例</strong></li><li><strong>帮你重构混乱的代码</strong></li></ul><hr><h2 id="对团队-统一规范-降低协作成本"><a href="#对团队-统一规范-降低协作成本" class="headerlink" title="对团队: 统一规范,降低协作成本"></a>对团队: 统一规范,降低协作成本</h2><p>如果你在团队工作，你可能遇到:</p><ul><li>每个人代码风格不一样</li><li>新人上手慢,老是问重复问题</li><li>Code Review 花时间</li></ul><p>Claude Code 可以:</p><ul><li><strong>通过 Skills 统一团队规范</strong>(所有人用同一套规则)</li><li><strong>帮新人快速理解项目</strong></li><li><strong>在提交前自动检查代码质量</strong></li></ul><hr><h2 id="Claude-Code-适合哪些人"><a href="#Claude-Code-适合哪些人" class="headerlink" title="Claude Code 适合哪些人?"></a>Claude Code 适合哪些人?</h2><p>我建议以下几类人可以尝试 Claude Code:</p><h3 id="编程新手"><a href="#编程新手" class="headerlink" title="编程新手"></a><strong>编程新手</strong></h3><ul><li>想学编程但总是卡在看不懂代码</li><li>需要一个随时解答问题的老师</li></ul><h3 id="独立开发者-创业者"><a href="#独立开发者-创业者" class="headerlink" title="独立开发者 / 创业者"></a><strong>独立开发者 / 创业者</strong></h3><ul><li>一个人做项目,需要快速试错</li><li>想把时间花在核心功能上,不想在琐碎问题上卡住</li></ul><h3 id="后端-前端-全栈工程师"><a href="#后端-前端-全栈工程师" class="headerlink" title="后端 / 前端 / 全栈工程师"></a><strong>后端 / 前端 / 全栈工程师</strong></h3><ul><li>想提升开发效率</li><li>想学习更好的代码写法</li><li>需要快速理解复杂项目</li></ul><h3 id="技术负责人-架构师"><a href="#技术负责人-架构师" class="headerlink" title="技术负责人 / 架构师"></a><strong>技术负责人 / 架构师</strong></h3><ul><li>想用 AI 辅助团队规范落地</li><li>想降低新人培养成本</li></ul><h3 id="不适合的场景"><a href="#不适合的场景" class="headerlink" title="不适合的场景"></a><strong>不适合的场景</strong></h3><ul><li>你完全不想学编程，只想输入需求自动出项目(AI 还做不到)</li><li>你的工作涉及高度敏感代码，不能使用外部 AI 服务</li></ul><hr><h2 id="Claude-Code-和其他-AI-编程工具有什么区别"><a href="#Claude-Code-和其他-AI-编程工具有什么区别" class="headerlink" title="Claude Code 和其他 AI 编程工具有什么区别?"></a>Claude Code 和其他 AI 编程工具有什么区别?</h2><p>你可能还听说过 ChatGPT、Cursor、GitHub Copilot，它们和 Claude Code 有什么不同?</p><h3 id="Claude-Code-vs-ChatGPT"><a href="#Claude-Code-vs-ChatGPT" class="headerlink" title="Claude Code vs ChatGPT"></a>Claude Code vs ChatGPT</h3><table><thead><tr><th align="left">维度</th><th align="left">ChatGPT</th><th align="left">Claude Code</th></tr></thead><tbody><tr><td align="left"><strong>使用方式</strong></td><td align="left">网页聊天</td><td align="left">命令行工具</td></tr><tr><td align="left"><strong>代码理解</strong></td><td align="left">需要手动粘贴</td><td align="left">自动读取整个项目</td></tr><tr><td align="left"><strong>文件修改</strong></td><td align="left">给建议,你手动改</td><td align="left">可以直接修改文件</td></tr><tr><td align="left"><strong>适合场景</strong></td><td align="left">问问题、学概念</td><td align="left">真实项目开发</td></tr></tbody></table><h3 id="Claude-Code-vs-Cursor-Copilot"><a href="#Claude-Code-vs-Cursor-Copilot" class="headerlink" title="Claude Code vs Cursor / Copilot"></a>Claude Code vs Cursor / Copilot</h3><table><thead><tr><th align="left">维度</th><th align="left">Cursor / Copilot</th><th align="left">Claude Code</th></tr></thead><tbody><tr><td align="left"><strong>工作方式</strong></td><td align="left">编辑器内自动补全</td><td align="left">命令行对话式</td></tr><tr><td align="left"><strong>适合场景</strong></td><td align="left">写代码时实时提示</td><td align="left">理解、重构、架构级修改</td></tr><tr><td align="left"><strong>学习成本</strong></td><td align="left">低,开箱即用</td><td align="left">中,需要学习指令</td></tr></tbody></table><p><strong>简单总结:</strong></p><ul><li><strong>Copilot/Cursor</strong>:边写边补全，像智能输入法</li><li><strong>Claude Code</strong>:对话式协作，像高级搭档</li><li><strong>ChatGPT</strong>:通用助手，像顾问</li></ul><p><strong>Claude Code 的优势:</strong></p><ul><li>对整个项目的理解能力更强</li><li>可以通过 Skills 定制行为</li><li>更适合理解旧代码大规模重构等场景</li></ul><p><strong>Claude Code 的局限:</strong></p><ul><li>不如 Copilot 那样无感(需要主动调用)</li><li>学习曲线稍高</li></ul>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h2 id=&quot;Claude-Code介绍&quot;&gt;&lt;a href=&quot;#Claude-Code介绍&quot; class=&quot;headerlink&quot; title=&quot;Claude Code介绍&quot;&gt;&lt;/a&gt;Claude Code介绍&lt;/h2&gt;&lt;p&gt;Claude Code 是 Anthropic 推出
      
    
    </summary>
    
    
      <category term="Claude Code" scheme="https://www.cicoding.cn/categories/Claude-Code/"/>
    
    
      <category term="Claude Code" scheme="https://www.cicoding.cn/tags/Claude-Code/"/>
    
  </entry>
  
  <entry>
    <title>微服务开中常用注解</title>
    <link href="https://www.cicoding.cn/micro-service/microservice-annotation/"/>
    <id>https://www.cicoding.cn/micro-service/microservice-annotation/</id>
    <published>2023-07-17T02:24:05.000Z</published>
    <updated>2025-03-05T14:22:17.758Z</updated>
    
    <content type="html"><![CDATA[<h2 id="一、组件注册"><a href="#一、组件注册" class="headerlink" title="一、组件注册"></a>一、组件注册</h2><h3 id="SpringBootConfiguration"><a href="#SpringBootConfiguration" class="headerlink" title="@SpringBootConfiguration"></a>@SpringBootConfiguration</h3><p>SpringBootConfiguration 是 SpringBoot 项目的配置注解，这也是一个组合注解，SpringBootConfiguration 注解可以用 java 代码的形式实现 Spring 中 xml 配置文件配置的效果，并会将当前类内声明的一个或多个以 @Bean 注解标记的方法的实例纳入到 spring 容器中，并且实例名就是方法名。</p><p>SpringBootConfiguration 可以作为 Spring 标准中 @Configuration 注解的替代。SpringBoot 项目中推荐使用@SpringBootConfiguration 替代 @Configuration。</p><h3 id="Scope"><a href="#Scope" class="headerlink" title="@Scope"></a>@Scope</h3><p>@Scope注解主要作用是调节Ioc容器中的作用域，在Spring IoC容器中主要有以下五种作用域：基本作用域：singleton(单例)、prototype(多例);Web 作用域(reqeust、session、globalsession)，自定义作用域。</p><p>@Scope注解是springIoc容器中的一个作用域，在 Spring IoC 容器中具有以下几种作用域：基本作用域<strong>singleton（单例）</strong>、<strong>prototype(多例)</strong>，Web 作用域（reqeust、session、globalsession），自定义作用域</p><ul><li><code>singleton</code>单例模式(<strong>默认</strong>):全局有且仅有一个实例</li><li><code>prototype</code>原型模式:每次获取Bean的时候会有一个新的实例</li><li><code>request</code>: request表示该针对每一次HTTP请求都会产生一个新的bean，同时该bean仅在当前HTTP request内有效</li><li><code>session</code> :session作用域表示该针对每一次HTTP请求都会产生一个新的bean，同时该bean仅在当前HTTP session内有效</li><li><code>global session</code> : global session作用域类似于标准的HTTP Session作用域，不过它仅仅在基于portlet的web应用中才有意义</li></ul><p>直接使用字符串容易出问题,spring有默认的参数:</p><ul><li><code>ConfigurableBeanFactory.SCOPE_PROTOTYPE</code>，即“prototype”</li><li><code>ConfigurableBeanFactory.SCOPE_SINGLETON</code>，即“singleton”</li><li><code>WebApplicationContext.SCOPE_REQUEST</code>，即“request”</li><li><code>WebApplicationContext.SCOPE_SESSION</code>，即“session”</li></ul><h2 id="二、常用注解"><a href="#二、常用注解" class="headerlink" title="二、常用注解"></a>二、常用注解</h2><h3 id="SpringBootApplication"><a href="#SpringBootApplication" class="headerlink" title="@SpringBootApplication"></a>@SpringBootApplication</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Target</span>(ElementType.TYPE)</span><br><span class="line"><span class="meta">@Retention</span>(RetentionPolicy.RUNTIME)</span><br><span class="line"><span class="meta">@Documented</span></span><br><span class="line"><span class="meta">@Inherited</span></span><br><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="meta">@EnableAutoConfiguration</span></span><br><span class="line"><span class="meta">@ComponentScan</span></span><br><span class="line"><span class="keyword">public</span> <span class="meta">@interface</span> SpringBootApplication &#123;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Exclude specific auto-configuration classes such that they will never be applied.</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span> the classes to exclude</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line">Class&lt;?&gt;[] exclude() <span class="keyword">default</span> &#123;&#125;;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>定义在main方法入口类处，用于启动sping boot应用项目</p><h3 id="EnableAutoConfiguration"><a href="#EnableAutoConfiguration" class="headerlink" title="@EnableAutoConfiguration"></a>@EnableAutoConfiguration</h3><p>让spring boot根据类路径中的jar包依赖当前项目进行自动配置</p><p>在src/main/resources的META-INF/spring.factories</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">org.springframework.boot.autoconfigure.EnableAutoConfiguration=\</span><br><span class="line">org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\</span><br><span class="line">org.springframework.boot.autoconfigure.aop.AopAutoConfiguration</span><br><span class="line"></span><br><span class="line">若有多个自动配置，用“，”隔开</span><br></pre></td></tr></table></figure><h3 id="ImportResource"><a href="#ImportResource" class="headerlink" title="@ImportResource"></a>@ImportResource</h3><p>加载xml配置，一般是放在启动main类上</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@ImportResource</span>(<span class="string">"classpath*:/spring/*.xml"</span>)  单个</span><br><span class="line"></span><br><span class="line"><span class="meta">@ImportResource</span>(&#123;<span class="string">"classpath*:/spring/1.xml"</span>,<span class="string">"classpath*:/spring/2.xml"</span>&#125;)   多个</span><br></pre></td></tr></table></figure><h3 id="Value"><a href="#Value" class="headerlink" title="@Value"></a>@Value</h3><p>application.properties定义属性，直接使用@Value注入即可</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">A</span></span>&#123;</span><br><span class="line"> <span class="meta">@Value</span>(<span class="string">"$&#123;push.start:0&#125;"</span>)    如果缺失，默认值为<span class="number">0</span></span><br><span class="line">     <span class="keyword">private</span> Long  id;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="ConfigurationProperties"><a href="#ConfigurationProperties" class="headerlink" title="@ConfigurationProperties"></a>@ConfigurationProperties</h3><p>可以新建一个properties文件，ConfigurationProperties的属性prefix指定properties的配置的前缀，通过location指定properties文件的位置</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@ConfigurationProperties</span>(prefix=<span class="string">"person"</span>)</span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">PersonProperties</span> </span>&#123;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> String name ;</span><br><span class="line"><span class="keyword">private</span> <span class="keyword">int</span> age;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="EnableConfigurationProperties"><a href="#EnableConfigurationProperties" class="headerlink" title="@EnableConfigurationProperties"></a>@EnableConfigurationProperties</h3><p>用 @EnableConfigurationProperties注解使 @ConfigurationProperties生效，并从IOC容器中获取bean。</p><p><a href="https://blog.csdn.net/u010502101/article/details/78758330" target="_blank" rel="noopener">https://blog.csdn.net/u010502101/article/details/78758330</a></p><h3 id="RestController"><a href="#RestController" class="headerlink" title="@RestController"></a>@RestController</h3><p>组合@Controller和@ResponseBody，当你开发一个和页面交互数据的控制时，比如bbs-web的api接口需要此注解</p><h3 id="RequestMapping"><a href="#RequestMapping" class="headerlink" title="@RequestMapping"></a>@RequestMapping</h3><p>用来映射web请求(访问路径和参数)、处理类和方法，可以注解在类或方法上。注解在方法上的路径会继承注解在类上的路径。</p><p>produces属性: 定制返回的response的媒体类型和字符集，或需返回值是json对象</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@RequestMapping</span>(value=<span class="string">"/api2/copper"</span>,produces=<span class="string">"application/json;charset=UTF-8"</span>,method = RequestMethod.POST)</span><br></pre></td></tr></table></figure><h3 id="RequestParam"><a href="#RequestParam" class="headerlink" title="@RequestParam"></a>@RequestParam</h3><p>获取request请求的参数值</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> List&lt;CopperVO&gt; <span class="title">getOpList</span><span class="params">(HttpServletRequest request,</span></span></span><br><span class="line"><span class="function"><span class="params">                                   @RequestParam(value = <span class="string">"pageIndex"</span>, required = <span class="keyword">false</span>)</span> Integer pageIndex,</span></span><br><span class="line"><span class="function">                                   @<span class="title">RequestParam</span><span class="params">(value = <span class="string">"pageSize"</span>, required = <span class="keyword">false</span>)</span> Integer pageSize) </span>&#123;</span><br><span class="line"></span><br><span class="line"> &#125;</span><br></pre></td></tr></table></figure><h3 id="ResponseBody"><a href="#ResponseBody" class="headerlink" title="@ResponseBody"></a>@ResponseBody</h3><p>支持将返回值放在response体内，而不是返回一个页面。比如Ajax接口，可以用此注解返回数据而不是页面。此注解可以放置在返回值前或方法前。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">另一个玩法，可以不用<span class="meta">@ResponseBody</span>。</span><br><span class="line">继承FastJsonHttpMessageConverter类并对writeInternal方法扩展，在spring响应结果时，再次拦截、加工结果</span><br><span class="line"><span class="comment">// stringResult: json返回结果</span></span><br><span class="line"><span class="comment">//HttpOutputMessage outputMessage</span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">byte</span>[] payload = stringResult.getBytes();</span><br><span class="line"> outputMessage.getHeaders().setContentType(META_TYPE);</span><br><span class="line"> outputMessage.getHeaders().setContentLength(payload.length);</span><br><span class="line"> outputMessage.getBody().write(payload);</span><br><span class="line"> outputMessage.getBody().flush();</span><br></pre></td></tr></table></figure><h3 id="Bean"><a href="#Bean" class="headerlink" title="@Bean"></a>@Bean</h3><p>@Bean(name=”bean的名字”,initMethod=”初始化时调用方法名字”,destroyMethod=”close”)</p><p>定义在方法上，在容器内初始化一个bean实例类。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Bean</span>(destroyMethod=<span class="string">"close"</span>)</span><br><span class="line"><span class="meta">@ConditionalOnMissingBean</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> PersonService <span class="title">registryService</span><span class="params">()</span> </span>&#123;</span><br><span class="line"><span class="keyword">return</span> <span class="keyword">new</span> PersonService();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="Service"><a href="#Service" class="headerlink" title="@Service"></a>@Service</h3><p>用于标注业务层组件</p><h3 id="Controller"><a href="#Controller" class="headerlink" title="@Controller"></a>@Controller</h3><p>用于标注控制层组件(如struts中的action)</p><h3 id="Repository"><a href="#Repository" class="headerlink" title="@Repository"></a>@Repository</h3><p>用于标注数据访问组件，即DAO组件</p><h3 id="Component"><a href="#Component" class="headerlink" title="@Component"></a>@Component</h3><p>泛指组件，当组件不好归类的时候，我们可以使用这个注解进行标注。</p><h3 id="PostConstruct"><a href="#PostConstruct" class="headerlink" title="@PostConstruct"></a>@PostConstruct</h3><p>spring容器初始化时，要执行该方法</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@PostConstruct</span>  </span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">init</span><span class="params">()</span> </span>&#123;   </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="PathVariable"><a href="#PathVariable" class="headerlink" title="@PathVariable"></a>@PathVariable</h3><p>用来获得请求url中的动态参数</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Controller</span>  </span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">TestController</span> </span>&#123;  </span><br><span class="line"></span><br><span class="line">     <span class="meta">@RequestMapping</span>(value=<span class="string">"/user/&#123;userId&#125;/roles/&#123;roleId&#125;"</span>,method = RequestMethod.GET)  </span><br><span class="line">     <span class="function"><span class="keyword">public</span> String <span class="title">getLogin</span><span class="params">(@PathVariable(<span class="string">"userId"</span>)</span> String userId,  </span></span><br><span class="line"><span class="function">         @<span class="title">PathVariable</span><span class="params">(<span class="string">"roleId"</span>)</span> String roleId)</span>&#123;</span><br><span class="line">           </span><br><span class="line">         System.out.println(<span class="string">"User Id : "</span> + userId);  </span><br><span class="line">         System.out.println(<span class="string">"Role Id : "</span> + roleId);  </span><br><span class="line">         <span class="keyword">return</span> <span class="string">"hello"</span>;  </span><br><span class="line">     </span><br><span class="line">     &#125;  </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="ComponentScan"><a href="#ComponentScan" class="headerlink" title="@ComponentScan"></a>@ComponentScan</h3><p>注解会告知Spring扫描指定的包来初始化Spring</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@ComponentScan</span>(basePackages = <span class="string">"com.bbs.xx"</span>)</span><br></pre></td></tr></table></figure><h3 id="EnableZuulProxy"><a href="#EnableZuulProxy" class="headerlink" title="@EnableZuulProxy"></a>@EnableZuulProxy</h3><p>路由网关的主要目的是为了让所有的微服务对外只有一个接口，我们只需访问一个网关地址，即可由网关将所有的请求代理到不同的服务中。Spring Cloud是通过Zuul来实现的，支持自动路由映射到在Eureka Server上注册的服务。Spring Cloud提供了注解@EnableZuulProxy来启用路由代理。</p><h3 id="Autowired"><a href="#Autowired" class="headerlink" title="@Autowired"></a>@Autowired</h3><p>在默认情况下使用 @Autowired 注释进行自动注入时，Spring 容器中匹配的候选 Bean 数目必须有且仅有一个。当找不到一个匹配的 Bean 时，Spring 容器将抛出 BeanCreationException 异常，并指出必须至少拥有一个匹配的 Bean。</p><p>当不能确定 Spring 容器中一定拥有某个类的 Bean 时，可以在需要自动注入该类 Bean 的地方可以使用 @Autowired(required = false)，这等于告诉 Spring: 在找不到匹配 Bean 时也不报错</p><p><a href="https://blog.csdn.net/ethunsex/article/details/66475792" target="_blank" rel="noopener">@Autowired注解注入map、list与@Qualifier在新窗口打开</a></p><h3 id="Configuration"><a href="#Configuration" class="headerlink" title="@Configuration"></a>@Configuration</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Configuration</span>(<span class="string">"name"</span>)<span class="comment">//表示这是一个配置信息类,可以给这个配置类也起一个名称</span></span><br><span class="line"><span class="meta">@ComponentScan</span>(<span class="string">"spring4"</span>)<span class="comment">//类似于xml中的&lt;context:component-scan base-package="spring4"/&gt;</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Config</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired</span><span class="comment">//自动注入，如果容器中有多个符合的bean时，需要进一步明确</span></span><br><span class="line">    <span class="meta">@Qualifier</span>(<span class="string">"compent"</span>)<span class="comment">//进一步指明注入bean名称为compent的bean</span></span><br><span class="line">    <span class="keyword">private</span> Compent compent;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Bean</span><span class="comment">//类似于xml中的&lt;bean id="newbean" class="spring4.Compent"/&gt;</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> Compent <span class="title">newbean</span><span class="params">()</span></span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> Compent();</span><br><span class="line">    &#125;   </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="Import"><a href="#Import" class="headerlink" title="@Import"></a>@Import</h3><p>导入Config1配置类里实例化的bean</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">CDConfig</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Bean</span>   <span class="comment">// 将SgtPeppers注册为 SpringContext中的bean</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> CompactDisc <span class="title">compactDisc</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> CompactDisc();  <span class="comment">// CompactDisc类型的</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="meta">@Import</span>(CDConfig.class)  <span class="comment">//导入CDConfig的配置</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">CDPlayerConfig</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Bean</span>(name = <span class="string">"cDPlayer"</span>)</span><br><span class="line">    <span class="function"><span class="keyword">public</span> CDPlayer <span class="title">cdPlayer</span><span class="params">(CompactDisc compactDisc)</span> </span>&#123;  </span><br><span class="line">         <span class="comment">// 这里会注入CompactDisc类型的bean</span></span><br><span class="line">         <span class="comment">// 这里注入的这个bean是CDConfig.class中的CompactDisc类型的那个bean</span></span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> CDPlayer(compactDisc);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="Order"><a href="#Order" class="headerlink" title="@Order"></a>@Order</h3><p>@Order(1)，值越小优先级超高，越先运行</p><h2 id="三、条件注解"><a href="#三、条件注解" class="headerlink" title="三、条件注解"></a>三、条件注解</h2><blockquote><p>如果注解指定的<strong>条件成立</strong>，则触发指定<strong>行为</strong>，条件注解列表:</p><p>@ConditionalOnRepositoryType (org.springframework.boot.autoconfigure.data)<br> @ConditionalOnDefaultWebSecurity (org.springframework.boot.autoconfigure.security)<br> @ConditionalOnSingleCandidate (org.springframework.boot.autoconfigure.condition)<br> @ConditionalOnWebApplication (org.springframework.boot.autoconfigure.condition)<br> @ConditionalOnWarDeployment (org.springframework.boot.autoconfigure.condition)<br> @ConditionalOnJndi (org.springframework.boot.autoconfigure.condition)<br> @ConditionalOnResource (org.springframework.boot.autoconfigure.condition)<br> @ConditionalOnExpression (org.springframework.boot.autoconfigure.condition)<br> @ConditionalOnClass (org.springframework.boot.autoconfigure.condition)<br> @ConditionalOnEnabledResourceChain (org.springframework.boot.autoconfigure.web)<br> @ConditionalOnMissingClass (org.springframework.boot.autoconfigure.condition)<br> @ConditionalOnNotWebApplication (org.springframework.boot.autoconfigure.condition)<br> @ConditionalOnProperty (org.springframework.boot.autoconfigure.condition)<br> @ConditionalOnCloudPlatform (org.springframework.boot.autoconfigure.condition)<br> @ConditionalOnBean (org.springframework.boot.autoconfigure.condition)<br> @ConditionalOnMissingBean (org.springframework.boot.autoconfigure.condition)<br> @ConditionalOnMissingFilterBean (org.springframework.boot.autoconfigure.web.servlet)<br> @Profile (org.springframework.context.annotation)<br> @ConditionalOnInitializedRestarter (org.springframework.boot.devtools.restart)<br> @ConditionalOnGraphQlSchema (org.springframework.boot.autoconfigure.graphql)<br> @ConditionalOnJava (org.springframework.boot.autoconfigure.condition)</p></blockquote><h3 id="ConditionalOnMissingClass"><a href="#ConditionalOnMissingClass" class="headerlink" title="@ConditionalOnMissingClass"></a>@ConditionalOnMissingClass</h3><p>如果类路径中不存在这个类，则触发指定行为</p><h3 id="ConditionalOnBean"><a href="#ConditionalOnBean" class="headerlink" title="@ConditionalOnBean"></a>@ConditionalOnBean</h3><p>如果容器中存在这个Bean（组件），则触发指定行为，@ConditionalOnBean（value=组件类型，name=组件名字）：判断容器中是否有这个类型的组件，并且名字是指定的值</p><h3 id="ConditionalOnMissingBean"><a href="#ConditionalOnMissingBean" class="headerlink" title="@ConditionalOnMissingBean"></a>@ConditionalOnMissingBean</h3><p>如果容器中不存在这个Bean（组件），则触发指定行为</p><blockquote><p>场景：</p><p> 如果存在<code>FastsqlException</code>这个类，给容器中放一个<code>Cat</code>组件，名cat01，</p><p> 否则，就给容器中放一个<code>Dog</code>组件，名dog01</p><p> 如果系统中有<code>dog01</code>这个组件，就给容器中放一个 User组件，名zhangsan</p><p> 否则，就放一个User，名叫lisi</p></blockquote><h3 id="ConditionalOnProperty"><a href="#ConditionalOnProperty" class="headerlink" title="@ConditionalOnProperty"></a>@ConditionalOnProperty</h3><p>这个注解能够控制某个 @Configuration 是否生效。具体操作是通过其两个属性name以及havingValue来实现的，其中name用来从application.properties中读取某个属性值，如果该值为空，则返回false;如果值不为空，则将该值与havingValue指定的值进行比较，如果一样则返回true;否则返回false。如果返回值为false，则该configuration不生效；为true则生效。</p><p><a href="https://blog.csdn.net/dalangzhonghangxing/article/details/78420057" target="_blank" rel="noopener">https://blog.csdn.net/dalangzhonghangxing/article/details/78420057</a></p><h3 id="ConditionalOnClass"><a href="#ConditionalOnClass" class="headerlink" title="@ConditionalOnClass"></a>@ConditionalOnClass</h3><p>如果类路径中存在这个类，则触发指定行为；</p><p>该注解的参数对应的类必须存在，否则不解析该注解修饰的配置类；</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="meta">@ConditionalOnClass</span>(&#123;Gson.class&#125;)</span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">GsonAutoConfiguration</span> </span>&#123;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">GsonAutoConfiguration</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="meta">@ConditionalOnMissingBean</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> Gson <span class="title">gson</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> Gson();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="ConditionalOnMisssingClass"><a href="#ConditionalOnMisssingClass" class="headerlink" title="@ConditionalOnMisssingClass"></a>@ConditionalOnMisssingClass</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">@ConditionalOnMisssingClass(&#123;ApplicationManager.class&#125;)</span><br></pre></td></tr></table></figure><p>如果存在它修饰的类的bean，则不需要再创建这个bean；</p><h3 id="ConditionOnMissingBean"><a href="#ConditionOnMissingBean" class="headerlink" title="@ConditionOnMissingBean"></a>@ConditionOnMissingBean</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">@ConditionOnMissingBean(name = &quot;example&quot;)</span><br></pre></td></tr></table></figure><p>表示如果name为“example”的bean存在，该注解修饰的代码块不执行。</p><h3 id="ConditionalOnExpression"><a href="#ConditionalOnExpression" class="headerlink" title="@ConditionalOnExpression"></a>@ConditionalOnExpression</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="meta">@ConditionalOnExpression</span>(<span class="string">"$&#123;enabled:false&#125;"</span>)</span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">BigpipeConfiguration</span> </span>&#123;</span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> OrderMessageMonitor <span class="title">orderMessageMonitor</span><span class="params">(ConfigContext configContext)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> OrderMessageMonitor(configContext);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>开关为true的时候才实例化bean</p><h2 id="四、属性绑定"><a href="#四、属性绑定" class="headerlink" title="四、属性绑定"></a>四、属性绑定</h2><h3 id="ConfigurationProperties-1"><a href="#ConfigurationProperties-1" class="headerlink" title="@ConfigurationProperties"></a>@ConfigurationProperties</h3><p> 声明组件的属性和配置文件哪些前缀开始项进行绑定</p><h3 id="EnableConfigurationProperties-1"><a href="#EnableConfigurationProperties-1" class="headerlink" title="@EnableConfigurationProperties"></a>@EnableConfigurationProperties</h3><p>快速注册注解：</p><ul><li><strong>场景：</strong> SpringBoot默认只扫描自己主程序所在的包。如果导入第三方包，即使组件上标注了 @Component、@ConfigurationProperties 注解，也没用。因为组件都扫描不进来，此时使用这个注解就可以快速进行属性绑定并把组件注册进容器</li></ul><blockquote><p>这个一点倒是很重要，SpringBoot默认扫描当前项目主程序包及其子包，再加上自动配置类，那么属性类是不会扫描到的，此时就算加上@Component注解也是没有任何用处，所以一般是在属性类上面使用 <strong>@ConfigurationProperties注解，而在相应的自动配置类上面使用@EnableConfigurationProperties注解，从而让属性类绑定生效</strong></p></blockquote><ul><li><p>将容器中任意<strong>组件（Bean）的属性值</strong>和<strong>配置文件</strong>的配置项的值<strong>进行绑定</strong></p><ul><li><p>1、给容器中注册组件（@Component、@Bean)</p></li><li><p>2、使用 <strong>@ConfigurationProperties 声明组件和配置文件的哪些配置项进行绑定</strong></p></li></ul></li></ul><h2 id="五、缓存注解"><a href="#五、缓存注解" class="headerlink" title="五、缓存注解"></a>五、缓存注解</h2><h3 id="EnableCaching"><a href="#EnableCaching" class="headerlink" title="@EnableCaching"></a>@EnableCaching</h3><p>开启缓存注解的支持</p><h3 id="Cacheable"><a href="#Cacheable" class="headerlink" title="@Cacheable"></a>@Cacheable</h3><p><code>@Cacheable</code> 注解表示方法的结果应该被缓存起来，下次调用该方法时，如果参数和之前相同，则返回缓存结果。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">MyService</span> </span>&#123;</span><br><span class="line">    <span class="meta">@Cacheable</span>(<span class="string">"greetingCache"</span>)</span><br><span class="line">    <span class="function"><span class="keyword">public</span> String <span class="title">greeting</span><span class="params">(String name)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="string">"Hello, "</span> + name + <span class="string">"!"</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="CachePut"><a href="#CachePut" class="headerlink" title="@CachePut"></a>@CachePut</h3><p><code>@CachePut</code> 注解表示方法的结果应该被缓存起来，下次调用该方法时，不会返回缓存结果，而是重新计算结果并缓存起来。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">MyService</span> </span>&#123;</span><br><span class="line">    <span class="meta">@CachePut</span>(<span class="string">"greetingCache"</span>)</span><br><span class="line">    <span class="function"><span class="keyword">public</span> String <span class="title">greeting</span><span class="params">(String name)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="string">"Hello, "</span> + name + <span class="string">"!"</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="CacheEvict"><a href="#CacheEvict" class="headerlink" title="@CacheEvict"></a>@CacheEvict</h3><p><code>@CacheEvict</code> 注解表示方法执行后从缓存中删除指定项。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">MyService</span> </span>&#123;</span><br><span class="line">    <span class="meta">@CacheEvict</span>(<span class="string">"greetingCache"</span>)</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">clearGreetingCache</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="CacheConfig"><a href="#CacheConfig" class="headerlink" title="@CacheConfig"></a>@CacheConfig</h3><p>@CacheConfig是结合@Cacheable使用的来设置过期时间的</p><h3 id="Caching"><a href="#Caching" class="headerlink" title="@Caching"></a>@Caching</h3><p>@Caching 注解可以在一个方法或者类上同时指定多个Spring Cache相关的注解。<br>其拥有三个属性：cacheable、put 和 evict，分别用于指定@Cacheable、@CachePut 和 @CacheEvict。对于一个数据变动，更新多个缓存的场景，可以通过 @Caching 来实现：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">@Caching(cacheable = @Cacheable(cacheNames = &quot;caching&quot;, key = &quot;#age&quot;), evict = @CacheEvict(cacheNames = &quot;t4&quot;, key = &quot;#age&quot;))</span><br><span class="line">public String caching(int age) &#123;</span><br><span class="line">    return &quot;caching: &quot; + age + &quot;--&gt;&quot; + UUID.randomUUID().toString();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>参考：</p><p><a href="https://www.cnblogs.com/ziyue7575/p/c925cfe466df01c1d352f37da8823946.html" target="_blank" rel="noopener">https://www.cnblogs.com/ziyue7575/p/c925cfe466df01c1d352f37da8823946.html</a></p><p><a href="https://pdai.tech/md/spring/springboot/springboot-x-hello-anno.html" target="_blank" rel="noopener">https://pdai.tech/md/spring/springboot/springboot-x-hello-anno.html</a></p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h2 id=&quot;一、组件注册&quot;&gt;&lt;a href=&quot;#一、组件注册&quot; class=&quot;headerlink&quot; title=&quot;一、组件注册&quot;&gt;&lt;/a&gt;一、组件注册&lt;/h2&gt;&lt;h3 id=&quot;SpringBootConfiguration&quot;&gt;&lt;a href=&quot;#SpringBootConf
      
    
    </summary>
    
    
      <category term="微服务" scheme="https://www.cicoding.cn/categories/%E5%BE%AE%E6%9C%8D%E5%8A%A1/"/>
    
    
      <category term="微服务" scheme="https://www.cicoding.cn/tags/%E5%BE%AE%E6%9C%8D%E5%8A%A1/"/>
    
      <category term="SpringCloud" scheme="https://www.cicoding.cn/tags/SpringCloud/"/>
    
      <category term="annotation" scheme="https://www.cicoding.cn/tags/annotation/"/>
    
  </entry>
  
  <entry>
    <title>Windows 11解决PowerShell因为在此系统上禁止运行脚本。有关详细信息</title>
    <link href="https://www.cicoding.cn/other/win11-PowerShell/"/>
    <id>https://www.cicoding.cn/other/win11-PowerShell/</id>
    <published>2022-09-19T10:31:31.000Z</published>
    <updated>2025-03-05T14:22:17.766Z</updated>
    
    <content type="html"><![CDATA[<p>Intellij Idea、VScode 等开发工具自带程序终端的时候会报出”系统禁止脚本运行的错误”。</p><h3 id="解决"><a href="#解决" class="headerlink" title="解决"></a>解决</h3><ol><li>管理员身份运行Windows PowerShell</li><li>执行：get-ExecutionPolicy，显示Restricted，表示状态是禁止的;</li></ol><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">Restricted禁止的</span><br><span class="line">RemoteSigned允许的</span><br></pre></td></tr></table></figure><ol start="3"><li>执行：set-ExecutionPolicy</li></ol><p>​        3.1 会提示输入参数：RemoteSigned，然后回车</p><p>​        3.2 或者直接输入  set-executionpolicy remotesigned 回车</p><p>这样就能使用了；</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;Intellij Idea、VScode 等开发工具自带程序终端的时候会报出”系统禁止脚本运行的错误”。&lt;/p&gt;
&lt;h3 id=&quot;解决&quot;&gt;&lt;a href=&quot;#解决&quot; class=&quot;headerlink&quot; title=&quot;解决&quot;&gt;&lt;/a&gt;解决&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;管理员身
      
    
    </summary>
    
    
      <category term="PowerShell" scheme="https://www.cicoding.cn/categories/PowerShell/"/>
    
    
      <category term="PowerShell" scheme="https://www.cicoding.cn/tags/PowerShell/"/>
    
      <category term="Windows 11" scheme="https://www.cicoding.cn/tags/Windows-11/"/>
    
  </entry>
  
  <entry>
    <title>RocketMQ--消息幂等（去重）通用解决方案</title>
    <link href="https://www.cicoding.cn/rocketmq/rocketmq-message-dedup/"/>
    <id>https://www.cicoding.cn/rocketmq/rocketmq-message-dedup/</id>
    <published>2022-05-19T15:01:02.000Z</published>
    <updated>2025-03-05T14:22:17.769Z</updated>
    
    <content type="html"><![CDATA[<p>​        消息中间件是分布式系统常用的组件，无论是异步化、解耦、削峰等都有广泛的应用价值。我们通常会认为，消息中间件是一个可靠的组件——这里所谓的可靠是指，只要我把消息成功投递到了消息中间件，消息就不会丢失，即消息肯定会至少保证消息能被消费者成功消费一次，这是消息中间件最基本的特性之一，也就是我们常说的“AT LEAST ONCE”，即消息至少会被“成功消费一遍”。</p><p>​        举个例子，一个消息M发送到了消息中间件，消息投递到了消费程序A，A接受到了消息，然后进行消费，但在消费到一半的时候程序重启了，这时候这个消息并没有标记为消费成功，这个消息还会继续投递给这个消费者，直到其消费成功了，消息中间件才会停止投递。</p><p>​        然而这种可靠的特性导致，消息可能被多次地投递。举个例子，还是刚刚这个例子，程序A接受到这个消息M并完成消费逻辑之后，正想通知消息中间件“我已经消费成功了”的时候，程序就重启了，那么对于消息中间件来说，这个消息并没有成功消费过，所以他还会继续投递。这时候对于应用程序A来说，看起来就是这个消息明明消费成功了，但是消息中间件还在重复投递。</p><p>​        这在RockectMQ的场景来看，就是同一个messageId的消息重复投递下来了。</p><p>​        基于消息的投递可靠（消息不丢）是优先级更高的，所以消息不重的任务就会转移到应用程序自我实现，这也是为什么RocketMQ的文档里强调的，消费逻辑需要自我实现幂等。背后的逻辑其实就是：不丢和不重是矛盾的（在分布式场景下），但消息重复是有解决方案的，而消息丢失是很麻烦的。</p><h2 id="简单的消息去重解决方案"><a href="#简单的消息去重解决方案" class="headerlink" title="简单的消息去重解决方案"></a>简单的消息去重解决方案</h2><p>例如：假设我们业务的消息消费逻辑是：插入某张订单表的数据，然后更新库存：</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">insert</span> <span class="keyword">into</span> t_order <span class="keyword">values</span> ..... <span class="keyword">update</span> t_inv <span class="keyword">set</span> <span class="keyword">count</span> = <span class="keyword">count</span><span class="number">-1</span> <span class="keyword">where</span> good_id = <span class="string">'good123'</span>;</span><br></pre></td></tr></table></figure><p>要实现消息的幂等，我们可能会采取这样的方案：</p><figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">select * from t_order where order_no = 'order123' if(order  != null) &#123;     return ;//消息重复，直接返回 &#125;</span><br></pre></td></tr></table></figure><p>这对于很多情况下，的确能起到不错的效果，但是在并发场景下，还是会有问题。</p><h2 id="并发重复消息"><a href="#并发重复消息" class="headerlink" title="并发重复消息"></a>并发重复消息</h2><p>​        假设这个消费的所有代码加起来需要1秒，有重复的消息在这1秒内（假设100毫秒）内到达（例如生产者快速重发，Broker重启等），那么很可能，上面去重代码里面会发现，数据依然是空的（因为上一条消息还没消费完，还没成功更新订单状态），</p><p>​        那么就会穿透掉检查的挡板，最后导致重复的消息消费逻辑进入到非幂等安全的业务代码中，从而引发重复消费的问题（如主键冲突抛出异常、库存被重复扣减而没释放等）</p><h3 id="并发去重的解决方案之一"><a href="#并发去重的解决方案之一" class="headerlink" title="并发去重的解决方案之一"></a>并发去重的解决方案之一</h3><p>要解决上面并发场景下的消息幂等问题，一个可取的方案是开启事务把select 改成 select for update语句，把记录进行锁定。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">select * from t_order where order_no = &apos;THIS_ORDER_NO&apos; for update  //开启事务 if(order.status != null) &#123;    return ;//消息重复，直接返回 &#125;</span><br></pre></td></tr></table></figure><p>​        但这样消费的逻辑会因为引入了事务包裹而导致整个消息消费可能变长，并发度下降。</p><p>当然还有其他更高级的解决方案，例如更新订单状态采取乐观锁，更新失败则消息重新消费之类的。但这需要针对具体业务场景做更复杂和细致的代码开发、库表设计，不在本文讨论的范围。</p><p>​        但无论是select for update， 还是乐观锁这种解决方案，实际上都是基于业务表本身做去重，这无疑增加了业务开发的复杂度， 一个业务系统里面很大部分的请求处理都是依赖MQ的，如果每个消费逻辑本身都需要基于业务本身而做去重/幂等的开发的话，这是繁琐的工作量。本文希望探索出一个通用的消息幂等处理的方法，从而抽象出一定的工具类用以适用各个业务场景。</p><h1 id="Exactly-Once"><a href="#Exactly-Once" class="headerlink" title="Exactly Once"></a>Exactly Once</h1><p>​        在消息中间件里，有一个投递语义的概念，而这个语义里有一个叫”Exactly Once”，即消息肯定会被成功消费，并且只会被消费一次。以下是阿里云里对Exactly Once的解释：</p><blockquote><p>Exactly-Once 是指发送到消息系统的消息只能被消费端处理且仅处理一次，即使生产端重试消息发送导致某消息重复投递，该消息在消费端也只被消费一次。</p></blockquote><p>在我们业务消息幂等处理的领域内，可以认为业务消息的代码肯定会被执行，并且只被执行一次，那么我们可以认为是Exactly Once。</p><p>但这在分布式的场景下想找一个通用的方案几乎是不可能的。不过如果是针对基于数据库事务的消费逻辑，实际上是可行的。</p><h2 id="基于关系数据库事务插入消息表"><a href="#基于关系数据库事务插入消息表" class="headerlink" title="基于关系数据库事务插入消息表"></a>基于关系数据库事务插入消息表</h2><p>假设我们业务的消息消费逻辑是：更新MySQL数据库的某张订单表的状态：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">update t_order set status = &apos;SUCCESS&apos; where order_no= &apos;order123&apos;;</span><br></pre></td></tr></table></figure><p>​        要实现Exaclty Once即这个消息只被消费一次（并且肯定要保证能消费一次），我们可以这样做：在这个数据库中增加一个消息消费记录表，把消息插入到这个表，并且把原来的订单更新和这个插入的动作放到同一个事务中一起提交，就能保证消息只会被消费一遍了。</p><ol><li>开启事务</li><li>插入消息表（处理好主键冲突的问题）</li><li>更新订单表（原消费逻辑）</li><li>提交事务</li></ol><p>说明：</p><ol><li>这时候如果消息消费成功并且事务提交了，那么消息表就插入成功了，这时候就算RocketMQ还没有收到消费位点的更新再次投递，也会插入消息失败而视为已经消费过，后续就直接更新消费位点了。这保证我们消费代码只会执行一次。</li><li>如果事务提交之前服务挂了（例如重启），对于本地事务并没有执行所以订单没有更新，消息表也没插入成功；而对于RocketMQ服务端来说，消费位点也没更新，所以消息还会继续投递下来，投递下来发现这个消息插入消息表也是成功的，所以可以继续消费。这保证了消息不丢失。</li></ol><p>​        事实上，阿里云ONS的EXACTLY-ONCE语义的实现上，就是类似这个方案基于数据库的事务特性实现的。更多详情可参考：<a href="https://help.aliyun.com/document_detail/102777.html" target="_blank" rel="noopener">https://help.aliyun.com/document_detail/102777.html</a></p><p>基于这种方式，的确这是有能力拓展到不同的应用场景，因为他的实现方案与具体业务本身无关——而是依赖一个消息表。</p><p>但是这里有它的局限性</p><ol><li>消息的消费逻辑必须是依赖于关系型数据库事务。如果消费的消费过程中还涉及其他数据的修改，例如Redis这种不支持事务特性的数据源，则这些数据是不可回滚的。</li><li>数据库的数据必须是在一个库，跨库无法解决</li></ol><p>注：业务上，消息表的设计不应该以消息ID作为标识，而应该以业务的业务主键作为标识更为合理，以应对生产者的重发。阿里云上的消息去重只是RocketMQ的messageId，在生产者因为某些原因手动重发（例如上游针对一个交易重复请求了）的场景下起不到去重/幂等的效果（因消息id不同）。</p><h2 id="更复杂的业务场景"><a href="#更复杂的业务场景" class="headerlink" title="更复杂的业务场景"></a>更复杂的业务场景</h2><p>​        如上所述，这种方式Exactly Once语义的实现，实际上有很多局限性，这种局限性使得这个方案基本不具备广泛应用的价值。并且由于基于事务，可能导致锁表时间过长等性能问题。</p><p>例如我们以一个比较常见的一个订单申请的消息来举例，可能有以下几步（以下统称为步骤X）：</p><ol><li>检查库存（RPC）</li><li>锁库存（RPC）</li><li>开启事务，插入订单表（MySQL）</li><li>调用某些其他下游服务（RPC）</li><li>更新订单状态</li><li>commit 事务（MySQL）</li></ol><p>​        这种情况下，我们如果采取消息表+本地事务的实现方式，消息消费过程中很多子过程是不支持回滚的，也就是说就算我们加了事务，实际上这背后的操作并不是原子性的。怎么说呢，就是说有可能第一条小在经历了第二步锁库存的时候，服务重启了，这时候实际上库存是已经在另外的服务里被锁定了，这并不能被回滚。当然消息还会再次投递下来，要保证消息能至少消费一遍，换句话说，锁库存的这个RPC接口本身依旧要支持“幂等”。</p><p>​        再者，如果在这个比较耗时的长链条场景下加入事务的包裹，将大大的降低系统的并发。所以通常情况下，我们处理这种场景的消息去重的方法还是会使用一开始说的业务自己实现去重逻辑的方式，如前面加select for update，或者使用乐观锁。</p><p>​        那我们有没有方法抽取出一个公共的解决方案，能兼顾去重、通用、高性能呢？</p><h2 id="拆解消息执行过程"><a href="#拆解消息执行过程" class="headerlink" title="拆解消息执行过程"></a>拆解消息执行过程</h2><p>其中一个思路是把上面的几步，拆解成几个不同的子消息，例如：</p><ol><li>库存系统消费A：检查库存并做锁库存，发送消息B给订单服务</li><li>订单系统消费消息B：插入订单表（MySQL），发送消息C给自己（下游系统）消费</li><li>下游系统消费消息C：处理部分逻辑，发送消息D给订单系统</li><li>订单系统消费消息D：更新订单状态</li></ol><p>注：上述步骤需要保证本地事务和消息是一个事务的（至少是最终一致性的），这其中涉及到分布式事务消息相关的话题，不在本文论述。</p><p>可以看到这样的处理方法会使得每一步的操作都比较原子，而原子则意味着是小事务，小事务则意味着使用消息表+事务的方案显得可行。</p><p>然而，这太复杂了！这把一个本来连续的代码逻辑割裂成多个系统多次消息交互！那还不如业务代码层面上加锁实现呢。</p><h2 id="更通用的解决方案"><a href="#更通用的解决方案" class="headerlink" title="更通用的解决方案"></a>更通用的解决方案</h2><p>上面消息表+本地事务的方案之所以有其局限性和并发的短板，究其根本是因为它<strong>依赖于关系型数据库的事务</strong>，且必须要把事务包裹于整个消息消费的环节。</p><p>如果我们能不依赖事务而实现消息的去重，那么方案就能推广到更复杂的场景例如：RPC、跨库等。</p><p>例如，我们依旧使用消息表，但是不依赖事务，而是针对消息表增加消费状态，是否可以解决问题呢？</p><h3 id="基于消息幂等表的非事务方案"><a href="#基于消息幂等表的非事务方案" class="headerlink" title="基于消息幂等表的非事务方案"></a>基于消息幂等表的非事务方案</h3><p><img src="/images/rocketmq/dedup-solution-01.png" alt="RocketMQ--消息幂等（去重）通用解决方案"></p><p>​        以上是去事务化后的消息幂等方案的流程，可以看到，此方案是无事务的，而是针对消息表本身做了状态的区分：消费中、消费完成。<strong>只有消费完成的消息才会被幂等处理掉</strong>。而对于已有消费中的消息，后面重复的消息会触发延迟消费（在RocketMQ的场景下即发送到RETRY TOPIC），之所以触发延迟消费是为了控制并发场景下，第二条消息在第一条消息没完成的过程中，去控制消息不丢（如果直接幂等，那么会丢失消息（同一个消息id的话），因为上一条消息如果没有消费完成的时候，第二条消息你已经告诉broker成功了，那么第一条消息这时候失败broker也不会重新投递了）</p><p>上面的流程不再细说，后文有github源码的地址，读者可以参考源码的实现，这里我们回头看看我们一开始想解决的问题是否解决了：</p><ol><li>消息已经消费成功了，第二条消息将被直接幂等处理掉（消费成功）。</li><li>并发场景下的消息，依旧能满足不会出现消息重复，即穿透幂等挡板的问题。</li><li>支持上游业务生产者重发的业务重复的消息幂等问题。</li></ol><p>​        关于第一个问题已经很明显已经解决了，在此就不讨论了。</p><p>​        关于第二个问题是如何解决的？主要是依靠插入消息表的这个动作做控制的，假设我们用MySQL作为消息表的存储媒介（设置消息的唯一ID为主键），那么插入的动作只有一条消息会成功，后面的消息插入会由于主键冲突而失败，走向延迟消费的分支，然后后面延迟消费的时候就会变成上面第一个场景的问题。</p><p>​        关于第三个问题，只要我们设计去重的消息键让其支持业务的主键（例如订单号、请求流水号等），而不仅仅是messageId即可。所以也不是问题。</p><h3 id="此方案是否有消息丢失的风险？"><a href="#此方案是否有消息丢失的风险？" class="headerlink" title="此方案是否有消息丢失的风险？"></a>此方案是否有消息丢失的风险？</h3><p>​        如果细心的读者可能会发现这里实际上是有逻辑漏洞的，问题出在上面聊到的个三问题中的第2个问题（并发场景），在并发场景下我们依赖于消息状态是做并发控制使得第2条消息重复的消息会不断延迟消费（重试）。但如果这时候第1条消息也由于一些异常原因（例如机器重启了、外部异常导致消费失败）没有成功消费成功呢？也就是说这时候延迟消费实际上每次下来看到的都是<em>消费中</em>的状态，最后消费就会被视为消费失败而被投递到死信Topic中（RocketMQ默认可以重复消费16次）。</p><p>​        有这种顾虑是正确的！对于此，我们解决的方法是，插入的消息表必须要带一个最长消费过期时间，例如10分钟，意思是如果一个消息处于<em>消费中</em>超过10分钟，就需要从消息表中删除（需要程序自行实现）。所以最后这个消息的流程会是这样的：</p><p><img src="/images/rocketmq/dedup-solution-02.png" alt="RocketMQ--消息幂等（去重）通用解决方案"></p><h2 id="更灵活的消息表存储媒介"><a href="#更灵活的消息表存储媒介" class="headerlink" title="更灵活的消息表存储媒介"></a>更灵活的消息表存储媒介</h2><p>我们这个方案实际上没有事务的，只需要一个存储的中心媒介，那么自然我们可以选择更灵活的存储媒介，例如Redis。使用Redis有两个好处：</p><ol><li>性能上损耗更低</li><li>上面我们讲到的超时时间可以直接利用Redis本身的ttl实现</li></ol><p>当然Redis存储的数据可靠性、一致性等方面是不如MySQL的，需要用户自己取舍。</p><h1 id="源码：RocketMQDedupListener"><a href="#源码：RocketMQDedupListener" class="headerlink" title="源码：RocketMQDedupListener"></a>源码：RocketMQDedupListener</h1><p>以上方案针对RocketMQ的Java实现已经开源放到Github中，具体的使用文档可以参考<a href="https://github.com/Jaskey/RocketMQDedupListener" target="_blank" rel="noopener">https://github.com/Jaskey/RocketMQDedupListener</a> ，</p><p>以下仅贴一个Readme中利用Redis去重的使用样例，用以意业务中如果使用此工具加入消息去重幂等的是多么简单：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">//利用Redis做幂等表        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer(&quot;TEST-APP1&quot;);        consumer.subscribe(&quot;TEST-TOPIC&quot;, &quot;*&quot;);         String appName = consumer.getConsumerGroup();// 大部分情况下可直接使用consumer group名        StringRedisTemplate stringRedisTemplate = null;// 这里省略获取StringRedisTemplate的过程        DedupConfig dedupConfig = DedupConfig.enableDedupConsumeConfig(appName, stringRedisTemplate);        DedupConcurrentListener messageListener = new SampleListener(dedupConfig);         consumer.registerMessageListener(messageListener);        consumer.start();</span><br></pre></td></tr></table></figure><p>​        以上代码大部分是原始RocketMQ的必须代码，唯一需要修改的仅仅是创建一个<code>DedupConcurrentListener</code>示例，在这个示例中指明你的消费逻辑和去重的业务键（默认是messageId）。</p><p>更多使用详情请参考Github上的说明。</p><h1 id="这种实现是否一劳永逸？"><a href="#这种实现是否一劳永逸？" class="headerlink" title="这种实现是否一劳永逸？"></a>这种实现是否一劳永逸？</h1><p>​        实现到这里，似乎方案挺完美的，所有的消息都能快速的接入去重，且与具体业务实现也完全解耦。那么这样是否就完美的完成去重的所有任务呢？</p><p>很可惜，其实不是的。原因很简单：因为要保证消息至少被成功消费一遍，那么消息就有机会消费到一半的时候失败触发消息重试的可能。还是以上面的订单流程X：</p><blockquote><ol><li>检查库存（RPC）</li><li>锁库存（RPC）</li><li>开启事务，插入订单表（MySQL）</li><li>调用某些其他下游服务（RPC）</li><li>更新订单状态</li><li>commit 事务（MySQL）</li></ol></blockquote><p>当消息消费到步骤3的时候，我们假设MySQL异常导致失败了，触发消息重试。因为在重试前我们会删除幂等表的记录，所以消息重试的时候就会重新进入消费代码，那么步骤1和步骤2就会重新再执行一遍。如果步骤2本身不是幂等的，那么这个业务消息消费依旧没有做好完整的幂等处理。</p><h1 id="本实现方式的价值？"><a href="#本实现方式的价值？" class="headerlink" title="本实现方式的价值？"></a>本实现方式的价值？</h1><p>​        那么既然这个并不能完整的完成消息幂等，还有什么价值呢？价值可就大了！虽然这不是解决消息幂等的银弹（事实上，软件工程领域里基本没有银弹），但是他能以便捷的手段解决：</p><p>1.各种由于Broker、负载均衡等原因导致的消息重投递的重复问题</p><p>2.各种上游生产者导致的业务级别消息重复问题</p><p>3.重复消息并发消费的控制窗口问题，就算重复，重复也不可能同一时间进入消费逻辑</p><h1 id="一些其他的消息去重的建议"><a href="#一些其他的消息去重的建议" class="headerlink" title="一些其他的消息去重的建议"></a>一些其他的消息去重的建议</h1><p>​        也就是说，使用这个方法能保证正常的消费逻辑场景下（无异常，无异常退出），消息的幂等工作全部都能解决，无论是业务重复，还是rocketmq特性带来的重复。</p><p>事实上，这已经能解决99%的消息重复问题了，毕竟异常的场景肯定是少数的。那么如果希望异常场景下也能处理好幂等的问题，可以做以下工作降低问题率：</p><ol><li>消息消费失败做好回滚处理。如果消息消费失败本身是带回滚机制的，那么消息重试自然就没有副作用了。</li><li>消费者做好优雅退出处理。这是为了尽可能避免消息消费到一半程序退出导致的消息重试。</li><li>一些无法做到幂等的操作，至少要做到终止消费并告警。例如锁库存的操作，如果统一的业务流水锁成功了一次库存，再触发锁库存，如果做不到幂等的处理，至少要做到消息消费触发异常（例如主键冲突导致消费异常等）</li><li>在#3做好的前提下，做好消息的消费监控，发现消息重试不断失败的时候，手动做好#1的回滚，使得下次重试消费成功。</li></ol>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;​        消息中间件是分布式系统常用的组件，无论是异步化、解耦、削峰等都有广泛的应用价值。我们通常会认为，消息中间件是一个可靠的组件——这里所谓的可靠是指，只要我把消息成功投递到了消息中间件，消息就不会丢失，即消息肯定会至少保证消息能被消费者成功消费一次，这是消息中
      
    
    </summary>
    
    
      <category term="RocketMQ" scheme="https://www.cicoding.cn/categories/RocketMQ/"/>
    
    
      <category term="RocketMQ" scheme="https://www.cicoding.cn/tags/RocketMQ/"/>
    
  </entry>
  
  <entry>
    <title>RocketMQ--消息文件过期原理</title>
    <link href="https://www.cicoding.cn/rocketmq/rocketmq-clean-commitlog/"/>
    <id>https://www.cicoding.cn/rocketmq/rocketmq-clean-commitlog/</id>
    <published>2022-05-19T14:26:00.000Z</published>
    <updated>2025-03-05T14:22:17.767Z</updated>
    
    <content type="html"><![CDATA[<p><a href="/rocketmq/rocketmq-consume-offset-management/">RocketMQ——消息ACK机制及消费进度管理</a> 文中提过，所有的消费均是客户端发起Pull请求的，告诉消息的offset位置，broker去查询并返回。但是有一点需要非常明确的是，消息消费后，消息其实<strong>并没有</strong>物理地被清除，这是一个非常特殊的设计。本文来探索此设计的一些细节。</p><h2 id="消费完后的消息去哪里了？"><a href="#消费完后的消息去哪里了？" class="headerlink" title="消费完后的消息去哪里了？"></a>消费完后的消息去哪里了？</h2><p>消息的存储是一直存在于CommitLog中的。而由于CommitLog是以文件为单位（而非消息）存在的，CommitLog的设计是只允许顺序写的，且每个消息大小不定长，所以这决定了消息文件几乎不可能按照消息为单位删除（否则性能会极具下降，逻辑也非常复杂）。所以消息被消费了，消息所占据的物理空间并不会立刻被回收。</p><p>但消息既然一直没有删除，那RocketMQ怎么知道应该投递过的消息就不再投递？——答案是客户端自身维护——客户端拉取完消息之后，在响应体中，broker会返回下一次应该拉取的位置，PushConsumer通过这一个位置，更新自己下一次的pull请求。这样就保证了正常情况下，消息只会被投递一次。</p><h2 id="什么时候清理物理消息文件？"><a href="#什么时候清理物理消息文件？" class="headerlink" title="什么时候清理物理消息文件？"></a>什么时候清理物理消息文件？</h2><p>那消息文件到底删不删，什么时候删？</p><p>消息存储在CommitLog之后，的确是会被清理的，但是这个清理只会在以下任一条件成立才会批量删除消息文件（CommitLog）：</p><ol><li>消息文件过期（默认72小时），且到达清理时点（默认是凌晨4点），删除过期文件。</li><li>消息文件过期（默认72小时），且磁盘空间达到了水位线（默认75%），删除过期文件。</li><li>磁盘已经达到必须释放的上限（85%水位线）的时候，则开始批量清理文件（无论是否过期），直到空间充足。</li></ol><p>注：若磁盘空间达到危险水位线（默认90%），出于保护自身的目的，broker会拒绝写入服务。</p><h2 id="这样设计带来的好处"><a href="#这样设计带来的好处" class="headerlink" title="这样设计带来的好处"></a>这样设计带来的好处</h2><p>消息的物理文件一直存在，消费逻辑只是听客户端的决定而搜索出对应消息进行，这样做，笔者认为，有以下几个好处：</p><ol><li>一个消息很可能需要被N个消费组（设计上很可能就是系统）消费，但消息只需要存储一份，消费进度单独记录即可。这给强大的消息堆积能力提供了很好的支持——一个消息无需复制N份，就可服务N个消费组。</li><li>由于消费从哪里消费的决定权一直都是客户端决定，所以只要消息还在，就可以消费到，这使得RocketMQ可以支持其他传统消息中间件不支持的回溯消费。即我可以通过设置消费进度回溯，就可以让我的消费组重新像放快照一样消费历史消息；或者我需要另一个系统也复制历史的数据，只需要另起一个消费组从头消费即可（前提是消息文件还存在）。</li><li>消息索引服务。只要消息还存在就能被搜索出来。所以可以依靠消息的索引搜索出消息的各种原信息，方便事后排查问题。</li></ol><p>注：在消息清理的时候，由于消息文件默认是1GB，所以在清理的时候其实是在删除一个大文件操作，这对于IO的压力是非常大的，这时候如果有消息写入，写入的耗时会明显变高。这个现象可以在凌晨4点（默认删时间时点）后的附近观察得到。</p><p>RocketMQ官方建议Linux下文件系统改为Ext4，对于文件删除操作相比Ext3有非常明显的提升。</p><h2 id="跳过历史消息的处理"><a href="#跳过历史消息的处理" class="headerlink" title="跳过历史消息的处理"></a>跳过历史消息的处理</h2><p>由于消息本身是没有过期的概念，只有文件才有过期的概念。那么对于很多业务场景——一个消息如果太老，是无需要被消费的，是不合适的。</p><p>这种需要跳过历史消息的场景，在RocketMQ要怎么实现呢？</p><p>对于一个全新的消费组，PushConsumer默认就是跳过以前的消息而从最尾开始消费的，解析请参看<a href="/rocketmq/rocketmq-consume-offset-management/">RocketMQ——消息ACK机制及消费进度管理</a>相关章节。</p><p>但对于已存在的消费组，RocketMQ没有内置的跳过历史消息的实现，但有以下手段可以解决：</p><ol><li><p>自身的消费代码按照日期过滤，太老的消息直接过滤。如：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> ConsumeConcurrentlyStatus <span class="title">consumeMessage</span><span class="params">(List&lt;MessageExt&gt; msgs, ConsumeConcurrentlyContext context)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">for</span>(MessageExt msg: msgs)&#123;</span><br><span class="line">        <span class="keyword">if</span>(System.currentTimeMillis()-msg.getBornTimestamp()&gt;<span class="number">60</span>*<span class="number">1000</span>) &#123;<span class="comment">//一分钟之前的认为过期</span></span><br><span class="line">            <span class="keyword">continue</span>;<span class="comment">//过期消息跳过</span></span><br><span class="line">        &#125;</span><br><span class="line">   </span><br><span class="line">        <span class="comment">//do consume here</span></span><br><span class="line">   </span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> ConsumeConcurrentlyStatus.CONSUME_SUCCESS;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li><li><p>自身的消费代码代码判断消息的offset和MAX_OFFSET相差很远，认为是积压了很多，直接return CONSUME_SUCCESS过滤。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> ConsumeConcurrentlyStatus <span class="title">consumeMessage</span><span class="params">(//</span></span></span><br><span class="line"><span class="function"><span class="params">    List&lt;MessageExt&gt; msgs, //</span></span></span><br><span class="line"><span class="function"><span class="params">    ConsumeConcurrentlyContext context)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">long</span> offset = msgs.get(<span class="number">0</span>).getQueueOffset();</span><br><span class="line">    String maxOffset = msgs.get(<span class="number">0</span>).getProperty(MessageConst.PROPERTY_MAX_OFFSET);</span><br><span class="line">    <span class="keyword">long</span> diff = Long. parseLong(maxOffset) - offset;</span><br><span class="line">    <span class="keyword">if</span> (diff &gt; <span class="number">100000</span>) &#123; <span class="comment">//消息堆积了10W情况的特殊处理</span></span><br><span class="line">        <span class="keyword">return</span> ConsumeConcurrentlyStatus. CONSUME_SUCCESS;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">//do consume here</span></span><br><span class="line">    <span class="keyword">return</span> ConsumeConcurrentlyStatus. CONSUME_SUCCESS;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li><li><p>消费者启动前，先调整该消费组的消费进度，再开始消费。可以人工使用控制台命令resetOffsetByTime把消费进度调整到后面，再启动消费。</p></li><li><p>原理同3，但使用代码来控制。代码中调用内部的运维接口，具体代码实例祥见<code>ResetOffsetByTimeCommand.java</code></p></li></ol>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;&lt;a href=&quot;/rocketmq/rocketmq-consume-offset-management/&quot;&gt;RocketMQ——消息ACK机制及消费进度管理&lt;/a&gt; 文中提过，所有的消费均是客户端发起Pull请求的，告诉消息的offset位置，broker去查询并返回。
      
    
    </summary>
    
    
      <category term="RocketMQ" scheme="https://www.cicoding.cn/categories/RocketMQ/"/>
    
    
      <category term="RocketMQ" scheme="https://www.cicoding.cn/tags/RocketMQ/"/>
    
  </entry>
  
  <entry>
    <title>Nacos介绍与安装启动</title>
    <link href="https://www.cicoding.cn/alibaba/nacos-installation-and-startup/"/>
    <id>https://www.cicoding.cn/alibaba/nacos-installation-and-startup/</id>
    <published>2021-07-15T12:50:15.000Z</published>
    <updated>2025-03-05T14:22:17.749Z</updated>
    
    <content type="html"><![CDATA[<h1 id="什么是-Nacos"><a href="#什么是-Nacos" class="headerlink" title="什么是 Nacos"></a>什么是 Nacos</h1><h2 id="概览"><a href="#概览" class="headerlink" title="概览"></a>概览</h2><p>欢迎来到 Nacos 的世界！</p><p>Nacos 致力于帮助您发现、配置和管理微服务。Nacos 提供了一组简单易用的特性集，帮助您快速实现动态服务发现、服务配置、服务元数据及流量管理。</p><p>Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。 Nacos 是构建以“服务”为中心的现代应用架构 (例如微服务范式、云原生范式) 的服务基础设施。</p><h2 id="什么是-Nacos？"><a href="#什么是-Nacos？" class="headerlink" title="什么是 Nacos？"></a>什么是 Nacos？</h2><p>服务（Service）是 Nacos 世界的一等公民。Nacos 支持几乎所有主流类型的“服务”的发现、配置和管理：</p><p>Kubernetes Service</p><p>gRPC &amp; Dubbo RPC Service</p><p>Spring Cloud RESTful Service</p><p>Nacos 的关键特性包括:</p><ul><li><strong>服务发现和服务健康监测</strong></li><li><strong>动态配置服务</strong></li><li><strong>动态 DNS 服务</strong></li><li><strong>服务及其元数据管理</strong></li></ul><h1 id="Nacos-快速开始"><a href="#Nacos-快速开始" class="headerlink" title="Nacos 快速开始"></a>Nacos 快速开始</h1><p>这个快速开始手册是帮忙您快速在您的电脑上，下载、安装并使用 Nacos。</p><h2 id="0-版本选择"><a href="#0-版本选择" class="headerlink" title="0.版本选择"></a>0.版本选择</h2><p>您可以在Nacos的<a href="https://github.com/alibaba/nacos/releases" target="_blank" rel="noopener">release notes</a>及<a href="https://nacos.io/zh-cn/blog/index.html" target="_blank" rel="noopener">博客</a>中找到每个版本支持的功能的介绍，当前推荐的稳定版本为1.4.2或2.0.1。</p><h2 id="1-预备环境准备"><a href="#1-预备环境准备" class="headerlink" title="1.预备环境准备"></a>1.预备环境准备</h2><p>Nacos 依赖 <a href="https://docs.oracle.com/cd/E19182-01/820-7851/inst_cli_jdk_javahome_t/" target="_blank" rel="noopener">Java</a> 环境来运行。如果您是从代码开始构建并运行Nacos，还需要为此配置 <a href="https://maven.apache.org/index.html" target="_blank" rel="noopener">Maven</a>环境，请确保是在以下版本环境中安装使用:</p><ol><li>64 bit OS，支持 Linux/Unix/Mac/Windows，推荐选用 Linux/Unix/Mac。</li><li>64 bit JDK 1.8+；<a href="http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html" target="_blank" rel="noopener">下载</a> &amp; <a href="https://docs.oracle.com/cd/E19182-01/820-7851/inst_cli_jdk_javahome_t/" target="_blank" rel="noopener">配置</a>。</li><li>Maven 3.2.x+；<a href="https://maven.apache.org/download.cgi" target="_blank" rel="noopener">下载</a> &amp; <a href="https://maven.apache.org/settings.html" target="_blank" rel="noopener">配置</a>。</li></ol><h3 id="下载编译后压缩包方式"><a href="#下载编译后压缩包方式" class="headerlink" title="下载编译后压缩包方式"></a>下载编译后压缩包方式</h3><p>您可以从 <a href="https://github.com/alibaba/nacos/releases" target="_blank" rel="noopener">最新稳定版本</a> 下载 <code>nacos-server-$version.zip</code> 包。</p><p>解压nacos-server-1.4.2.zip</p><p><img src="/images/alibaba/image-20210715114901792.png" alt="Nacos介绍与安装启动"></p><h3 id="Windows"><a href="#Windows" class="headerlink" title="Windows"></a>Windows</h3><p>启动命令(standalone代表着单机模式运行，非集群模式):</p><figure class="highlight cmd"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ <span class="built_in">cd</span> bin/</span><br><span class="line">$ startup.<span class="built_in">cmd</span> -m standalone</span><br></pre></td></tr></table></figure><h3 id="单机模式支持mysql"><a href="#单机模式支持mysql" class="headerlink" title="单机模式支持mysql"></a>单机模式支持mysql</h3><p>在0.7版本之前，在单机模式时nacos使用嵌入式数据库实现数据的存储，不方便观察数据存储的基本情况。0.7版本增加了支持mysql数据源能力，具体的操作步骤：</p><ul><li>1.安装数据库，版本要求：5.6.5+</li><li>2.初始化mysql数据库，数据库初始化文件：nacos-mysql.sql</li><li>3.修改conf/application.properties文件，增加支持mysql数据源配置（目前只支持mysql），添加mysql数据源的url、用户名和密码。</li></ul><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">#*************** Config Module Related Configurations ***************#</span><br><span class="line">### If use MySQL as datasource:</span><br><span class="line">spring.datasource.platform=mysql</span><br><span class="line"></span><br><span class="line">### Count of DB:</span><br><span class="line"># 数据库实例数量</span><br><span class="line">db.num=1</span><br><span class="line"></span><br><span class="line"># 数据库连接信息，如果是 MySQL 8.0+ 版本需要添加 serverTimezone=Asia/Shanghai</span><br><span class="line">### Connect URL of DB:</span><br><span class="line">db.url.0=jdbc:mysql://127.0.0.1:3306/nacos?characterEncoding=utf8&amp;connectTimeout=1000&amp;socketTimeout=3000&amp;autoReconnect=true&amp;useUnicode=true&amp;useSSL=false&amp;serverTimezone=UTC&amp;allowPublicKeyRetrieval=true</span><br><span class="line">db.user=root</span><br><span class="line">db.password=root</span><br></pre></td></tr></table></figure><p>配置好数据库然后重启nacos如下:</p><p><img src="/images/alibaba/image-20210715124235335.png" alt="Nacos介绍与安装启动"></p><h3 id="访问ip-8848-nacos"><a href="#访问ip-8848-nacos" class="headerlink" title="访问ip:8848/nacos"></a>访问ip:8848/nacos</h3><p><a href="http://192.168.2.159:8848/nacos/index.html" target="_blank" rel="noopener">http://192.168.2.159:8848/nacos/index.html</a></p><p>输入用户名 nacos 密码 nacos</p><p><img src="/images/alibaba/image-20210715124449098.png" alt="Nacos介绍与安装启动"></p><p><img src="/images/alibaba/image-20210715124507137.png" alt="Nacos介绍与安装启动"></p><p>这样就启动配置成功!</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h1 id=&quot;什么是-Nacos&quot;&gt;&lt;a href=&quot;#什么是-Nacos&quot; class=&quot;headerlink&quot; title=&quot;什么是 Nacos&quot;&gt;&lt;/a&gt;什么是 Nacos&lt;/h1&gt;&lt;h2 id=&quot;概览&quot;&gt;&lt;a href=&quot;#概览&quot; class=&quot;headerlink&quot; 
      
    
    </summary>
    
    
      <category term="SpringCloud Alibaba" scheme="https://www.cicoding.cn/categories/SpringCloud-Alibaba/"/>
    
    
      <category term="SpringCloud Alibaba" scheme="https://www.cicoding.cn/tags/SpringCloud-Alibaba/"/>
    
  </entry>
  
  <entry>
    <title>MySQL各存储引擎</title>
    <link href="https://www.cicoding.cn/mysql/mysql-storage-engines/"/>
    <id>https://www.cicoding.cn/mysql/mysql-storage-engines/</id>
    <published>2021-06-28T13:27:35.000Z</published>
    <updated>2025-03-05T14:22:17.763Z</updated>
    
    <content type="html"><![CDATA[<p>MySQL中的数据用各种不同的技术存储在文件（或者内存）中。这些技术中的每一种技术都使用不同的存储机制、索引技巧、锁定水平并且最终提供广泛的不同的功能和能力。通过选择不同的技术，你能够获得额外的速度或者功能，从而改善你的应用的整体功能。这些不同的技术以及配套的相关功能在MySQL中被称作存储引擎(也称作表类型)。MySQL默认配置了许多不同的存储引擎，可以预先设置或者在MySQL服务器中启用。你可以选择适用于服务器、数据库和表格的存储引擎，以便在选择如何存储你的信息、如何检索这些信息以及你需要你的数据结合什么性能和功能的时候为你提供最大的灵活性。<br>使用以下命令可以查看MySQL支持的引擎：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">mysql&gt; show engines;</span><br></pre></td></tr></table></figure><h2 id="一、InnoDB存储引擎"><a href="#一、InnoDB存储引擎" class="headerlink" title="一、InnoDB存储引擎"></a>一、InnoDB存储引擎</h2><p>InnoDB是MySQL的默认事务型引擎，也是最重要、使用最广泛的存储引擎。它被设计 用来处理大量的短期(short-lived)事务，短期事务大部分情况是正常提交的，很少会被回滚。InnoDB的性能和自动崩溃恢复特性，使得它在非事务型存储的需求中也很流 行。除非有非常特别的原因需要使用其他的存储引擎，否则应该优先考虑InnoDB引擎。 □D＞如果要学习存储引擎，InnoDB也是一个非常好的值得花最多的时间去深入学习的对象， 收益肯定比将时间平均花在每个存储引擎的学习上要高得多。</p><h3 id="InnoDB的历史"><a href="#InnoDB的历史" class="headerlink" title="InnoDB的历史"></a>InnoDB的历史</h3><p>InnoDB有着复杂的发布历史，了解一下这段历史对于理解InnoDB很有帮助。2008年， 发布了所谓的InnoDB plugin,适用于MySQL 5.1版本，但这是Oracle创建的下一代 InnoDB引擎，其拥有者是InnoDB而不是MySQL。这基于很多原因，这些原因如果要一一道来，恐怕得喝掉好几桶啤酒。MySQL默认还是选择了集成旧的InnoDB引擎。当 然用户可以自行选择使用新的性能更好、扩展性更佳的InnoDB plugin来覆盖旧的版本。 直到最后，在Oracle收购了 Sun公司后发布的MySQL 5.5中才彻底使用InnoDB plugin 替代了旧版本的InnoDB (是的，这也意味着InnoDB plugin已经是原生编译了，而不是编译成一个插件，但名字已经约定俗成很难更改)。</p><p>这个现代的InnoDB版本，也就是MySQL 5.1中所谓的InnoDB plugin,支持一些新特性， 诸如利用排序创建索引(building index by sorting)、删除或者增加索引时不需要复制全表数据、新的支持压缩的存储格式、新的大型列值如BLOB的存储方式，以及文件格式管 理等。很多用户在MySQL 5.1中没有使用InnoDB plugin,或许是因为他们没有注意到有这个区别。所以如果你使用的是MySQL 5.1, 一定要使用InnoDB plugin,真的比旧版本的InnoDB要好很多。</p><p>InnoDB是一个很重要的存储引擎，很多个人和公司都对其贡献代码，而不仅仅是 Oracle公司的开发团队。一些重要的贡献者包括Google、Yasufumi Kinoshita、Percona,、Facebook等，他们的一些改进被直接移植到官方版本，也有一些由InnoDB团队重新实现。 在过去的几年间，InnoDB的改进速度大大加快，主要的改进集中在可测量性、可扩展性、 可配置化、性能、各种新特性和对Windows的支持等方面。MySQL 5.6实验室预览版 和里程碑版也包含了一系列重要的InnoDB新特性。</p><p>为改善InnoDB的性能，Oracle投入了大量的资源，并做了很多卓有成效的工作(外部贡献者对此也提供了很大的帮助)。在本书的第二版中，我们注意到在超过四核CPU的系统中InnoDB表现不佳，而现在已经可以很好地扩展至24核的系统，甚至在某些场景， 32核或者更多核的系统中也表现良好。很多改进将在即将发布的MySQL 5.6中引入， 当然也还有机会做更进一步的改善。</p><h3 id="InnoDB概览"><a href="#InnoDB概览" class="headerlink" title="InnoDB概览"></a>InnoDB概览</h3><p>InnoDB的数据存储在表空间(tablespace)中，表空间是由InnoDB管理的一个黑盒子， 由一系列的数据文件组成。在MySQL 4.1以后的版本中，InnoDB可以将每个表的数据 和索引存放在单独的文件中。InnoDB也可以使用裸设备作为表空间的存储介质，但现代的文件系统使得裸设备不再是必要的选择。</p><p>InnoDB采用MVCC来支持高并发，并且实现了四个标准的隔离级别。其默认级别是 REPEATABLE READ (可重复读)，并且通过间隙锁(next-key locking)策略防止幻读的出现。 间隙锁使得InnoDB不仅仅锁定査询涉及的行，还会对索引中的间隙进行锁定，以防止幻影行的插入。    -</p><p>InnoDB表是基于聚簇索引建立的，我们会在后面的章节详细讨论聚簇索引。IimoDB的 索引结构和MySQL的其他存储引擎有很大的不同，聚簇索引对主键査询有很髙的性能。 不过它的二级索引(secondary index,非主键索引)中必须包含主键列，所以如果主键 列很大的话，其他的所有索引都会很大。因此，若表上的索引较多的话，主键应当尽可 能的小。InnoDB的存储格式是平台独立的，也就是说可以将数据和索引文件从Intel平 台复制到PowerPC或者Sun SPARC平台。</p><p>InnoDB内部做了很多优化，包括从磁盘读取数据时釆用的可预测性预读，能够自动在 内存中创建hash索引以加速读操作的自适应哈希索引(adaptive hash index),以及能够 加速插入操作的插入缓冲区(insert buffer)等。本书后面将更详细地讨论这些内容。</p><p>InnoDB的行为是非常复杂的，不容易理解。如果使用了 InnoDB引擎，笔者强烈建议阅 读官方手册中的”InnoDB事务模型和锁” 一节。如果应用程序基于InnoDB构建，则事 先了解一下InnoDB的MVCC架构带来的一些微妙和细节之处是非常有必要的。存储引 擎要为所有用户甚至包括修改数据的用户维持一致性的视图，是非常复杂的工作。</p><p>作为事务型的存储引擎，InnoDB通过一些机制和工具支持真正的热备份，Oracle提供 的MySQL Enterprise Backup. Percona提供的开源的XtraBackup都可以做到这一点。 MySQL的其他存储引擎不支持热备份，要获取一致性视图需要停止对所有表的写入， 而在读写混合场景中，停止写入可能也意味着停止读取。</p><h2 id="MylSAM存储引擎"><a href="#MylSAM存储引擎" class="headerlink" title="MylSAM存储引擎"></a>MylSAM存储引擎</h2><p>在MySQL 5.1及之前的版本，MylSAM是默认的存储引擎。MylSAM提供了大量的特 性，包括全文索引、压缩、空间函数(GIS)等，但MylSAM不支持事务和行级锁，而且有一个毫无疑问的缺陷就是崩溃后无法安全恢复。正是由于MylSAM引擎的缘故，即 使MySQL支持事务已经很长时间了，在很多人的概念中MySQL还是非事务型的数据 库。尽管MylSAM引擎不支持事务、不支持崩溃后的安全恢复，但它绝不是一无是处的。对于只读的数据，或者表比较小、可以忍受修复（repair）操作，则依然可以继续使 用MylSAM （但请不要默认使用MylSAM,而是应当默认使用InnoDB）。</p><h3 id="存储"><a href="#存储" class="headerlink" title="存储"></a>存储</h3><p>MylSAM会将表存储在两个文件中：数据文件和索引文件，分别以.MYD和.MYI为扩展名。MylSAM表可以包含动态或者静态（长度固定）行。MySQL会根据表的定义来 决定采用何种行格式。MylSAM表可以存储的行记录数，一般受限于可用的磁盘空间， 或者操作系统中单个文件的最大尺寸。</p><p>在MySQL 5.0中，MylSAM表如果是变长行，则默认配置只能处理256TB的数据，因 .为指向数据记录的指针长度是6个字节。而在更早的版本中，指针长度默认是4字节，所以只能处理4GB的数据。而所有的MySQL版本都支持8字节的指针。要改变 MylSAM表指针的长度（调高或者调低），可以通过修改表的MAX_R0WS和AVG_R0W_ LENGTH选项的值来实现，两者相乘就是表可能达到的最大大小。修改这两个参数会导致 重建整个表和表的所有索引，这可能需要很长的时间才能完成。</p><h3 id="MylSAM特性"><a href="#MylSAM特性" class="headerlink" title="MylSAM特性"></a>MylSAM特性</h3><p>作为MySQL最早的存储引擎之一，MylSAM有一些已经开发出来很多年的特性，可以 满足用户的实际需求。</p><h4 id="加锁与并发"><a href="#加锁与并发" class="headerlink" title="加锁与并发"></a>加锁与并发</h4><p>MylSAM对整张表加锁，而不是针对行。读取时会对需要读到的所有表加共享锁， 写入时则对表加排他锁。但是在表有读取査询的同时，也可以往表中插入新的记录 （这被称为并发插入，CONCURRENT INSERT） o</p><h4 id="修复"><a href="#修复" class="headerlink" title="修复"></a>修复</h4><p>对于MylSAM表，MySQL可以手工或者自动执行检査和修复操作，但这里说的修 复和事务.恢复以及崩溃恢复是不同的概念。执行表的修复可能导致一些数据丢失， 而且修复操作是非常慢的。可以通过CHECK TABLE mytable检査表的错误，如果有 错误可以通过执行REPAIR TABLE mytable进行修复。另外，如果MySQL服务器已 经关闭，也可以通过<strong>myisamchk</strong>命令行工具进行检査和修复操作。</p><h4 id="索引特性"><a href="#索引特性" class="headerlink" title="索引特性"></a>索引特性</h4><p>对于MylSAM表，即使是BLOB和TEXT等长字段，也可以基于其前500个字符创建 </p><p>索引。MylSAM也支持全文索引，这是一种基于分词创建的索引，可以支持复杂的 査询。</p><h4 id="延迟更新索引键-Delayed-Key-Write"><a href="#延迟更新索引键-Delayed-Key-Write" class="headerlink" title="延迟更新索引键(Delayed Key Write)"></a>延迟更新索引键(Delayed Key Write)</h4><p>创建MylSAM表的时候，如果指定了 DELAY_KEY_WRITE选项，在每次修改执行完成 时，不会立刻将修改的索引数据写入磁盘，而是会写到内存中的键缓冲区(in.memory key buffer),只有在清理键缓冲区或者关闭表的时候才会将对应的索引块写入到磁 盘。这种方式可以极大地提升写入性能，但是在数据库或者主机崩溃时会造成索引 损坏，需要执行修复操作。延迟更新索引键的特性，可以在全局设置，也可以为单 个表设置。</p><h3 id="MylSAM压缩表"><a href="#MylSAM压缩表" class="headerlink" title="MylSAM压缩表"></a>MylSAM压缩表</h3><p>如果表在创建并导入数据以后，不会再进行修改操作，那么这样的表或许适合釆用- MylSAM压缩表。</p><p>可以使用<strong>myisampack</strong>对MylSAM表进行压缩(也叫打包pack)o压缩表是不能进行修 改的(除非先将表解除压缩，修改数据，然后再次压缩)。压缩表可以极大地减少磁盘 空间占用，因此也可以减少磁盘I/O,从而提升査询性能。压缩表也支持索引，但索引 也是只读的。</p><p>以现在的硬件能力，对大多数应用场景，读取压缩表数据时的解压带来的开销影响并不 大，而减少I/O带来的好处则要大得多。压缩时表中的记录是独立压缩的，所以读取单 行的时候不需要去解压整个表(甚至也不解压行所在的整个页面)。</p><h3 id="MylSAM性能"><a href="#MylSAM性能" class="headerlink" title="MylSAM性能"></a>MylSAM性能</h3><p>MylSAM引擎设计简单，数据以紧密格式存储，所以在某些场景下的性能很好。 MylSAM有一些服务器级别的性能扩展限制，比如对索引键缓冲区(key cache)的 Mutex锁，MariaDB基于段(segment)的索引键缓冲区机制来避免该问题。但MylSAM 最典型的性能问题还是表锁的问题，如果你发现所有的査询都长期处于“Locked”状态， 那么毫无疑问表锁就是罪魁祸首。</p><h1 id="MySQL内建的其他存储引擎"><a href="#MySQL内建的其他存储引擎" class="headerlink" title="MySQL内建的其他存储引擎"></a>MySQL内建的其他存储引擎</h1><p>MySQL还有一些有特殊用途的存储引擎。在新版本中，有些可能因为一些原因已经不 再支持;另外还有些会继续支持，但是需要明确地启用后才能使用。</p><h2 id="Archive-引擎"><a href="#Archive-引擎" class="headerlink" title="Archive 引擎"></a>Archive 引擎</h2><p>Archive存储引擎只支持INSERT和SELECT操作，在MySQL 5.1之前也不支持索引。</p><p>Archive引擎会缓存所有的写并利用<strong>zlib</strong>对插入的行进行压缩，所以比MylSAM表的磁 盘I/O更少。但是每次SELECT査询都需要执行全表扫描。所以Archive表适合日志和 数据釆集类应用，这类应用做数据分析时往往需要全表扫描。或者在一些需要更快速的 INSERT操作的场合下也可以使用。</p><p>Archive引擎支持行级锁和专用的缓冲区，所以可以实现高并发的插入。在一个査询开 始直到返回表中存在的所有行数之前，Archive引擎会阻止其他的SELECT执行，以实现 一致性读。另外，也实现了批量插入在完成之前对读操作是不可见的。这种机制模仿了 事务和MVCC的一些特性，但Archive引擎不是一个事务型的引擎，而是一个针对高速 插入和压缩做了优化的简单引擎。</p><h2 id="Blackhole-引擎"><a href="#Blackhole-引擎" class="headerlink" title="Blackhole 引擎"></a>Blackhole 引擎</h2><p>Blackhole引擎没有实现任何的存储机制，它会丢弃所有插入的数据，不做任何保存。但 是服务器会记录Blackhole表的日志，所以可以用于复制数据到备库，或者只是简单地 记录到日志。这种特殊的存储引擎可以在一些特殊的复制架构和日志审核时发挥作用。 但这种应用方式我们碰到过很多问题，因此并不推荐。</p><h2 id="CSV引擎"><a href="#CSV引擎" class="headerlink" title="CSV引擎"></a>CSV引擎</h2><p>CSV引擎可以将普通的CSV文件（逗号分割值的文件）作为MySQL的表来处理，但 这种表不支持索引。CSV引擎可以在数据库运行时拷入或者拷出文件。可以将Excel 等电子表格软件中的数据存储为CSV文件，然后复制到MySQL数据目录下，就能在 MySQL中打开使用。同样，如果将数据写入到一个CSV引擎表，其他的外部程序也能 立即从表的数据文件中读取csv格式的数据。因此CSV引擎可以作为一种数据交换的 机制，非常有用。</p><h2 id="Federated-引擎"><a href="#Federated-引擎" class="headerlink" title="Federated 引擎"></a>Federated 引擎</h2><p>Federated引擎是访问其他MySQL服务器的一个代理，它会创建一个到远程MySQL服 务器的客户端连接，并将査询传输到远程服务器执行，然后提取或者发送需要的数据。 最初设计该存储引擎是为了和企业级数据库如Microsoft SQL Server和Oracle的类似特 性竞争的，可以说更多的是一种市场行为。尽管该引擎看起来提供了一种很好的跨服务 器的灵活性，但也经常带来问题，因此默认是禁用的。MariaDB使用了它的一个后续改 进版本，叫做FederatedXo</p><h2 id="Memory引擎"><a href="#Memory引擎" class="headerlink" title="Memory引擎"></a>Memory引擎</h2><p>如果需要快速地访问数据，并且这些数据不会被修改，重启以后丢失也没有关系，那么 使用Memory表(以前也叫做HEAP表)是非常有用的。Memory表至少比MylSAM表 要快一个数量级，因为所有的数据都保存在内存中，不需要进行磁盘I/O。Memory表的 结构在重启以后还会保留，但数据会丢失。</p><p>Memroy表在很多场景可以发挥好的作用：</p><ul><li><p>用于査找(lookup)或者映射(mapping)表，例如将邮编和州名映射的表。</p></li><li><p>用于缓存周期性聚合数据(periodically aggregated data)的结果。</p></li><li><p>用于保存数据分析中产生的中间数据。</p></li></ul><p>Memory表支持Hash索引，因此査找操作非常快。虽然Memory表的速度非常快，但还 是无法取代传统的基于磁盘的表。Memroy表是表级锁，因此并发写入的性能较低。它 不支持BLOB或TEXT类型的列，并且每行的长度是固定的，所以即使指定了 VARCHAR列， 实际存储时也会转换成CHAR,这可能导致部分内存的浪费(其中一些限制在Percona版 本已经解决)。</p><p>如果MySQL在执行査询的过程中需要使用临时表来保存中间结果，内部使用的临时表 就是Memory表。如果中间结果太大超出了 Memory表的限制，或者含有BLOB或TEXT 字段，则临时表会转换成MylSAM表。在后续的章节还会继续讨论该问题。</p><p>人们经常混淆<strong>Memory</strong>表和临时表。临时表是指使用<strong>CREATE TEMPORARY TABLE</strong>语句 创建的表，它可以使用任何存储引擎，因此和<strong>Memory</strong>表不是一回事。临时表只在 单个连接中可见，当连接断开时，临时表也将不复存在。</p><h2 id="Merge引擎"><a href="#Merge引擎" class="headerlink" title="Merge引擎"></a>Merge引擎</h2><p>Merge引擎是MylSAM引擎的一个变种。Merge表是由多个MylSAM表合并而来的虚 拟表。如果将MySQL用于日志或者数据仓库类应用，该引擎可以发挥作用。但是引入 分区功能后，该引擎已经被放弃(参考第7章)。</p><h2 id="NDB集群引擎"><a href="#NDB集群引擎" class="headerlink" title="NDB集群引擎"></a>NDB集群引擎</h2><p>2003年，当时的MySQL AB公司从索尼爱立信公司收购了 NDB数据库，然后开发了 NDB集群存储引擎，作为SQL和NDB原生协议之间的接口。MySQL服务器、NDB集 群存储引擎，以及分布式的、share.nothing的、容灾的、高可用的NDB数据库的组合， 被称为MySQL集群(MySQL Cluster) </p><h2 id="第三方存储引擎"><a href="#第三方存储引擎" class="headerlink" title="第三方存储引擎"></a>第三方存储引擎</h2><p>OLTP类引擎等不多介绍了。</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;MySQL中的数据用各种不同的技术存储在文件（或者内存）中。这些技术中的每一种技术都使用不同的存储机制、索引技巧、锁定水平并且最终提供广泛的不同的功能和能力。通过选择不同的技术，你能够获得额外的速度或者功能，从而改善你的应用的整体功能。这些不同的技术以及配套的相关功能在My
      
    
    </summary>
    
    
      <category term="MySQL" scheme="https://www.cicoding.cn/categories/MySQL/"/>
    
    
      <category term="MySQL" scheme="https://www.cicoding.cn/tags/MySQL/"/>
    
      <category term="索引" scheme="https://www.cicoding.cn/tags/%E7%B4%A2%E5%BC%95/"/>
    
  </entry>
  
  <entry>
    <title>MySQL之索引原理分析</title>
    <link href="https://www.cicoding.cn/mysql/index-principle-of-mysql/"/>
    <id>https://www.cicoding.cn/mysql/index-principle-of-mysql/</id>
    <published>2021-06-28T04:27:35.000Z</published>
    <updated>2025-03-05T14:22:17.762Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>一步一步推导出 Mysql 索引的底层数据结构。</p></blockquote><p>Mysql 作为互联网中非常热门的数据库，其底层的存储引擎和数据检索引擎的设计非常重要，尤其是 Mysql 数据的存储形式以及索引的设计，决定了 Mysql 整体的数据检索性能。</p><p>我们知道，索引的作用是做数据的快速检索，而快速检索的实现的本质是数据结构。通过不同数据结构的选择，实现各种数据快速检索。在数据库中，高效的查找算法是非常重要的，因为数据库中存储了大量数据，一个高效的索引能节省巨大的时间。比如下面这个数据表，如果 Mysql 没有实现索引算法，那么查找 id=7 这个数据，那么只能采取暴力顺序遍历查找，找到 id=7 这个数据需要比较 7 次，如果这个表存储的是 1000W 个数据，查找 id=1000W 这个数据那就要比较 1000W 次，这种速度是不能接受的。</p><p><img src="/images/mysql/v2-5141ed6f1bddd61750763b51bdc9ecb4_r.jpg" alt="MySQL之索引原理分析"></p><h2 id="一、Mysql-索引底层数据结构选型"><a href="#一、Mysql-索引底层数据结构选型" class="headerlink" title="一、Mysql 索引底层数据结构选型"></a><strong>一、Mysql 索引底层数据结构选型</strong></h2><ol><li>哈希表（Hash）</li></ol><p>哈希表是做数据快速检索的有效利器。</p><p>哈希算法：也叫散列算法，就是把任意值(key)通过哈希函数变换为固定长度的 key 地址，通过这个地址进行具体数据的数据结构。</p><p><img src="/images/mysql/v2-7805f7b4bab4c98adc045e3667046398_r.jpg" alt="MySQL之索引原理分析"></p><p>考虑这个数据库表 user，表中一共有 7 个数据，我们需要检索 id=7 的数据，SQL 语法是：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">select \* from user where id=7;</span><br></pre></td></tr></table></figure><p>哈希算法首先计算存储 id=7 的数据的物理地址 addr=hash(7)=4231，而 4231 映射的物理地址是 0x77，0x77 就是 id=7 存储的额数据的物理地址，通过该独立地址可以找到对应 user_name=’g’这个数据。这就是哈希算法快速检索数据的计算过程。</p><p>但是哈希算法有个数据碰撞的问题，也就是哈希函数可能对不同的 key 会计算出同一个结果，比如 hash(7)可能跟 hash(199)计算出来的结果一样，也就是不同的 key 映射到同一个结果了，这就是碰撞问题。解决碰撞问题的一个常见处理方式就是链地址法，即用链表把碰撞的数据接连起来。计算哈希值之后，还需要检查该哈希值是否存在碰撞数据链表，有则一直遍历到链表尾，直达找到真正的 key 对应的数据为止。</p><p><img src="/images/mysql/v2-4deae667d7d5c9a1a166cb0e8bac9dd6_r.jpg" alt="MySQL之索引原理分析"></p><p><img src="/images/mysql/v2-df9820ea9f7146d09af8280040f185f5_r.jpg" alt="MySQL之索引原理分析"></p><p>从算法时间复杂度分析来看，哈希算法时间复杂度为 O（1），检索速度非常快。比如查找 id=7 的数据，哈希索引只需要计算一次就可以获取到对应的数据，检索速度非常快。但是 Mysql 并没有采取哈希作为其底层算法，这是为什么呢？</p><p>因为考虑到数据检索有一个常用手段就是范围查找，比如以下这个 SQL 语句：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">select \* from user where id \&gt;3;</span><br></pre></td></tr></table></figure><p>针对以上这个语句，我们希望做的是找出 id&gt;3 的数据，这是很典型的范围查找。如果使用哈希算法实现的索引，范围查找怎么做呢？一个简单的思路就是一次把所有数据找出来加载到内存，然后再在内存里筛选筛选目标范围内的数据。但是这个范围查找的方法也太笨重了，没有一点效率而言。</p><p>所以，使用哈希算法实现的索引虽然可以做到快速检索数据，但是没办法做数据高效范围查找，因此哈希索引是不适合作为 Mysql 的底层索引的数据结构。</p><ol><li>二叉查找树(BST)</li></ol><p>二叉查找树是一种支持数据快速查找的数据结构，如图下所示:</p><p><img src="/images/mysql/v2-032790aff0ddf52b676413573acce776_r.jpg" alt="MySQL之索引原理分析"></p><p>二叉查找树的时间复杂度是 O(lgn)，比如针对上面这个二叉树结构，我们需要计算比较 3 次就可以检索到 id=7 的数据，相对于直接遍历查询省了一半的时间，从检索效率上看来是能做到高速检索的。此外二叉树的结构能不能解决哈希索引不能提供的范围查找功能呢？</p><p>答案是可以的。观察上面的图，二叉树的叶子节点都是按序排列的，从左到右依次升序排列，如果我们需要找 id&gt;5 的数据，那我们取出节点为 6 的节点以及其右子树就可以了，范围查找也算是比较容易实现。</p><p>但是普通的二叉查找树有个致命缺点：极端情况下会退化为线性链表，二分查找也会退化为遍历查找，时间复杂退化为 O（N），检索性能急剧下降。比如以下这个情况，二叉树已经极度不平衡了，已经退化为链表了，检索速度大大降低。此时检索 id=7 的数据的所需要计算的次数已经变为 7 了。</p><p><img src="/images/mysql/v2-1cc416d59d4c44cf029e9e2103347bb8_r.jpg" alt="MySQL之索引原理分析"></p><p>在数据库中，数据的自增是一个很常见的形式，比如一个表的主键是 id，而主键一般默认都是自增的，如果采取二叉树这种数据结构作为索引，那上面介绍到的不平衡状态导致的线性查找的问题必然出现。因此，简单的二叉查找树存在不平衡导致的检索性能降低的问题，是不能直接用于实现 Mysql 底层索引的。</p><ol><li>AVL 树和红黑树</li></ol><p>二叉查找树存在不平衡问题，因此学者提出通过树节点的自动旋转和调整，让二叉树始终保持基本平衡的状态，就能保持二叉查找树的最佳查找性能了。基于这种思路的自调整平衡状态的二叉树有 AVL 树和红黑树。</p><p>首先简单介绍红黑树，这是一颗会自动调整树形态的树结构，比如当二叉树处于一个不平衡状态时，红黑树就会自动左旋右旋节点以及节点变色，调整树的形态，使其保持基本的平衡状态（时间复杂度为 O（logn）），也就保证了查找效率不会明显减低。比如从 1 到 7 升序插入数据节点，如果是普通的二叉查找树则会退化成链表，但是红黑树则会不断调整树的形态，使其保持基本平衡状态，如下图所示。下面这个红黑树下查找 id=7 的所要比较的节点数为 4，依然保持二叉树不错的查找效率。</p><p>红黑树拥有不错的平均查找效率，也不存在极端的 O(n)情况，那红黑树作为 Mysql 底层索引实现是否可以呢？其实红黑树也存在一些问题，观察下面这个例子。</p><p>红黑树顺序插入 1~7 个节点，查找 id=7 时需要计算的节点数为 4。</p><p><img src="/images/mysql/v2-46e7e44e8ba85e606d68ea9644092d08_r.jpg" alt="MySQL之索引原理分析"></p><p>红黑树顺序插入 1~16 个节点，查找 id=16 需要比较的节点数为 6 次。观察一下这个树的形态，是不是当数据是顺序插入时，树的形态一直处于“右倾”的趋势呢？从根本上上看，红黑树并没有完全解决二叉查找树虽然这个“右倾”趋势远没有二叉查找树退化为线性链表那么夸张，但是数据库中的基本主键自增操作，主键一般都是数百万数千万的，如果红黑树存在这种问题，对于查找性能而言也是巨大的消耗，我们数据库不可能忍受这种无意义的等待的。</p><p><img src="/images/mysql/v2-60cea4af963b156ee5f40030977ab77c_r.jpg" alt="MySQL之索引原理分析"></p><p>现在考虑另一种更为严格的自平衡二叉树 AVL 树。因为 AVL 树是个绝对平衡的二叉树，因此他在调整二叉树的形态上消耗的性能会更多。</p><p>AVL 树顺序插入 1~7 个节点，查找 id=7 所要比较节点的次数为 3。</p><p><img src="/images/mysql/v2-4c39b8eacc6879d661ddb1ed4190aff2_r.jpg" alt="MySQL之索引原理分析"></p><p>AVL 树顺序插入 1~16 个节点，查找 id=16 需要比较的节点数为 4。从查找效率而言，AVL 树查找的速度要高于红黑树的查找效率（AVL 树是 4 次比较，红黑树是 6 次比较）。从树的形态看来，AVL 树不存在红黑树的“右倾”问题。也就是说，大量的顺序插入不会导致查询性能的降低，这从根本上解决了红黑树的问题。</p><p><img src="/images/mysql/v2-0dad51645707e973b152e44e4b7479c5_r.jpg" alt="MySQL之索引原理分析"></p><p>总结一下 AVL 树的优点：</p><ol><li>不错的查找性能（O（logn）），不存在极端的低效查找的情况。</li><li>可以实现范围查找、数据排序。</li></ol><p>看起来 AVL 树作为数据查找的数据结构确实很不错，但是 AVL 树并不适合做 Mysql 数据库的索引数据结构，因为考虑一下这个问题：</p><p>数据库查询数据的瓶颈在于磁盘 IO，如果使用的是 AVL 树，我们每一个树节点只存储了一个数据，我们一次磁盘 IO 只能取出来一个节点上的数据加载到内存里，那比如查询 id=7 这个数据我们就要进行磁盘 IO 三次，这是多么消耗时间的。所以我们设计数据库索引时需要首先考虑怎么尽可能减少磁盘 IO 的次数。</p><p>磁盘 IO 有个有个特点，就是从磁盘读取 1B 数据和 1KB 数据所消耗的时间是基本一样的，我们就可以根据这个思路，我们可以在一个树节点上尽可能多地存储数据，一次磁盘 IO 就多加载点数据到内存，这就是 B 树，B+树的的设计原理了。</p><ol><li>B 树</li></ol><p>下面这个 B 树，每个节点限制最多存储两个 key，一个节点如果超过两个 key 就会自动分裂。比如下面这个存储了 7 个数据 B 树，只需要查询两个节点就可以知道 id=7 这数据的具体位置，也就是两次磁盘 IO 就可以查询到指定数据，优于 AVL 树。</p><p><img src="/images/mysql/v2-f508590121487b595c4ad4fa83d8aa15_r.jpg" alt="MySQL之索引原理分析"></p><p>下面是一个存储了 16 个数据的 B 树，同样每个节点最多存储 2 个 key，查询 id=16 这个数据需要查询比较 4 个节点，也就是经过 4 次磁盘 IO。看起来查询性能与 AVL 树一样。</p><p><img src="/images/mysql/v2-f335bdb3e922a5f334416f557df20848_r.jpg" alt="MySQL之索引原理分析"></p><p>但是考虑到磁盘 IO 读一个数据和读 100 个数据消耗的时间基本一致，那我们的优化思路就可以改为：尽可能在一次磁盘 IO 中多读一点数据到内存。这个直接反映到树的结构就是，每个节点能存储的 key 可以适当增加。</p><p>当我们把单个节点限制的 key 个数设置为 6 之后，一个存储了 7 个数据的 B 树，查询 id=7 这个数据所要进行的磁盘 IO 为 2 次。</p><p><img src="/images/mysql/v2-0fdff8f9a516383548cd0e636f593e52_r.jpg" alt="MySQL之索引原理分析"></p><p>一个存储了 16 个数据的 B 树，查询 id=7 这个数据所要进行的磁盘 IO 为 2 次。相对于 AVL 树而言磁盘 IO 次数降低为一半。</p><p><img src="/images/mysql/v2-7d5e34c698b1e4192ad0ff93c2c897d0_720w.jpg" alt="MySQL之索引原理分析"></p><p>所以数据库索引数据结构的选型而言，B 树是一个很不错的选择。总结来说，B 树用作数据库索引有以下优点：</p><ol><li>优秀检索速度，时间复杂度：B 树的查找性能等于 O（h*logn），其中 h 为树高，n 为每个节点关键词的个数；</li><li>尽可能少的磁盘 IO，加快了检索速度；</li><li>可以支持范围查找。</li><li>B+树</li></ol><p>B 树和 B+树有什么不同呢？</p><p>第一，B 树一个节点里存的是数据，而 B+树存储的是索引（地址），所以 B 树里一个节点存不了很多个数据，但是 B+树一个节点能存很多索引，B+树叶子节点存所有的数据。</p><p>第二，B+树的叶子节点是数据阶段用了一个链表串联起来，便于范围查找。</p><p><img src="/images/mysql/v2-bda6661499c51dcff63eb12fd4b3795d_r.jpg" alt="MySQL之索引原理分析"></p><p>通过 B 树和 B+树的对比我们看出，B+树节点存储的是索引，在单个节点存储容量有限的情况下，单节点也能存储大量索引，使得整个 B+树高度降低，减少了磁盘 IO。其次，B+树的叶子节点是真正数据存储的地方，叶子节点用了链表连接起来，这个链表本身就是有序的，在数据范围查找时，更具备效率。因此 Mysql 的索引用的就是 B+树，B+树在查找效率、范围查找中都有着非常不错的性能。</p><h2 id="二、Innodb-引擎和-Myisam-引擎的实现"><a href="#二、Innodb-引擎和-Myisam-引擎的实现" class="headerlink" title="二、Innodb 引擎和 Myisam 引擎的实现"></a><strong>二、Innodb 引擎和 Myisam 引擎的实现</strong></h2><p>Mysql 底层数据引擎以插件形式设计，最常见的是 Innodb 引擎和 Myisam 引擎，用户可以根据个人需求选择不同的引擎作为 Mysql 数据表的底层引擎。我们刚分析了，B+树作为 Mysql 的索引的数据结构非常合适，但是数据和索引到底怎么组织起来也是需要一番设计，设计理念的不同也导致了 Innodb 和 Myisam 的出现，各自呈现独特的性能。</p><p>MyISAM 虽然数据查找性能极佳，但是不支持事务处理。Innodb 最大的特色就是支持了 ACID 兼容的事务功能，而且他支持行级锁。Mysql 建立表的时候就可以指定引擎，比如下面的例子，就是分别指定了 Myisam 和 Innodb 作为 user 表和 user2 表的数据引擎。</p><p><img src="/images/mysql/v2-e4e24288dca3ef02735c41a2a3a69556_r.jpg" alt="MySQL之索引原理分析"></p><p><img src="/images/mysql/v2-0df18cce6c3871fa9e4ac99050bf1692_r.jpg" alt="MySQL之索引原理分析"></p><p>执行这两个指令后，系统出现了以下的文件，说明这两个引擎数据和索引的组织方式是不一样的。</p><p><img src="/images/mysql/v2-8a065d6e21a2adbf06d2e6b5dd02e969_r.jpg" alt="MySQL之索引原理分析"></p><p>Innodb 创建表后生成的文件有：</p><ul><li>frm:创建表的语句</li><li>idb:表里面的数据+索引文件</li></ul><p>Myisam 创建表后生成的文件有</p><ul><li>frm:创建表的语句</li><li>MYD:表里面的数据文件（myisam data）</li><li>MYI:表里面的索引文件（myisam index）</li></ul><p>从生成的文件看来，这两个引擎底层数据和索引的组织方式并不一样，MyISAM 引擎把数据和索引分开了，一人一个文件，这叫做非聚集索引方式；Innodb 引擎把数据和索引放在同一个文件里了，这叫做聚集索引方式。下面将从底层实现角度分析这两个引擎是怎么依靠 B+树这个数据结构来组织引擎实现的。</p><ol><li>MyISAM 引擎的底层实现（非聚集索引方式）</li></ol><p>MyISAM 用的是非聚集索引方式，即数据和索引落在不同的两个文件上。MyISAM 在建表时以主键作为 KEY 来建立主索引 B+树，树的叶子节点存的是对应数据的物理地址。我们拿到这个物理地址后，就可以到 MyISAM 数据文件中直接定位到具体的数据记录了。</p><p><img src="/images/mysql/v2-d9a03627e8e1319e46f42e6963c35e30_r.jpg" alt="MySQL之索引原理分析"></p><p>当我们为某个字段添加索引时，我们同样会生成对应字段的索引树，该字段的索引树的叶子节点同样是记录了对应数据的物理地址，然后也是拿着这个物理地址去数据文件里定位到具体的数据记录。</p><ol><li>Innodb 引擎的底层实现（聚集索引方式）</li></ol><p>InnoDB 是聚集索引方式，因此数据和索引都存储在同一个文件里。首先 InnoDB 会根据主键 ID 作为 KEY 建立索引 B+树，如左下图所示，而 B+树的叶子节点存储的是主键 ID 对应的数据，比如在执行 select * from user_info where id=15 这个语句时，InnoDB 就会查询这颗主键 ID 索引 B+树，找到对应的 user_name=’Bob’。</p><p>这是建表的时候 InnoDB 就会自动建立好主键 ID 索引树，这也是为什么 Mysql 在建表时要求必须指定主键的原因。当我们为表里某个字段加索引时 InnoDB 会怎么建立索引树呢？比如我们要给 user_name 这个字段加索引，那么 InnoDB 就会建立 user_name 索引 B+树，节点里存的是 user_name 这个 KEY，叶子节点存储的数据的是主键 KEY。注意，叶子存储的是主键 KEY！拿到主键 KEY 后，InnoDB 才会去主键索引树里根据刚在 user_name 索引树找到的主键 KEY 查找到对应的数据。</p><p><img src="/images/mysql/v2-6e16b355e3d0f05ed8bfb0f7c71de8f1_r.jpg" alt="MySQL之索引原理分析"></p><p>问题来了，为什么 InnoDB 只在主键索引树的叶子节点存储了具体数据，但是其他索引树却不存具体数据呢，而要多此一举先找到主键，再在主键索引树找到对应的数据呢?</p><p>其实很简单，因为 InnoDB 需要节省存储空间。一个表里可能有很多个索引，InnoDB 都会给每个加了索引的字段生成索引树，如果每个字段的索引树都存储了具体数据，那么这个表的索引数据文件就变得非常巨大（数据极度冗余了）。从节约磁盘空间的角度来说，真的没有必要每个字段索引树都存具体数据，通过这种看似“多此一举”的步骤，在牺牲较少查询的性能下节省了巨大的磁盘空间，这是非常有值得的。</p><p>在进行 InnoDB 和 MyISAM 特点对比时谈到，MyISAM 查询性能更好，从上面索引文件数据文件的设计来看也可以看出原因：MyISAM 直接找到物理地址后就可以直接定位到数据记录，但是 InnoDB 查询到叶子节点后，还需要再查询一次主键索引树，才可以定位到具体数据。等于 MyISAM 一步就查到了数据，但是 InnoDB 要两步，那当然 MyISAM 查询性能更高。</p><p>本文首先探讨了哪种数据结构更适合作为 Mysql 底层索引的实现，然后再介绍了 Mysql 两种经典数据引擎 MyISAM 和 InnoDB 的底层实现。最后再总结一下什么时候需要给你的表里的字段加索引吧：</p><ol><li>较频繁的作为查询条件的字段应该创建索引；</li><li>唯一性太差的字段不适合单独创建索引，即使该字段频繁作为查询条件；</li><li>更新非常频繁的字段不适合创建索引。</li></ol><p>原文链接：<br><a href="https://zhuanlan.zhihu.com/p/113917726" target="_blank" rel="noopener">https://zhuanlan.zhihu.com/p/113917726</a></p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;blockquote&gt;
&lt;p&gt;一步一步推导出 Mysql 索引的底层数据结构。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Mysql 作为互联网中非常热门的数据库，其底层的存储引擎和数据检索引擎的设计非常重要，尤其是 Mysql 数据的存储形式以及索引的设计，决定了 Mysql
      
    
    </summary>
    
    
      <category term="MySQL" scheme="https://www.cicoding.cn/categories/MySQL/"/>
    
    
      <category term="MySQL" scheme="https://www.cicoding.cn/tags/MySQL/"/>
    
      <category term="索引" scheme="https://www.cicoding.cn/tags/%E7%B4%A2%E5%BC%95/"/>
    
      <category term="原理" scheme="https://www.cicoding.cn/tags/%E5%8E%9F%E7%90%86/"/>
    
  </entry>
  
  <entry>
    <title>MySQL索引在什么情况下会失效</title>
    <link href="https://www.cicoding.cn/mysql/under-what-circumstances-will-mysql-index-fail/"/>
    <id>https://www.cicoding.cn/mysql/under-what-circumstances-will-mysql-index-fail/</id>
    <published>2021-06-28T01:27:35.000Z</published>
    <updated>2025-03-05T14:22:17.763Z</updated>
    
    <content type="html"><![CDATA[<p>索引的失效，会大大降低sql的执行效率，日常中又有哪些常见的情况会导致索引失效？</p><ol><li>对查询进行优化，应尽量避免全表扫描，首先应考虑在 where 及 order by 涉及的列上建立索引。</li><li>应尽量避免在 where 子句中对字段进行 null 值判断，否则将导致引擎放弃使用索引而进行全表扫描，如：</li></ol><blockquote><p>select id from t where num is null</p></blockquote><p>可以在num上设置默认值0，确保表中num列没有null值，然后这样查询：</p><blockquote><p>select id from t where num=0  </p></blockquote><ol start="3"><li>应尽量避免在 where 子句中使用!=或&lt;&gt;操作符，否则将引擎放弃使用索引而进行全表扫描。</li><li>应尽量避免在 where 子句中使用 or 来连接条件，否则将导致引擎放弃使用索引而进行全表扫描，如果or的所有字段都有索引还是会走索迎查询，如：</li></ol><blockquote><p>select id from t where num=10 or num=20</p></blockquote><p>可以这样查询：</p><blockquote><p>select id from t where num=10<br>union all<br>select id from t where num=20 </p></blockquote><ol start="5"><li>in 和 not in 也要慎用，否则会导致全表扫描，如：</li></ol><blockquote><p>select id from t where num in(1,2,3)  </p></blockquote><p>对于连续的数值，能用 between 就不要用 in 了：</p><blockquote><p>select id from t where num between 1 and 3 </p></blockquote><ol start="6"><li>下面的查询也将导致全表扫描：</li></ol><blockquote><p>select id from t where name like ‘%abc%’  和 select id from t where name like ‘%abc’ </p></blockquote><p>只有like abc% 索引才有效，若要提高效率，可以考虑全文检索</p><ol start="7"><li>如果在 where 子句中使用参数，也会导致全表扫描。因为SQL只有在运行时才会解析局部变量，但优化程序不能将访问计划的选择推迟到运行时；它必须在编译时进行选择。然 而，如果在编译时建立访问计划，变量的值还是未知的，因而无法作为索引选择的输入项。如下面语句将进行全表扫描：</li></ol><blockquote><p>select id from t where num=@num </p></blockquote><p>可以改为强制查询使用索引：</p><blockquote><p>select id from t with(index(索引名)) where num=@num  </p></blockquote><ol start="8"><li>应尽量避免在 where 子句中对字段进行表达式操作，这将导致引擎放弃使用索引而进行全表扫描。如：</li></ol><blockquote><p>select id from t where num/2=100  </p></blockquote><p>应改为:</p><blockquote><p>select id from t where num=100*2  </p></blockquote><ol start="9"><li>应尽量避免在where子句中对字段进行函数操作，这将导致引擎放弃使用索引而进行全表扫描。如：</li></ol><blockquote><p>select id from t where substring(name,1,3)=’abc’–name以abc开头的id<br>select id from t where datediff(day,createdate,’2005-11-30’)=0–‘2005-11-30’生成的id  </p></blockquote><p>应改为:</p><blockquote><p>select id from t where name like ‘abc%’  </p><p>select id from t where createdate&gt;=’2005-11-30’ and createdate&lt;’2005-12-1’ </p></blockquote><ol start="10"><li><p>不要在 where 子句中的“=”左边进行函数、算术运算或其他表达式运算，否则系统将可能无法正确使用索引。</p></li><li><p>在使用索引字段作为条件时，如果该索引是复合索引，那么必须使用到该索引中的第一个字段作为条件时才能保证系统使用该索引，否则该索引将不会被使用，并且应尽可能的让字段顺序与索引顺序相一致。<br>对于复合索引：Mysql从左到右的使用索引中的字段，一个查询可以只使用索引中的一部份，但只能是最左侧部分。</p></li></ol><p>例如索引是key index （a,b,c）。 可以支持a | a,b| a,b,c 3种组合进行查找，但不支持 b,c进行查找 .当最左侧字段是常量引用时，索引就十分有效</p><ol start="12"><li>不要写一些没有意义的查询，如需要生成一个空表结构：</li></ol><blockquote><p>select col1,col2 into #t from t where 1=0 </p></blockquote><p>这类代码不会返回任何结果集，但是会消耗系统资源的，应改成这样：</p><blockquote><p>create table #t(…)  </p></blockquote><ol start="13"><li>很多时候用 exists 代替 in 是一个好的选择：</li></ol><blockquote><p>select num from a where num in(select num from b)  </p></blockquote><p>用下面的语句替换：</p><blockquote><p>select num from a where exists(select 1 from b where num=a.num) </p></blockquote><ol start="14"><li>并不是所有索引对查询都有效，SQL是根据表中数据来进行查询优化的，当索引列有大量数据重复时，SQL查询可能不会去利用索引，如一表中有字段sex，male、female几乎各一半，那么即使在sex上建了索引也对查询效率起不了作用。</li></ol><p>原文链接：<a href="https://blog.csdn.net/BestDD/article/details/113359124" target="_blank" rel="noopener">https://blog.csdn.net/BestDD/article/details/113359124</a></p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;索引的失效，会大大降低sql的执行效率，日常中又有哪些常见的情况会导致索引失效？&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;对查询进行优化，应尽量避免全表扫描，首先应考虑在 where 及 order by 涉及的列上建立索引。&lt;/li&gt;
&lt;li&gt;应尽量避免在 where 子句中对字段进行
      
    
    </summary>
    
    
      <category term="MySQL" scheme="https://www.cicoding.cn/categories/MySQL/"/>
    
    
      <category term="MySQL" scheme="https://www.cicoding.cn/tags/MySQL/"/>
    
      <category term="索引" scheme="https://www.cicoding.cn/tags/%E7%B4%A2%E5%BC%95/"/>
    
  </entry>
  
  <entry>
    <title>MySQL之索引失效分析及优化相关</title>
    <link href="https://www.cicoding.cn/mysql/mysql-index-failure/"/>
    <id>https://www.cicoding.cn/mysql/mysql-index-failure/</id>
    <published>2021-06-27T04:27:35.000Z</published>
    <updated>2025-03-05T14:22:17.762Z</updated>
    
    <content type="html"><![CDATA[<p>MySQL索引失效的几种情况：</p><ol><li>条件中有or，即使其中有条件带索引也不会使用；</li><li>对于多列索引，不使用的第一部分，则不会使用索引；</li><li>like查询以%开头，索引无效；</li><li>当字段类型为字符串时，条件中数据没有使用引号引用。</li></ol><p>索引并不是时时都会生效的，比如以下几种情况，将导致索引失效：</p><ol><li>如果条件中有or，即使其中有条件带索引也不会使用(这也是为什么尽量少用or的原因)</li></ol><p><img src="/images/mysql/image-20210703183600639.png" alt="MySQL之索引失效分析及优化相关"></p><p>注意：要想使用or，又想让索引生效，只能将or条件中的每个列都加上索引</p><ol start="2"><li><p>对于多列索引，不是使用的第一部分，则不会使用索引</p></li><li><p>like查询是以%开头，索引无效；当like前缀没有%，后缀有%时，索引有效。</p></li></ol><p><img src="/images/mysql/image-20210703202920624.png" alt="MySQL之索引失效分析及优化相关"></p><ol start="4"><li>如果列类型是字符串，那一定要在条件中将数据使用引号引用起来,否则不使用索引</li></ol><p><img src="/images/mysql/image-20210703203047934.png" alt="MySQL之索引失效分析及优化相关"></p><ol start="5"><li>如果mysql估计使用全表扫描要比使用索引快,则不使用索引</li></ol><p>此外，查看索引的使用情况</p><p>show status like ‘Handler_read%’;</p><p>大家可以注意：</p><p>handler_read_key:这个值越高越好，越高表示使用索引查询到的次数</p><p>handler_read_rnd_next:这个值越高，说明查询低效</p><p>1)  没有查询条件，或者查询条件没有建立索引</p><p>2)  在查询条件上没有使用引导列</p><p>3)  查询的数量是大表的大部分，应该是30％以上。</p><p>4)  索引本身失效</p><p>5)  查询条件使用函数在索引列上，或者对索引列进行运算，运算包括(+，-，*，/，! 等)</p><p>错误的例子：</p><blockquote><p>select * from test where id-1=9; </p></blockquote><p>正确的例子：</p><blockquote><p> select * from test where id=10;</p></blockquote><p>6) 对小表查询</p><p>7) 提示不使用索引</p><p>8) 统计数据不真实</p><p>9) CBO计算走索引花费过大的情况。其实也包含了上面的情况，这里指的是表占有的block要比索引小。</p><p>10) 隐式转换导致索引失效.这一点应当引起重视.也是开发中经常会犯的错误. 由于表的字段tu_mdn定义为varchar2(20),但在查询时把该字段作为number类型以where条件传给Oracle,这样会导致索引失效. .</p><p>错误的例子：</p><blockquote><p>select * from test where tu_mdn=13333333333; </p></blockquote><p>正确的例子：</p><blockquote><p>select * from test where tu_mdn=’13333333333’;</p></blockquote><p>12) 1,&lt;&gt; 2,单独的&gt;,</p><p>13) like “%_” 百分号在前.</p><p>14) 表没分析.</p><p>15) 单独引用复合索引里非第一位置的索引列.</p><p>16) 字符型字段为数字时在where条件里不添加引号.</p><p>17) 对索引列进行运算.需要建立函数索引.</p><p>18) not in ,not exist.</p><p>19) 当变量采用的是times变量，而表的字段采用的是date变量时.或相反情况。</p><p>20) B-tree索引 is null不会走,is not null会走,位图索引 is null,is not null 都会走</p><p>21) 联合索引 is not null 只要在建立的索引列(不分先后)都会走, in null时 必须要和建立索引第一列一起使用,当建立索引第一位置条件是is null 时,其他建立索引的列可以是is null(但必须在所有列 都满足is null的时候),或者=一个值； 当建立索引的第一位置是=一个值时,其他索引列可以是任何情况(包括is null =一个值),以上两种情况索引都会走。其他情况不会走。</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;MySQL索引失效的几种情况：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;条件中有or，即使其中有条件带索引也不会使用；&lt;/li&gt;
&lt;li&gt;对于多列索引，不使用的第一部分，则不会使用索引；&lt;/li&gt;
&lt;li&gt;like查询以%开头，索引无效；&lt;/li&gt;
&lt;li&gt;当字段类型为字符串时，条件中数
      
    
    </summary>
    
    
      <category term="MySQL" scheme="https://www.cicoding.cn/categories/MySQL/"/>
    
    
      <category term="MySQL" scheme="https://www.cicoding.cn/tags/MySQL/"/>
    
      <category term="索引" scheme="https://www.cicoding.cn/tags/%E7%B4%A2%E5%BC%95/"/>
    
  </entry>
  
  <entry>
    <title>MySQL索引类型区分</title>
    <link href="https://www.cicoding.cn/mysql/mysql-index-type-distinction/"/>
    <id>https://www.cicoding.cn/mysql/mysql-index-type-distinction/</id>
    <published>2021-06-26T13:37:35.000Z</published>
    <updated>2025-03-05T14:22:17.762Z</updated>
    
    <content type="html"><![CDATA[<h2 id="存储方式区分"><a href="#存储方式区分" class="headerlink" title="存储方式区分"></a>存储方式区分</h2><h4 id="1-B-树索引"><a href="#1-B-树索引" class="headerlink" title="1. B-树索引"></a>1. B-树索引</h4><p>目前大多数索引都是采用B-树来存储，其包含组件有：</p><ul><li>叶子节点：包含的条目直接指向表里的数据行。叶子节点之间彼此相连，一个叶子节点有一个指向下一个叶子节点的指针。</li><li>分支节点：包含的条目指向索引里其他的分支节点或者叶子节点。</li><li>根节点：一个 B-树索引只有一个根节点，实际上就是位于树的最顶端的分支节点。</li></ul><h4 id="2-哈希索引"><a href="#2-哈希索引" class="headerlink" title="2. 哈希索引"></a>2. 哈希索引</h4><p>哈希索引也称为散列索引或 HASH 索引。MySQL 目前仅有 MEMORY 存储引擎和 HEAP 存储引擎支持这类索引。其中，MEMORY 存储引擎可以支持 B-树索引和 HASH 索引，且将 HASH 当成默认索引。</p><p>哈希索引的最大特点是访问速度快，但也存在下面的一些缺点：</p><ul><li>MySQL 需要读取表中索引列的值来参与散列计算，散列计算是一个比较耗时的操作。也就是说，相对于 B-树索引来说，建立哈希索引会耗费更多的时间。</li><li>不能使用 HASH 索引排序。</li><li>HASH 索引只支持等值比较，如”=” “IN()”或”&lt;=&gt;”。</li><li>HASH 索引不支持键的部分匹配，因为在计算 HASH 值的时候是通过整个索引值来计算的。</li></ul><h2 id="逻辑区分"><a href="#逻辑区分" class="headerlink" title="逻辑区分"></a>逻辑区分</h2><h4 id="1-普通索引"><a href="#1-普通索引" class="headerlink" title="1. 普通索引"></a>1. 普通索引</h4><p>普通索引是 MySQL 中最基本的索引类型，它没有任何限制，唯一任务就是加快系统对数据的访问速度。允许重复值和空值。</p><p>关键字是 <strong>INDEX</strong> 或 <strong>KEY</strong>。</p><h4 id="2-唯一索引"><a href="#2-唯一索引" class="headerlink" title="2. 唯一索引"></a>2. 唯一索引</h4><p>唯一索引列的值必须唯一，允许有空值。如果是组合索引，则列值的组合必须唯一。</p><p>关键字是 <strong>UNIQUE</strong>。</p><h4 id="3-主键索引"><a href="#3-主键索引" class="headerlink" title="3. 主键索引"></a>3. 主键索引</h4><p>主键索引是一种特殊的唯一索引，不允许值重复或者值为空。</p><p>关键字是 <strong>PRIMARY KEY</strong>。</p><h4 id="4-空间索引"><a href="#4-空间索引" class="headerlink" title="4. 空间索引"></a>4. 空间索引</h4><p>空间索引是对空间数据类型的字段建立的索引，不允许空值，只能在存储引擎为 MyISAM 的表中创建。</p><p>关键字是 <strong>SPATIAL</strong>。</p><h4 id="5-全文索引"><a href="#5-全文索引" class="headerlink" title="5. 全文索引"></a>5. 全文索引</h4><p>全文索引主要用来查找文本中的关键字，只能在 CHAR、VARCHAR 或 TEXT 类型的列上创建。只有 MyISAM 存储引擎支持，允许重复值和空值。</p><p>关键字是 <strong>FULLTEXT</strong>。</p><h2 id="实际使用区分"><a href="#实际使用区分" class="headerlink" title="实际使用区分"></a>实际使用区分</h2><h4 id="1-单列索引"><a href="#1-单列索引" class="headerlink" title="1. 单列索引"></a>1. 单列索引</h4><p>单列索引可以是普通索引，也可以是唯一性索引，还可以是全文索引。只要保证该索引只对应一个字段即可。</p><h4 id="2-组合索引"><a href="#2-组合索引" class="headerlink" title="2. 组合索引"></a>2. 组合索引</h4><p>组合索引也称为复合索引或多列索引。相对于单列索引来说，组合索引是将原表的多个列共同组成一个索引。</p><p>查询时，字段顺序需与索引顺序一致；LIKE时，首字符不能是 ‘%’，否则会影响索引使用。</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h2 id=&quot;存储方式区分&quot;&gt;&lt;a href=&quot;#存储方式区分&quot; class=&quot;headerlink&quot; title=&quot;存储方式区分&quot;&gt;&lt;/a&gt;存储方式区分&lt;/h2&gt;&lt;h4 id=&quot;1-B-树索引&quot;&gt;&lt;a href=&quot;#1-B-树索引&quot; class=&quot;headerlink&quot; ti
      
    
    </summary>
    
    
      <category term="MySQL" scheme="https://www.cicoding.cn/categories/MySQL/"/>
    
    
      <category term="MySQL" scheme="https://www.cicoding.cn/tags/MySQL/"/>
    
      <category term="索引" scheme="https://www.cicoding.cn/tags/%E7%B4%A2%E5%BC%95/"/>
    
  </entry>
  
  <entry>
    <title>Navicat建表MySQL索引类型</title>
    <link href="https://www.cicoding.cn/mysql/navicat-mysql-index-type/"/>
    <id>https://www.cicoding.cn/mysql/navicat-mysql-index-type/</id>
    <published>2021-06-26T13:27:35.000Z</published>
    <updated>2025-03-05T14:22:17.763Z</updated>
    
    <content type="html"><![CDATA[<h3 id="mysql索引类型：FULLTEXT、NORMAL、SPATIAL、UNIQUE的详细介绍"><a href="#mysql索引类型：FULLTEXT、NORMAL、SPATIAL、UNIQUE的详细介绍" class="headerlink" title="mysql索引类型：FULLTEXT、NORMAL、SPATIAL、UNIQUE的详细介绍"></a>mysql索引类型：FULLTEXT、NORMAL、SPATIAL、UNIQUE的详细介绍</h3><h2 id="Normal-普通索引"><a href="#Normal-普通索引" class="headerlink" title="Normal 普通索引"></a>Normal 普通索引</h2><p>表示普通索引，大多数情况下都可以使用</p><h2 id="Unique-唯一索引"><a href="#Unique-唯一索引" class="headerlink" title="Unique 唯一索引"></a>Unique 唯一索引</h2><p>表示唯一的，不允许重复的索引，如果该字段信息保证不会重复例如身份证号用作索引时，可设置为unique</p><p>约束唯一标识数据库表中的每一条记录，即在单表中不能用每条记录是唯一的（例如身份证就是唯一的），Unique(要求列唯一)和Primary Key(primary key = unique + not null 列唯一)约束均为列或列集合中提供了唯一性的保证，Primary Key是拥有自动定义的Unique约束，但是每个表中可以有多个Unique约束，但是只能有一个Primary Key约束。<br>mysql中创建Unique约束</p><h2 id="Full-Text-全文索引"><a href="#Full-Text-全文索引" class="headerlink" title="Full Text 全文索引"></a>Full Text 全文索引</h2><p>表示全文收索，在检索长文本的时候，效果最好，短文本建议使用Index,但是在检索的时候数据量比较大的时候，现将数据放入一个没有全局索引的表中，然后在用Create Index创建的Full Text索引，要比先为一张表建立Full Text然后在写入数据要快的很多</p><p>FULLTEXT 用于搜索很长一篇文章的时候，效果最好。用在比较短的文本，如果就一两行字的，普通的 INDEX 也可以。</p><h2 id="SPATIAL-空间索引"><a href="#SPATIAL-空间索引" class="headerlink" title="SPATIAL 空间索引"></a>SPATIAL 空间索引</h2><p>空间索引是对空间数据类型的字段建立的索引，MYSQL中的空间数据类型有4种，分别是GEOMETRY、POINT、LINESTRING、POLYGON。MYSQL使用SPATIAL关键字进行扩展，使得能够用于创建正规索引类型的语法创建空间索引。创建空间索引的列，必须将其声明为NOT NULL，空间索引只能在存储引擎为MYISAM的表中创建</p><h2 id="btree索引和hash索引的区别"><a href="#btree索引和hash索引的区别" class="headerlink" title="btree索引和hash索引的区别"></a>btree索引和hash索引的区别</h2><p>1、BTREE（B树（可以是多叉树）） {主流使用}<br>2、HASH（key,value） 这种方式对范围查询支持得不是很好</p><p>hash 索引结构的特殊性，其检索效率非常高，索引的检索可以一次定位，不像B-Tree 索引需要从根节点到枝节点，最后才能访问到页节点这样多次的IO访问，所以 Hash 索引的查询效率要远高于 B-Tree 索引。<br>可 能很多人又有疑问了，既然 Hash 索引的效率要比 B-Tree 高很多，为什么大家不都用 Hash 索引而还要使用 B-Tree 索引呢？任何事物都是有两面性的，Hash 索引也一样，虽然 Hash 索引效率高，但是 Hash 索引本身由于其特殊性也带来了很多限制和弊端，主要有以下这些。</p><p>（1）Hash 索引仅仅能满足”=”,”IN”和”&lt;=&gt;”查询，不能使用范围查询。</p><p>由于 Hash 索引比较的是进行 Hash 运算之后的 Hash 值，所以它只能用于等值的过滤，不能用于基于范围的过滤，因为经过相应的 Hash 算法处理之后的 Hash 值的大小关系，并不能保证和Hash运算前完全一样。</p><p>（2）Hash 索引无法被用来避免数据的排序操作。</p><p>由于 Hash 索引中存放的是经过 Hash 计算之后的 Hash 值，而且Hash值的大小关系并不一定和 Hash 运算前的键值完全一样，所以数据库无法利用索引的数据来避免任何排序运算；</p><p>（3）Hash 索引不能利用部分索引键查询。</p><p>对于组合索引，Hash 索引在计算 Hash 值的时候是组合索引键合并后再一起计算 Hash 值，而不是单独计算 Hash 值，所以通过组合索引的前面一个或几个索引键进行查询的时候，Hash 索引也无法被利用。</p><p>（4）Hash 索引在任何时候都不能避免表扫描。</p><p>前面已经知道，Hash 索引是将索引键通过 Hash 运算之后，将 Hash运算结果的 Hash 值和所对应的行指针信息存放于一个 Hash 表中，由于不同索引键存在相同 Hash 值，所以即使取满足某个 Hash 键值的数据的记录条数，也无法从 Hash 索引中直接完成查询，还是要通过访问表中的实际数据进行相应的比较，并得到相应的结果。</p><p>（5）Hash 索引遇到大量Hash值相等的情况后性能并不一定就会比B-Tree索引高。</p><p>对于选择性比较低的索引键，如果创建 Hash 索引，那么将会存在大量记录指针信息存于同一个 Hash 值相关联。这样要定位某一条记录时就会非常麻烦，会浪费多次表数据的访问，而造成整体性能低下。</p><h2 id="在实际操作过程中，应该选取表中哪些字段作为索引？"><a href="#在实际操作过程中，应该选取表中哪些字段作为索引？" class="headerlink" title="在实际操作过程中，应该选取表中哪些字段作为索引？"></a>在实际操作过程中，应该选取表中哪些字段作为索引？</h2><p>为了使索引的使用效率更高，在创建索引时，必须考虑在哪些字段上创建索引和创建什么类型的索引,有7大原则：</p><ol><li>选择唯一性索引</li><li>为经常需要排序、分组和联合操作的字段建立索引</li><li>为常作为查询条件的字段建立索引</li><li>限制索引的数目</li><li>尽量使用数据量少的索引</li><li>尽量使用前缀来索引</li><li>删除不再使用或者很少使用的索引</li><li>经常更新修改的字段不要建立索引（针对mysql说，因为字段更改同时索引就要重新建立，排序，而Orcale好像是有这样的机制字段值更改了，它不立刻建立索引，排序索引，而是根据更改个数，时间段去做平衡索引这件事的）</li><li>不推荐在同一列建多个索引</li></ol>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h3 id=&quot;mysql索引类型：FULLTEXT、NORMAL、SPATIAL、UNIQUE的详细介绍&quot;&gt;&lt;a href=&quot;#mysql索引类型：FULLTEXT、NORMAL、SPATIAL、UNIQUE的详细介绍&quot; class=&quot;headerlink&quot; title=&quot;my
      
    
    </summary>
    
    
      <category term="MySQL" scheme="https://www.cicoding.cn/categories/MySQL/"/>
    
    
      <category term="MySQL" scheme="https://www.cicoding.cn/tags/MySQL/"/>
    
      <category term="索引" scheme="https://www.cicoding.cn/tags/%E7%B4%A2%E5%BC%95/"/>
    
      <category term="Navicat" scheme="https://www.cicoding.cn/tags/Navicat/"/>
    
  </entry>
  
  <entry>
    <title>MySQL索引类型</title>
    <link href="https://www.cicoding.cn/mysql/mysql-index-type/"/>
    <id>https://www.cicoding.cn/mysql/mysql-index-type/</id>
    <published>2021-06-25T13:27:35.000Z</published>
    <updated>2025-03-05T14:22:17.762Z</updated>
    
    <content type="html"><![CDATA[<h3 id="一、简介"><a href="#一、简介" class="headerlink" title="一、简介"></a>一、简介</h3><p>MySQL目前主要有以下几种索引类型：<br>1.普通索引<br>2.唯一索引<br>3.主键索引<br>4.组合索引<br>5.全文索引</p><h3 id="二、语句"><a href="#二、语句" class="headerlink" title="二、语句"></a>二、语句</h3><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">CREATE</span> <span class="keyword">TABLE</span> table_name[col_name <span class="keyword">data</span> <span class="keyword">type</span>]</span><br><span class="line">[<span class="keyword">unique</span>|fulltext][<span class="keyword">index</span>|<span class="keyword">key</span>][index_name](col_name[<span class="keyword">length</span>])[<span class="keyword">asc</span>|<span class="keyword">desc</span>]</span><br></pre></td></tr></table></figure><ol><li>unique|fulltext为可选参数，分别表示唯一索引、全文索引</li><li>index和key为同义词，两者作用相同，用来指定创建索引</li><li>col_name为需要创建索引的字段列，该列必须从数据表中该定义的多个列中选择</li><li>index_name指定索引的名称，为可选参数，如果不指定，默认col_name为索引值</li><li>length为可选参数，表示索引的长度，只有字符串类型的字段才能指定索引长度</li><li>asc或desc指定升序或降序的索引值存储</li></ol><h3 id="三、索引类型"><a href="#三、索引类型" class="headerlink" title="三、索引类型"></a>三、索引类型</h3><p>1.普通索引<br>是最基本的索引，它没有任何限制。它有以下几种创建方式：<br>（1）直接创建索引</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">CREATE INDEX index_name ON table(column(length))</span><br></pre></td></tr></table></figure><p>（2）修改表结构的方式添加索引</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ALTER TABLE table_name ADD INDEX index_name ON (column(length))</span><br></pre></td></tr></table></figure><p>（3）创建表的时候同时创建索引</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">CREATE</span> <span class="keyword">TABLE</span> <span class="string">`table`</span> (</span><br><span class="line">    <span class="string">`id`</span> <span class="built_in">int</span>(<span class="number">11</span>) <span class="keyword">NOT</span> <span class="literal">NULL</span> AUTO_INCREMENT ,</span><br><span class="line">    <span class="string">`title`</span> <span class="built_in">char</span>(<span class="number">255</span>) <span class="built_in">CHARACTER</span> <span class="keyword">NOT</span> <span class="literal">NULL</span> ,</span><br><span class="line">    <span class="string">`content`</span> <span class="built_in">text</span> <span class="built_in">CHARACTER</span> <span class="literal">NULL</span> ,</span><br><span class="line">    <span class="string">`time`</span> <span class="built_in">int</span>(<span class="number">10</span>) <span class="literal">NULL</span> <span class="keyword">DEFAULT</span> <span class="literal">NULL</span> ,</span><br><span class="line">    PRIMARY <span class="keyword">KEY</span> (<span class="string">`id`</span>),</span><br><span class="line">    <span class="keyword">INDEX</span> index_name (title(<span class="keyword">length</span>))</span><br><span class="line">)</span><br></pre></td></tr></table></figure><p>（4）删除索引</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">DROP INDEX index_name ON table</span><br></pre></td></tr></table></figure><p>2.唯一索引<br>与前面的普通索引类似，不同的就是：索引列的值必须唯一，但允许有空值。如果是组合索引，则列值的组合必须唯一。它有以下几种创建方式：<br>（1）创建唯一索引</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">CREATE UNIQUE INDEX indexName ON table(column(length))</span><br></pre></td></tr></table></figure><p>（2）修改表结构</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ALTER TABLE table_name ADD UNIQUE indexName ON (column(length))</span><br></pre></td></tr></table></figure><p>（3）创建表的时候直接指定</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">CREATE</span> <span class="keyword">TABLE</span> <span class="string">`table`</span> (</span><br><span class="line">    <span class="string">`id`</span> <span class="built_in">int</span>(<span class="number">11</span>) <span class="keyword">NOT</span> <span class="literal">NULL</span> AUTO_INCREMENT ,</span><br><span class="line">    <span class="string">`title`</span> <span class="built_in">char</span>(<span class="number">255</span>) <span class="built_in">CHARACTER</span> <span class="keyword">NOT</span> <span class="literal">NULL</span> ,</span><br><span class="line">    <span class="string">`content`</span> <span class="built_in">text</span> <span class="built_in">CHARACTER</span> <span class="literal">NULL</span> ,</span><br><span class="line">    <span class="string">`time`</span> <span class="built_in">int</span>(<span class="number">10</span>) <span class="literal">NULL</span> <span class="keyword">DEFAULT</span> <span class="literal">NULL</span> ,</span><br><span class="line">    <span class="keyword">UNIQUE</span> indexName (title(<span class="keyword">length</span>))</span><br><span class="line">);</span><br></pre></td></tr></table></figure><p>3.主键索引<br>是一种特殊的唯一索引，一个表只能有一个主键，不允许有空值。一般是在建表的时候同时创建主键索引：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">CREATE TABLE `table` (</span><br><span class="line">    `id` int(11) NOT NULL AUTO_INCREMENT ,</span><br><span class="line">    `title` char(255) NOT NULL ,</span><br><span class="line">    PRIMARY KEY (`id`)</span><br><span class="line">);</span><br></pre></td></tr></table></figure><p>4.组合索引<br>指多个字段上创建的索引，只有在查询条件中使用了创建索引时的第一个字段，索引才会被使用。使用组合索引时遵循最左前缀集合</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ALTER TABLE `table` ADD INDEX name_city_age (name,city,age);</span><br></pre></td></tr></table></figure><p>5.全文索引<br>主要用来查找文本中的关键字，而不是直接与索引中的值相比较。fulltext索引跟其它索引大不相同，它更像是一个搜索引擎，而不是简单的where语句的参数匹配。fulltext索引配合match against操作使用，而不是一般的where语句加like。它可以在create table，alter table ，create index使用，不过目前只有char、varchar，text 列上可以创建全文索引。值得一提的是，在数据量较大时候，现将数据放入一个没有全局索引的表中，然后再用CREATE index创建fulltext索引，要比先为一张表建立fulltext然后再将数据写入的速度快很多。<br>（1）创建表的适合添加全文索引</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">CREATE</span> <span class="keyword">TABLE</span> <span class="string">`table`</span> (</span><br><span class="line">    <span class="string">`id`</span> <span class="built_in">int</span>(<span class="number">11</span>) <span class="keyword">NOT</span> <span class="literal">NULL</span> AUTO_INCREMENT ,</span><br><span class="line">    <span class="string">`title`</span> <span class="built_in">char</span>(<span class="number">255</span>) <span class="built_in">CHARACTER</span> <span class="keyword">NOT</span> <span class="literal">NULL</span> ,</span><br><span class="line">    <span class="string">`content`</span> <span class="built_in">text</span> <span class="built_in">CHARACTER</span> <span class="literal">NULL</span> ,</span><br><span class="line">    <span class="string">`time`</span> <span class="built_in">int</span>(<span class="number">10</span>) <span class="literal">NULL</span> <span class="keyword">DEFAULT</span> <span class="literal">NULL</span> ,</span><br><span class="line">    PRIMARY <span class="keyword">KEY</span> (<span class="string">`id`</span>),</span><br><span class="line">    FULLTEXT (<span class="keyword">content</span>)</span><br><span class="line">);</span><br></pre></td></tr></table></figure><p>（2）修改表结构添加全文索引</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ALTER TABLE article ADD FULLTEXT index_content(content)</span><br></pre></td></tr></table></figure><p>（3）直接创建索引</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">CREATE FULLTEXT INDEX index_content ON article(content)</span><br></pre></td></tr></table></figure><h3 id="四、缺点"><a href="#四、缺点" class="headerlink" title="四、缺点"></a>四、缺点</h3><ol><li>虽然索引大大提高了查询速度，同时却会降低更新表的速度，如对表进行insert、update和delete。因为更新表时，不仅要保存数据，还要保存一下索引文件。</li><li>建立索引会占用磁盘空间的索引文件。一般情况这个问题不太严重，但如果你在一个大表上创建了多种组合索引，索引文件的会增长很快。<br>索引只是提高效率的一个因素，如果有大数据量的表，就需要花时间研究建立最优秀的索引，或优化查询语句。</li></ol><h3 id="五、注意事项"><a href="#五、注意事项" class="headerlink" title="五、注意事项"></a>五、注意事项</h3><p>使用索引时，有以下一些技巧和注意事项：</p><ol><li>索引不会包含有null值的列<br>只要列中包含有null值都将不会被包含在索引中，复合索引中只要有一列含有null值，那么这一列对于此复合索引就是无效的。所以我们在数据库设计时不要让字段的默认值为null。</li><li>使用短索引<br>对串列进行索引，如果可能应该指定一个前缀长度。例如，如果有一个char(255)的列，如果在前10个或20个字符内，多数值是惟一的，那么就不要对整个列进行索引。短索引不仅可以提高查询速度而且可以节省磁盘空间和I/O操作。</li><li>索引列排序<br>查询只使用一个索引，因此如果where子句中已经使用了索引的话，那么order by中的列是不会使用索引的。因此数据库默认排序可以符合要求的情况下不要使用排序操作；尽量不要包含多个列的排序，如果需要最好给这些列创建复合索引。</li><li>like语句操作<br>一般情况下不推荐使用like操作，如果非使用不可，如何使用也是一个问题。like “%aaa%” 不会使用索引而like “aaa%”可以使用索引。</li><li>不要在列上进行运算<br>这将导致索引失效而进行全表扫描，例如</li></ol><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">SELECT * FROM table_name WHERE YEAR(column_name)&lt;2017;</span><br></pre></td></tr></table></figure><ol start="6"><li>不使用not in和&lt;&gt;操作</li></ol>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h3 id=&quot;一、简介&quot;&gt;&lt;a href=&quot;#一、简介&quot; class=&quot;headerlink&quot; title=&quot;一、简介&quot;&gt;&lt;/a&gt;一、简介&lt;/h3&gt;&lt;p&gt;MySQL目前主要有以下几种索引类型：&lt;br&gt;1.普通索引&lt;br&gt;2.唯一索引&lt;br&gt;3.主键索引&lt;br&gt;4.组合索引&lt;br
      
    
    </summary>
    
    
      <category term="MySQL" scheme="https://www.cicoding.cn/categories/MySQL/"/>
    
    
      <category term="MySQL" scheme="https://www.cicoding.cn/tags/MySQL/"/>
    
      <category term="索引" scheme="https://www.cicoding.cn/tags/%E7%B4%A2%E5%BC%95/"/>
    
  </entry>
  
  <entry>
    <title>MySQL之索引</title>
    <link href="https://www.cicoding.cn/mysql/Indexes/"/>
    <id>https://www.cicoding.cn/mysql/Indexes/</id>
    <published>2021-06-25T08:17:35.000Z</published>
    <updated>2025-03-05T14:22:17.762Z</updated>
    
    <content type="html"><![CDATA[<p>索引（在MySQL中也叫做“键（key）”）是存储引擎用于快速找到记录的一种数据结构。 这是索引的基本功能，除此之外，本章还将讨论索引其他一些方面有用的属性。<br>索引对于良好的性能非常关键。尤其是当表中的数据量越来越大时，索引对性能的影响愈发重要。在数据量较小且负载较低时，不恰当的索引对性能的影响可能还不明显，但 当数据量逐渐增大时，性能则会急剧下降。<br>不过，索引却经常被忽略，有时候甚至被误解，所以在实际案例中经常会遇到由糟糕索引导致的问题。这也是我们把索引优化放在了靠前的章节，甚至比査询优化还靠前的原因。<br>索引优化应该是对査询性能优化最有效的手段了。索引能够轻易将查询性能提高几个数量级，“最优”的索引有时比一个“好的”索引性能要好两个数量级。创建一个真正“最 优”的索引经常需要重写査询，所以，本章和下一章的关系非常紧密。</p><h2 id="索引基础"><a href="#索引基础" class="headerlink" title="索引基础"></a>索引基础</h2><p>要理解MySQL中索引是如何工作的，最简单的方法就是去看看一本书的“索引”部分: 如果想在一本书中找到某个特定主题，一般会先看书的“索引”，找到对应的页码。</p><p>在MySQL中，存储引擎用类似的方法使用索引，其先在索引中找到对应值，然后根据 匹配的索引记录找到对应的数据行。假如要运行下面的査询：</p><blockquote><p>mysql&gt; SELECT first_ame FROM sakila.actor WHERE actor_id = 5;</p></blockquote><p>如果在actor_id列上建有索引，则MySQL将使用该索引找到actor_id为5的行，也 就是说，MySQL先在索引上按值进行査找，然后返回所有包含该值的数据行。</p><p>索引可以包含一个或多个列的值。如果索引包含多个列，那么列的顺序也十分重要，因 为MySQL只能高效地使用索引的最左前缀列。创建一个包含两个列的索引，和创建两 个只包含一列的索引是大不相同的，下面将详细介绍。</p><h2 id="如果使用的是ORM-是否还需要关心索引？"><a href="#如果使用的是ORM-是否还需要关心索引？" class="headerlink" title="如果使用的是ORM,是否还需要关心索引？"></a>如果使用的是ORM,是否还需要关心索引？</h2><p>简而言之：是的，仍然需要理解索引，即使是使用对象关系映射<strong>(ORM)</strong>工具。</p><p><strong>(ORM)</strong>工具能够生产符合逻辑的、合法的查询(多数时候)，除非只是生成非常基本 的查询(例如仅是根据主键查询)，否则它很难生成适合索引的查询。无论是多么 复杂的<strong>(ORM)</strong>工具，在精妙和复杂的索引面前都是“浮云”。读完本章后面的内容 以后，你就会同意这个观点的！很多时候，即使是查询优化技术专家也很难兼顾到 各种情况，更别说<strong>(ORM)</strong> 了。</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;索引（在MySQL中也叫做“键（key）”）是存储引擎用于快速找到记录的一种数据结构。 这是索引的基本功能，除此之外，本章还将讨论索引其他一些方面有用的属性。&lt;br&gt;索引对于良好的性能非常关键。尤其是当表中的数据量越来越大时，索引对性能的影响愈发重要。在数据量较小且负载较低
      
    
    </summary>
    
    
      <category term="MySQL" scheme="https://www.cicoding.cn/categories/MySQL/"/>
    
    
      <category term="MySQL" scheme="https://www.cicoding.cn/tags/MySQL/"/>
    
  </entry>
  
  <entry>
    <title>Spring Boot版 Sharding JDBC 垂直拆分（不同的表在不同的库中）+ 读写分离</title>
    <link href="https://www.cicoding.cn/sharding-jdbc/sharding-jdbc006/"/>
    <id>https://www.cicoding.cn/sharding-jdbc/sharding-jdbc006/</id>
    <published>2021-06-10T12:50:15.000Z</published>
    <updated>2025-03-05T14:22:17.772Z</updated>
    
    <content type="html"><![CDATA[<p>上一篇介绍的了Spring Boot版 Sharding JDBC 垂直拆分（不同的表在不同的库中）例子，接下来我们写demo，介绍Spring Boot版 Sharding JDBC 垂直拆分（不同的表在不同的库中）+ 读写分离。话不多说，直接写代码。</p><h1 id="准备"><a href="#准备" class="headerlink" title="准备"></a>准备</h1><ul><li>SpringBoot 2.1.12</li><li>Sharding-JDBC 4.0.0</li><li>Mybatis 3.x</li><li>Mysql 8.0</li><li>lombok</li></ul><h1 id="本文场景介绍"><a href="#本文场景介绍" class="headerlink" title="本文场景介绍"></a>本文场景介绍</h1><p>user表分表分为2个表，2个库</p><h2 id="POM文件"><a href="#POM文件" class="headerlink" title="POM文件"></a>POM文件</h2><p>pom文件引入如下相关依赖：</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;?xml version="1.0" encoding="UTF-8"?&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">project</span> <span class="attr">xmlns</span>=<span class="string">"http://maven.apache.org/POM/4.0.0"</span> <span class="attr">xmlns:xsi</span>=<span class="string">"http://www.w3.org/2001/XMLSchema-instance"</span></span></span><br><span class="line"><span class="tag">         <span class="attr">xsi:schemaLocation</span>=<span class="string">"http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">modelVersion</span>&gt;</span>4.0.0<span class="tag">&lt;/<span class="name">modelVersion</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">parent</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>cn.cicoding<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>shardingsphere-example<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">version</span>&gt;</span>1.0-SNAPSHOT<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">parent</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>cn.cicoding<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>sharding-db-read-write<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">version</span>&gt;</span>0.0.1-SNAPSHOT<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">name</span>&gt;</span>sharding-db-read-write<span class="tag">&lt;/<span class="name">name</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">description</span>&gt;</span>Demo project for Spring Boot<span class="tag">&lt;/<span class="name">description</span>&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="tag">&lt;<span class="name">properties</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">java.version</span>&gt;</span>1.8<span class="tag">&lt;/<span class="name">java.version</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">properties</span>&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="tag">&lt;<span class="name">dependencies</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-starter-web<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.mybatis.spring.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>mybatis-spring-boot-starter<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">version</span>&gt;</span>2.0.1<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line"></span><br><span class="line">        <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.apache.shardingsphere<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>sharding-jdbc-spring-boot-starter<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line"></span><br><span class="line">        <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>mysql<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>mysql-connector-java<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line"></span><br><span class="line">        <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.projectlombok<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>lombok<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">optional</span>&gt;</span>true<span class="tag">&lt;/<span class="name">optional</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-starter-test<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">scope</span>&gt;</span>test<span class="tag">&lt;/<span class="name">scope</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">dependencies</span>&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="tag">&lt;<span class="name">build</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">plugins</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">plugin</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-maven-plugin<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;/<span class="name">plugin</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">plugins</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">build</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;/<span class="name">project</span>&gt;</span></span><br></pre></td></tr></table></figure><h2 id="相关代码实现"><a href="#相关代码实现" class="headerlink" title="相关代码实现"></a>相关代码实现</h2><h3 id="Controller代码"><a href="#Controller代码" class="headerlink" title="Controller代码"></a>Controller代码</h3><h4 id="CicodingController代码"><a href="#CicodingController代码" class="headerlink" title="CicodingController代码"></a>CicodingController代码</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> cn.cicoding.controller;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> cn.cicoding.model.LouDong;</span><br><span class="line"><span class="keyword">import</span> cn.cicoding.service.LouDongService;</span><br><span class="line"><span class="keyword">import</span> org.springframework.beans.factory.annotation.Autowired;</span><br><span class="line"><span class="keyword">import</span> org.springframework.web.bind.annotation.GetMapping;</span><br><span class="line"><span class="keyword">import</span> org.springframework.web.bind.annotation.RestController;</span><br><span class="line"></span><br><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">LouDongController</span> </span>&#123;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Autowired</span></span><br><span class="line"><span class="keyword">private</span> LouDongService louDongService;</span><br><span class="line"></span><br><span class="line"><span class="meta">@GetMapping</span>(<span class="string">"/lds"</span>)</span><br><span class="line"><span class="function"><span class="keyword">public</span> Object <span class="title">list</span><span class="params">()</span> </span>&#123;</span><br><span class="line"><span class="keyword">return</span> louDongService.list();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@GetMapping</span>(<span class="string">"/ld/add"</span>)</span><br><span class="line"><span class="function"><span class="keyword">public</span> Object <span class="title">add</span><span class="params">()</span> </span>&#123;</span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">long</span> i = <span class="number">0</span>; i &lt; <span class="number">10</span>; i++) &#123;</span><br><span class="line">LouDong louDong = <span class="keyword">new</span> LouDong();</span><br><span class="line">louDong.setId(i+<span class="string">"a"</span>);</span><br><span class="line">louDong.setCity(<span class="string">"深圳"</span>);</span><br><span class="line">louDong.setRegion(<span class="string">"宝安"</span>);</span><br><span class="line">louDong.setName(<span class="string">"李四"</span>);</span><br><span class="line">louDong.setLdNum(<span class="string">"A"</span>);</span><br><span class="line">louDong.setUnitNum(<span class="string">"2"</span>);</span><br><span class="line">louDongService.addLouDong(louDong);</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> <span class="string">"success"</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="UserController代码"><a href="#UserController代码" class="headerlink" title="UserController代码"></a>UserController代码</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> cn.cicoding.controller;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> cn.cicoding.model.User;</span><br><span class="line"><span class="keyword">import</span> cn.cicoding.service.UserService;</span><br><span class="line"><span class="keyword">import</span> org.springframework.beans.factory.annotation.Autowired;</span><br><span class="line"><span class="keyword">import</span> org.springframework.web.bind.annotation.GetMapping;</span><br><span class="line"><span class="keyword">import</span> org.springframework.web.bind.annotation.PathVariable;</span><br><span class="line"><span class="keyword">import</span> org.springframework.web.bind.annotation.RestController;</span><br><span class="line"></span><br><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">UserController</span> </span>&#123;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Autowired</span></span><br><span class="line"><span class="keyword">private</span> UserService userService;</span><br><span class="line"></span><br><span class="line"><span class="meta">@GetMapping</span>(<span class="string">"/users"</span>)</span><br><span class="line"><span class="function"><span class="keyword">public</span> Object <span class="title">list</span><span class="params">()</span> </span>&#123;</span><br><span class="line"><span class="keyword">return</span> userService.list();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@GetMapping</span>(<span class="string">"/add"</span>)</span><br><span class="line"><span class="function"><span class="keyword">public</span> Object <span class="title">add</span><span class="params">()</span> </span>&#123;</span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">long</span> i = <span class="number">0</span>; i &lt; <span class="number">100</span>; i++) &#123;</span><br><span class="line">User user = <span class="keyword">new</span> User();</span><br><span class="line">user.setCity(<span class="string">"深圳"</span>);</span><br><span class="line">user.setName(<span class="string">"李四"</span>);</span><br><span class="line">userService.add(user);</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> <span class="string">"success"</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@GetMapping</span>(<span class="string">"/users/&#123;id&#125;"</span>)</span><br><span class="line"><span class="function"><span class="keyword">public</span> Object <span class="title">get</span><span class="params">(@PathVariable Long id)</span> </span>&#123;</span><br><span class="line"><span class="keyword">return</span> userService.findById(id);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@GetMapping</span>(<span class="string">"/users/query"</span>)</span><br><span class="line"><span class="function"><span class="keyword">public</span> Object <span class="title">get</span><span class="params">(String name)</span> </span>&#123;</span><br><span class="line"><span class="keyword">return</span> userService.findByName(name);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="Service代码"><a href="#Service代码" class="headerlink" title="Service代码"></a>Service代码</h3><h4 id="CicodingService代码"><a href="#CicodingService代码" class="headerlink" title="CicodingService代码"></a>CicodingService代码</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> cn.cicoding.service;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> cn.cicoding.model.LouDong;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.util.List;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">CicodingService</span> </span>&#123;</span><br><span class="line"></span><br><span class="line"><span class="function">List&lt;LouDong&gt; <span class="title">list</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="function">Long <span class="title">addLouDong</span><span class="params">(LouDong louDong)</span></span>;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="UserService代码"><a href="#UserService代码" class="headerlink" title="UserService代码"></a>UserService代码</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> cn.cicoding.service;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.util.List;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> cn.cicoding.model.User;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">UserService</span> </span>&#123;</span><br><span class="line"></span><br><span class="line"><span class="function">List&lt;User&gt; <span class="title">list</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="function">Long <span class="title">add</span><span class="params">(User user)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="function">User <span class="title">findById</span><span class="params">(Long id)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="function">User <span class="title">findByName</span><span class="params">(String name)</span></span>;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="CicodingServiceImpl代码"><a href="#CicodingServiceImpl代码" class="headerlink" title="CicodingServiceImpl代码"></a>CicodingServiceImpl代码</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> cn.cicoding.service;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.util.List;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> cn.cicoding.model.LouDong;</span><br><span class="line"><span class="keyword">import</span> cn.cicoding.repository.LouDongRepository;</span><br><span class="line"><span class="keyword">import</span> org.springframework.beans.factory.annotation.Autowired;</span><br><span class="line"><span class="keyword">import</span> org.springframework.stereotype.Service;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">CicodingServiceImpl</span> <span class="keyword">implements</span> <span class="title">CicodingService</span> </span>&#123;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Autowired</span></span><br><span class="line"><span class="keyword">private</span> LouDongRepository louDongRepository;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> List&lt;LouDong&gt; <span class="title">list</span><span class="params">()</span> </span>&#123;</span><br><span class="line"><span class="keyword">return</span> louDongRepository.list();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> Long <span class="title">addLouDong</span><span class="params">(LouDong louDong)</span> </span>&#123;</span><br><span class="line"><span class="keyword">return</span> louDongRepository.addLouDong(louDong);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="UserServiceImpl代码"><a href="#UserServiceImpl代码" class="headerlink" title="UserServiceImpl代码"></a>UserServiceImpl代码</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> cn.cicoding.service;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.util.List;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> cn.cicoding.model.User;</span><br><span class="line"><span class="keyword">import</span> cn.cicoding.repository.UserRepository;</span><br><span class="line"><span class="keyword">import</span> org.springframework.beans.factory.annotation.Autowired;</span><br><span class="line"><span class="keyword">import</span> org.springframework.stereotype.Service;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">UserServiceImpl</span> <span class="keyword">implements</span> <span class="title">UserService</span> </span>&#123;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Autowired</span></span><br><span class="line"><span class="keyword">private</span> UserRepository userRepository;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">public</span> List&lt;User&gt; <span class="title">list</span><span class="params">()</span> </span>&#123;</span><br><span class="line"><span class="keyword">return</span> userRepository.list();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">public</span> Long <span class="title">add</span><span class="params">(User user)</span> </span>&#123;</span><br><span class="line"><span class="keyword">return</span> userRepository.addUser(user);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> User <span class="title">findById</span><span class="params">(Long id)</span> </span>&#123;</span><br><span class="line"><span class="keyword">return</span> userRepository.findById(id);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> User <span class="title">findByName</span><span class="params">(String name)</span> </span>&#123;</span><br><span class="line"><span class="keyword">return</span> userRepository.findByName(name);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="Repository代码"><a href="#Repository代码" class="headerlink" title="Repository代码"></a>Repository代码</h3><h4 id="CicodingRepository代码"><a href="#CicodingRepository代码" class="headerlink" title="CicodingRepository代码"></a>CicodingRepository代码</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> cn.cicoding.repository;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.util.List;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> cn.cicoding.model.LouDong;</span><br><span class="line"><span class="keyword">import</span> org.apache.ibatis.annotations.Mapper;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Mapper</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">CicodingRepository</span> </span>&#123;</span><br><span class="line"></span><br><span class="line"><span class="function">Long <span class="title">addLouDong</span><span class="params">(LouDong louDong)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="function">List&lt;LouDong&gt; <span class="title">list</span><span class="params">()</span></span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="UserRepository代码"><a href="#UserRepository代码" class="headerlink" title="UserRepository代码"></a>UserRepository代码</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> cn.cicoding.repository;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.util.List;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> cn.cicoding.model.User;</span><br><span class="line"><span class="keyword">import</span> org.apache.ibatis.annotations.Mapper;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Mapper</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">UserRepository</span> </span>&#123;</span><br><span class="line"></span><br><span class="line"><span class="function">Long <span class="title">addUser</span><span class="params">(User user)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="function">List&lt;User&gt; <span class="title">list</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="function">User <span class="title">findById</span><span class="params">(Long id)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="function">User <span class="title">findByName</span><span class="params">(String name)</span></span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="Mapper-xml代码实现"><a href="#Mapper-xml代码实现" class="headerlink" title="Mapper.xml代码实现"></a>Mapper.xml代码实现</h3><h4 id="CicodingMapper-xml"><a href="#CicodingMapper-xml" class="headerlink" title="CicodingMapper.xml"></a>CicodingMapper.xml</h4><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;?xml version="1.0" encoding="UTF-8" ?&gt;</span></span><br><span class="line"><span class="meta">&lt;!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">mapper</span> <span class="attr">namespace</span>=<span class="string">"cn.cicoding.repository.CicodingMapper"</span>&gt;</span></span><br><span class="line">    </span><br><span class="line">    <span class="tag">&lt;<span class="name">resultMap</span> <span class="attr">id</span>=<span class="string">"baseResultMap"</span> <span class="attr">type</span>=<span class="string">"cn.cicoding.model.Cicoding"</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">result</span> <span class="attr">column</span>=<span class="string">"id"</span> <span class="attr">property</span>=<span class="string">"id"</span> <span class="attr">jdbcType</span>=<span class="string">"VARCHAR"</span> /&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">result</span> <span class="attr">column</span>=<span class="string">"city"</span> <span class="attr">property</span>=<span class="string">"city"</span> <span class="attr">jdbcType</span>=<span class="string">"VARCHAR"</span> /&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">result</span> <span class="attr">column</span>=<span class="string">"region"</span> <span class="attr">property</span>=<span class="string">"region"</span> <span class="attr">jdbcType</span>=<span class="string">"VARCHAR"</span> /&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">result</span> <span class="attr">column</span>=<span class="string">"name"</span> <span class="attr">property</span>=<span class="string">"name"</span> <span class="attr">jdbcType</span>=<span class="string">"VARCHAR"</span> /&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">result</span> <span class="attr">column</span>=<span class="string">"ld_num"</span> <span class="attr">property</span>=<span class="string">"ldNum"</span> <span class="attr">jdbcType</span>=<span class="string">"VARCHAR"</span> /&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">result</span> <span class="attr">column</span>=<span class="string">"unit_num"</span> <span class="attr">property</span>=<span class="string">"unitNum"</span> <span class="attr">jdbcType</span>=<span class="string">"VARCHAR"</span> /&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">resultMap</span>&gt;</span></span><br><span class="line">    </span><br><span class="line">    <span class="tag">&lt;<span class="name">insert</span> <span class="attr">id</span>=<span class="string">"addLouDong"</span>&gt;</span></span><br><span class="line">        INSERT INTO loudong (</span><br><span class="line">        id, city, region, name, ld_num, unit_num</span><br><span class="line">        )</span><br><span class="line">        VALUES (</span><br><span class="line">        #&#123;id,jdbcType=VARCHAR&#125;,</span><br><span class="line">        #&#123;city,jdbcType=VARCHAR&#125;,</span><br><span class="line">        #&#123;region,jdbcType=VARCHAR&#125;,</span><br><span class="line">        #&#123;name,jdbcType=VARCHAR&#125;,</span><br><span class="line">        #&#123;ldNum,jdbcType=VARCHAR&#125;,</span><br><span class="line">        #&#123;unitNum,jdbcType=VARCHAR&#125;</span><br><span class="line">        )</span><br><span class="line">    <span class="tag">&lt;/<span class="name">insert</span>&gt;</span></span><br><span class="line">   </span><br><span class="line">    <span class="tag">&lt;<span class="name">select</span> <span class="attr">id</span>=<span class="string">"list"</span> <span class="attr">resultMap</span>=<span class="string">"baseResultMap"</span>&gt;</span></span><br><span class="line">        SELECT ld.* FROM loudong ld</span><br><span class="line">    <span class="tag">&lt;/<span class="name">select</span>&gt;</span></span><br><span class="line">    </span><br><span class="line"><span class="tag">&lt;/<span class="name">mapper</span>&gt;</span></span><br></pre></td></tr></table></figure><h4 id="UserMapper-xml"><a href="#UserMapper-xml" class="headerlink" title="UserMapper.xml"></a>UserMapper.xml</h4><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;?xml version="1.0" encoding="UTF-8" ?&gt;</span></span><br><span class="line"><span class="meta">&lt;!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">mapper</span> <span class="attr">namespace</span>=<span class="string">"cn.cicoding.repository.UserRepository"</span>&gt;</span></span><br><span class="line">    </span><br><span class="line">    <span class="tag">&lt;<span class="name">resultMap</span> <span class="attr">id</span>=<span class="string">"baseResultMap"</span> <span class="attr">type</span>=<span class="string">"cn.cicoding.model.User"</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">result</span> <span class="attr">column</span>=<span class="string">"id"</span> <span class="attr">property</span>=<span class="string">"id"</span> <span class="attr">jdbcType</span>=<span class="string">"INTEGER"</span> /&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">result</span> <span class="attr">column</span>=<span class="string">"city"</span> <span class="attr">property</span>=<span class="string">"city"</span> <span class="attr">jdbcType</span>=<span class="string">"VARCHAR"</span> /&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">result</span> <span class="attr">column</span>=<span class="string">"name"</span> <span class="attr">property</span>=<span class="string">"name"</span> <span class="attr">jdbcType</span>=<span class="string">"VARCHAR"</span> /&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">resultMap</span>&gt;</span></span><br><span class="line">    </span><br><span class="line">    <span class="tag">&lt;<span class="name">insert</span> <span class="attr">id</span>=<span class="string">"addUser"</span>&gt;</span></span><br><span class="line">        INSERT INTO user (</span><br><span class="line">           city, name</span><br><span class="line">        )</span><br><span class="line">        VALUES (</span><br><span class="line">        #&#123;city,jdbcType=VARCHAR&#125;,</span><br><span class="line">        #&#123;name,jdbcType=VARCHAR&#125;</span><br><span class="line">        )</span><br><span class="line">    <span class="tag">&lt;/<span class="name">insert</span>&gt;</span></span><br><span class="line">   </span><br><span class="line">    <span class="tag">&lt;<span class="name">select</span> <span class="attr">id</span>=<span class="string">"list"</span> <span class="attr">resultMap</span>=<span class="string">"baseResultMap"</span>&gt;</span></span><br><span class="line">        SELECT u.* FROM user u</span><br><span class="line">    <span class="tag">&lt;/<span class="name">select</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">select</span> <span class="attr">id</span>=<span class="string">"findById"</span> <span class="attr">resultMap</span>=<span class="string">"baseResultMap"</span>&gt;</span></span><br><span class="line">        SELECT u.* FROM user u WHERE u.id=#&#123;id,jdbcType=INTEGER&#125;</span><br><span class="line">    <span class="tag">&lt;/<span class="name">select</span>&gt;</span></span><br><span class="line">    </span><br><span class="line">    <span class="tag">&lt;<span class="name">select</span> <span class="attr">id</span>=<span class="string">"findByName"</span> <span class="attr">resultMap</span>=<span class="string">"baseResultMap"</span>&gt;</span></span><br><span class="line">        SELECT u.* FROM user u WHERE u.name=#&#123;name,jdbcType=VARCHAR&#125;</span><br><span class="line">    <span class="tag">&lt;/<span class="name">select</span>&gt;</span></span><br><span class="line">    </span><br><span class="line"><span class="tag">&lt;/<span class="name">mapper</span>&gt;</span></span><br></pre></td></tr></table></figure><h3 id="实体类"><a href="#实体类" class="headerlink" title="实体类"></a>实体类</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> cn.cicoding.model;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.io.Serializable;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 分表</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@author</span> cicoding</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">User</span> <span class="keyword">implements</span> <span class="title">Serializable</span> </span>&#123;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">long</span> serialVersionUID = -<span class="number">1205226416664488559L</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> Long id;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> String city = <span class="string">""</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> String name = <span class="string">""</span>;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">public</span> Long <span class="title">getId</span><span class="params">()</span> </span>&#123;</span><br><span class="line"><span class="keyword">return</span> id;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">setId</span><span class="params">(Long id)</span> </span>&#123;</span><br><span class="line"><span class="keyword">this</span>.id = id;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">public</span> String <span class="title">getCity</span><span class="params">()</span> </span>&#123;</span><br><span class="line"><span class="keyword">return</span> city;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">setCity</span><span class="params">(String city)</span> </span>&#123;</span><br><span class="line"><span class="keyword">this</span>.city = city;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">public</span> String <span class="title">getName</span><span class="params">()</span> </span>&#123;</span><br><span class="line"><span class="keyword">return</span> name;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">setName</span><span class="params">(String name)</span> </span>&#123;</span><br><span class="line"><span class="keyword">this</span>.name = name;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>到这我们完成了基本的代码编写，由于sharding-jdbc是jar包，我们来看主要的配置信息</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br></pre></td><td class="code"><pre><span class="line"># Spring Boot版 Sharding JDBC 垂直拆分（不同的表在不同的库中）+ 读写分离</span><br><span class="line">server.port=8084</span><br><span class="line"></span><br><span class="line"># mybatis对应的映射文件路径</span><br><span class="line">mybatis.mapper-locations=classpath:mapper/*.xml</span><br><span class="line"># mybatis对应的实体类</span><br><span class="line">mybatis.type-aliases-package=cn.cicoding.model</span><br><span class="line"></span><br><span class="line">spring.shardingsphere.datasource.names=ds0,ds0slave,ds1,ds1slave</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"># 数据源</span><br><span class="line">spring.shardingsphere.datasource.ds0.type=com.zaxxer.hikari.HikariDataSource</span><br><span class="line">spring.shardingsphere.datasource.ds0.driver-class-name=com.mysql.jdbc.Driver</span><br><span class="line">spring.shardingsphere.datasource.ds0.jdbc-url=jdbc:mysql://localhost:3306/ds_0?useSSL=false&amp;useUnicode=true&amp;characterEncoding=utf8&amp;serverTimezone=UTC</span><br><span class="line">spring.shardingsphere.datasource.ds0.username=root</span><br><span class="line">spring.shardingsphere.datasource.ds0.password=root</span><br><span class="line"></span><br><span class="line">spring.shardingsphere.datasource.ds0slave.type=com.zaxxer.hikari.HikariDataSource</span><br><span class="line">spring.shardingsphere.datasource.ds0slave.driver-class-name=com.mysql.jdbc.Driver</span><br><span class="line">spring.shardingsphere.datasource.ds0slave.jdbc-url=jdbc:mysql://localhost:3306/ds0slave?useSSL=false&amp;useUnicode=true&amp;characterEncoding=utf8&amp;serverTimezone=UTC</span><br><span class="line">spring.shardingsphere.datasource.ds0slave.username=root</span><br><span class="line">spring.shardingsphere.datasource.ds0slave.password=root</span><br><span class="line"></span><br><span class="line">spring.shardingsphere.datasource.ds1.type=com.alibaba.druid.pool.DruidDataSource</span><br><span class="line">spring.shardingsphere.datasource.ds1.driver-class-name=com.mysql.jdbc.Driver</span><br><span class="line">spring.shardingsphere.datasource.ds1.jdbc-url=jdbc:mysql://localhost:3306/ds_1?useSSL=false&amp;useUnicode=true&amp;characterEncoding=utf8&amp;serverTimezone=UTC</span><br><span class="line">spring.shardingsphere.datasource.ds1.username=root</span><br><span class="line">spring.shardingsphere.datasource.ds1.password=root</span><br><span class="line"></span><br><span class="line">spring.shardingsphere.datasource.ds1slave.type=com.alibaba.druid.pool.DruidDataSource</span><br><span class="line">spring.shardingsphere.datasource.ds1slave.driver-class-name=com.mysql.jdbc.Driver</span><br><span class="line">spring.shardingsphere.datasource.ds1slave.jdbc-url=jdbc:mysql://localhost:3306/ds1slave?useSSL=false&amp;useUnicode=true&amp;characterEncoding=utf8&amp;serverTimezone=UTC</span><br><span class="line">spring.shardingsphere.datasource.ds1slave.username=root</span><br><span class="line">spring.shardingsphere.datasource.ds1slave.password=root</span><br><span class="line"></span><br><span class="line"># 绑定loudong表所在节点</span><br><span class="line">spring.shardingsphere.sharding.tables.loudong.actual-data-nodes=ds1.loudong</span><br><span class="line"></span><br><span class="line"># 绑定user表所在节点</span><br><span class="line">spring.shardingsphere.sharding.tables.user.actual-data-nodes=ds0.user</span><br><span class="line">spring.shardingsphere.sharding.tables.user.key-generator.column=id</span><br><span class="line">spring.shardingsphere.sharding.tables.user.key-generator.type=SNOWFLAKE</span><br><span class="line"></span><br><span class="line"># 读写分离</span><br><span class="line">spring.shardingsphere.sharding.master-slave-rules.ds0.master-data-source-name=ds0</span><br><span class="line">spring.shardingsphere.sharding.master-slave-rules.ds0.slave-data-source-names=ds0slave</span><br><span class="line"></span><br><span class="line">spring.shardingsphere.sharding.master-slave-rules.ds1.master-data-source-name=ds1</span><br><span class="line">spring.shardingsphere.sharding.master-slave-rules.ds1.slave-data-source-names=ds1slave</span><br></pre></td></tr></table></figure><h3 id="启动类"><a href="#启动类" class="headerlink" title="启动类"></a>启动类</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">package cn.cicoding;</span><br><span class="line"></span><br><span class="line">import org.springframework.boot.SpringApplication;</span><br><span class="line">import org.springframework.boot.autoconfigure.SpringBootApplication;</span><br><span class="line"></span><br><span class="line">/**</span><br><span class="line"> * Spring Boot版 Sharding JDBC 垂直拆分（不同的表在不同的库中）+ 读写分离</span><br><span class="line"> */</span><br><span class="line">@SpringBootApplication</span><br><span class="line">public class ShardingDbReadWriteApplication &#123;</span><br><span class="line"></span><br><span class="line">public static void main(String[] args) &#123;</span><br><span class="line">SpringApplication.run(ShardingDbReadWriteApplication.class, args);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="测试演示"><a href="#测试演示" class="headerlink" title="测试演示"></a>测试演示</h3><p>启动启动类，访问<a href="http://localhost:8084/add" target="_blank" rel="noopener">http://localhost:8084/add</a></p><p><a href="http://localhost:8084/cis" target="_blank" rel="noopener">http://localhost:8084/cis</a></p><p>分别进入不同的库！</p><p>到此我们就实现了sharding-jdbc主从读写分离实现，更多配置请参考<a href="https://shardingsphere.apache.org/document/legacy/3.x/document/cn/manual/sharding-jdbc/configuration/config-spring-boot/" target="_blank" rel="noopener">此处</a>！</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;上一篇介绍的了Spring Boot版 Sharding JDBC 垂直拆分（不同的表在不同的库中）例子，接下来我们写demo，介绍Spring Boot版 Sharding JDBC 垂直拆分（不同的表在不同的库中）+ 读写分离。话不多说，直接写代码。&lt;/p&gt;
&lt;h1 i
      
    
    </summary>
    
    
      <category term="Sharding-JDBC" scheme="https://www.cicoding.cn/categories/Sharding-JDBC/"/>
    
    
      <category term="Sharding-JDBC" scheme="https://www.cicoding.cn/tags/Sharding-JDBC/"/>
    
  </entry>
  
</feed>
