<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>MySQL on 小盒子的技术分享</title><link>https://xiaobox.github.io/tags/mysql/</link><description>Recent content in MySQL on 小盒子的技术分享</description><generator>Hugo -- gohugo.io</generator><language>zh-cn</language><lastBuildDate>Fri, 14 Mar 2025 09:19:52 +0000</lastBuildDate><atom:link href="https://xiaobox.github.io/tags/mysql/index.xml" rel="self" type="application/rss+xml"/><item><title>数据库选型终极指南：从数据类型到应用场景，一篇就够了</title><link>https://xiaobox.github.io/p/2025-03-14-shu-ju-ku-xuan-xing-zhong-ji-zhi-nan-cong-shu-ju-lei-xing-da/</link><pubDate>Fri, 14 Mar 2025 09:19:52 +0000</pubDate><guid>https://xiaobox.github.io/p/2025-03-14-shu-ju-ku-xuan-xing-zhong-ji-zhi-nan-cong-shu-ju-lei-xing-da/</guid><description>&lt;img src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2025-03-14-shu-ju-ku-xuan-xing-zhong-ji-zhi-nan-cong-shu-ju-lei-xing-da/cover.jpg" alt="Featured image of post 数据库选型终极指南：从数据类型到应用场景，一篇就够了" /&gt;&lt;h2 id="引言"&gt;&lt;a href="#%e5%bc%95%e8%a8%80" class="header-anchor"&gt;&lt;/a&gt;引言
&lt;/h2&gt;&lt;p&gt;在当今的数字化时代，数据已成为企业和组织的核心资产。无论是金融交易记录、社交媒体互动、物联网传感器数据，还是企业内部的业务流程信息，都需要通过数据库进行存储、管理和分析。然而，面对市场上数十种主流的数据库技术（如 MySQL、MongoDB、Elasticsearch、HBase、Hive等），如何选择适合自身业务需求的数据库系统，成为许多技术决策者面临的难题。本文将深入探讨数据库的核心分类、技术特性、应用场景以及选择策略，帮助读者构建系统化的选型框架。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="数据库的分类"&gt;&lt;a href="#%e6%95%b0%e6%8d%ae%e5%ba%93%e7%9a%84%e5%88%86%e7%b1%bb" class="header-anchor"&gt;&lt;/a&gt;数据库的分类
&lt;/h2&gt;&lt;p&gt;在进行数据库的选择前，你需要至少知道它的&lt;strong&gt;分类&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;在数据库技术的演进过程中，数据存储模型和应用需求的多样性催生了不同类型的数据库系统。这些系统根据其核心设计理念、数据组织方式以及适用场景的差异，形成了多个分类。&lt;/p&gt;
&lt;h3 id="关系型数据库rdbms结构化数据的基石"&gt;&lt;a href="#%e5%85%b3%e7%b3%bb%e5%9e%8b%e6%95%b0%e6%8d%ae%e5%ba%93rdbms%e7%bb%93%e6%9e%84%e5%8c%96%e6%95%b0%e6%8d%ae%e7%9a%84%e5%9f%ba%e7%9f%b3" class="header-anchor"&gt;&lt;/a&gt;关系型数据库（RDBMS）：结构化数据的基石
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;关系型数据库的根基是关系代数和集合论&lt;/strong&gt;，通过二维表（Table）组织数据。每个表由行（记录）和列（字段）构成，通过主键（Primary Key）唯一标识记录，外键（Foreign Key）实现表间的关联。其核心优势在于ACID事务支持，即原子性（Atomicity）、一致性（Consistency）、隔离性（Isolation）、持久性（Durability），适用于对数据一致性要求极高的场景（如金融交易）&lt;/p&gt;
&lt;p&gt;适用场景：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;需要强一致性的业务系统（银行核心系统、ERP）。&lt;/li&gt;
&lt;li&gt;多表关联查询频繁的OLTP（联机事务处理）场景（电商订单管理）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;局限性：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;表结构预定义，修改成本高（如新增字段需 ALTER TABLE）。&lt;/li&gt;
&lt;li&gt;水平扩展困难，分库分表复杂度高（需处理分布式事务和跨分片查询）。&lt;/li&gt;
&lt;li&gt;不适合存储半结构化数据（如JSON文档、嵌套数组）。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;代表数据库：&lt;code&gt;MySQL&lt;/code&gt;、&lt;code&gt;PostgreSQL&lt;/code&gt;、&lt;code&gt;Oracle&lt;/code&gt;、&lt;code&gt;SQL Server&lt;/code&gt;&lt;/p&gt;
&lt;h3 id="nosql-数据库灵活性与扩展性的革命"&gt;&lt;a href="#nosql-%e6%95%b0%e6%8d%ae%e5%ba%93%e7%81%b5%e6%b4%bb%e6%80%a7%e4%b8%8e%e6%89%a9%e5%b1%95%e6%80%a7%e7%9a%84%e9%9d%a9%e5%91%bd" class="header-anchor"&gt;&lt;/a&gt;NoSQL 数据库：灵活性与扩展性的革命
&lt;/h3&gt;&lt;p&gt;NoSQL（Not Only SQL）的诞生是为了解决关系型数据库在扩展性、灵活性和高性能场景下的不足。根据数据模型的差异，NoSQL 可进一步细分为四类：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. 文档型数据库（Document Database）&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;数据模型：以文档为基本单元，通常采用JSON或BSON格式存储，支持嵌套结构和动态字段&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;1&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;2&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;user_id&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;101&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;3&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;name&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;张三&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;4&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;orders&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;5&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nt"&gt;&amp;#34;order_id&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2001&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;amount&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;150.0&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;6&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nt"&gt;&amp;#34;order_id&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2002&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;amount&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;300.0&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;7&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;8&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;查询能力：支持基于文档属性的查询，部分数据库（如MongoDB）提供类SQL的聚合管道（Aggregation Pipeline）和索引优化。&lt;/p&gt;
&lt;p&gt;适用场景：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;内容管理系统（CMS）中文章的多版本存储。&lt;/li&gt;
&lt;li&gt;用户配置文件的动态字段管理（如社交平台用户的个性化标签）。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;局限性：跨文档事务支持较弱（MongoDB 4.0后支持多文档事务，但性能损耗较大）。&lt;/p&gt;
&lt;p&gt;代表数据库：&lt;code&gt;MongoDB&lt;/code&gt;、&lt;code&gt;Couchbase&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. 键值型数据库（Key-Value Store）&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;数据模型：最简单的 NoSQL 模型，数据以键值对（Key-Value）形式存储，Value可以是任意二进制数据。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;1&lt;/span&gt;&lt;span class="cl"&gt;Key: &amp;#34;user:101:profile&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;2&lt;/span&gt;&lt;span class="cl"&gt;Value: &amp;#34;{&amp;#39;name&amp;#39;: &amp;#39;李四&amp;#39;, &amp;#39;last_login&amp;#39;: &amp;#39;2023-10-01&amp;#39;}&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;高性能特性：通过哈希表实现O(1)时间复杂度的读写操作，适合缓存和高并发场景。&lt;/p&gt;
&lt;p&gt;适用场景：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;会话存储（Session Storage）：快速存取用户登录状态。&lt;/li&gt;
&lt;li&gt;分布式缓存（如Redis缓存热门商品信息）。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;局限性：缺乏复杂查询能力（仅能通过Key检索），需业务层处理数据关联逻辑。&lt;/p&gt;
&lt;p&gt;代表数据库：&lt;code&gt;Redis&lt;/code&gt;、&lt;code&gt;Memcached&lt;/code&gt;、&lt;code&gt;Amazon DynamoDB&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. 列族数据库（Wide-Column Store）&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;数据模型：数据按列族（Column Family）组织，每行可动态添加列，适合稀疏矩阵存储。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;1&lt;/span&gt;&lt;span class="cl"&gt;Row Key: &amp;#34;device_001&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;2&lt;/span&gt;&lt;span class="cl"&gt;Columns: 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;3&lt;/span&gt;&lt;span class="cl"&gt; &amp;#34;metrics:temperature&amp;#34; -&amp;gt; 25.5
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;4&lt;/span&gt;&lt;span class="cl"&gt; &amp;#34;metrics:humidity&amp;#34; -&amp;gt; 60%
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;5&lt;/span&gt;&lt;span class="cl"&gt; &amp;#34;location:city&amp;#34; -&amp;gt; &amp;#34;北京&amp;#34; 
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;存储优势：基于LSM树（Log-Structured Merge Tree）的存储引擎，优化高吞吐写入（如日志、传感器数据）。&lt;/p&gt;
&lt;p&gt;适用场景：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;时间序列数据（物联网设备监控）。&lt;/li&gt;
&lt;li&gt;海量数据的随机读写（如HBase存储网页爬虫数据）。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;局限性：复杂查询需依赖Row Key设计，二级索引支持有限。&lt;/p&gt;
&lt;p&gt;代表数据库：&lt;code&gt;Apache HBase&lt;/code&gt;、&lt;code&gt;Cassandra&lt;/code&gt;、&lt;code&gt;Google Bigtable&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;4. 图数据库（Graph Database）&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;数据模型：以图论为基础，通过节点（Node）、边（Edge）、属性（Property）表示实体及其关系。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;1&lt;/span&gt;&lt;span class="cl"&gt;Node: User(id=101, name=&amp;#34;王五&amp;#34;)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;2&lt;/span&gt;&lt;span class="cl"&gt;Edge: User101 -[FRIEND]-&amp;gt; User102 (since=2020)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;查询优势：专为关系查询优化，可高效遍历多跳关系（如社交网络的六度分隔理论）。&lt;/p&gt;
&lt;p&gt;适用场景：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;社交网络中的好友推荐。&lt;/li&gt;
&lt;li&gt;欺诈检测（识别异常交易环路）。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;局限性：非关系场景下性能无明显优势，学习曲线陡峭。&lt;/p&gt;
&lt;p&gt;代表数据库：&lt;code&gt;Neo4j&lt;/code&gt;、&lt;code&gt;Amazon Neptune&lt;/code&gt;&lt;/p&gt;
&lt;h3 id="大数据生态数据库分布式与批量处理的支柱"&gt;&lt;a href="#%e5%a4%a7%e6%95%b0%e6%8d%ae%e7%94%9f%e6%80%81%e6%95%b0%e6%8d%ae%e5%ba%93%e5%88%86%e5%b8%83%e5%bc%8f%e4%b8%8e%e6%89%b9%e9%87%8f%e5%a4%84%e7%90%86%e7%9a%84%e6%94%af%e6%9f%b1" class="header-anchor"&gt;&lt;/a&gt;大数据生态数据库：分布式与批量处理的支柱
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;1. 分布式列式存储（HBase）&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;技术架构：基于HDFS的分布式存储，通过Region分片实现水平扩展，ZooKeeper协调元数据。&lt;/p&gt;
&lt;p&gt;核心能力：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;随机实时读写（毫秒级延迟）。&lt;/li&gt;
&lt;li&gt;稀疏数据的高效存储（空值不占空间）。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;适用场景：实时查询TB级数据（如电信通话记录检索）。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. 数据仓库（Hive）&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;技术原理：将结构化数据映射为HDFS文件，通过 HiveQL（类SQL）转换为MapReduce或Tez任务。&lt;/p&gt;
&lt;p&gt;核心能力：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;离线批量处理（小时级延迟）。&lt;/li&gt;
&lt;li&gt;复杂ETL流程（数据清洗、转换）。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;适用场景：历史数据报表生成（如零售业月度销售分析）。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. 实时数仓（ClickHouse、Doris）&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;技术突破：向量化执行引擎、列式存储、预聚合，实现亚秒级响应。&lt;/p&gt;
&lt;p&gt;适用场景：交互式OLAP分析（如广告投放效果实时看板）。&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2025-03-14-shu-ju-ku-xuan-xing-zhong-ji-zhi-nan-cong-shu-ju-lei-xing-da/001-609ddd79.png"&gt;&lt;/p&gt;
&lt;h3 id="总结"&gt;&lt;a href="#%e6%80%bb%e7%bb%93" class="header-anchor"&gt;&lt;/a&gt;总结
&lt;/h3&gt;&lt;p&gt;我们做一个整体的对比&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2025-03-14-shu-ju-ku-xuan-xing-zhong-ji-zhi-nan-cong-shu-ju-lei-xing-da/002-4df3e69a.png"&gt;&lt;/p&gt;
&lt;p&gt;随着技术发展，数据库的界限逐渐模糊。例如：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;多模型数据库：如PostgreSQL通过扩展支持JSONB（文档模型）和Citus（分布式能力）。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;HTAP&lt;/code&gt;(Hybrid Transactional/Analytical Processing)数据库：TiDB、Oracle Exadata支持OLTP与OLAP混合负载。&lt;/li&gt;
&lt;li&gt;AI驱动数据库：利用机器学习优化查询计划（如Google AlloyDB）。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;随着 AI 技术的兴起，&lt;code&gt;向量数据库&lt;/code&gt;也是非常热门的一类数据库。数据库的分类也并非绝对的技术壁垒，而是反映了不同场景下的核心矛盾权衡：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;结构化 vs 灵活性&lt;/strong&gt;：关系型牺牲灵活性换取严格约束，文档型反之。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;一致性 vs 扩展性&lt;/strong&gt;：CP系统（如ZooKeeper）优先保障一致性，AP系统（如Cassandra）优先保障可用性。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;实时性 vs 吞吐量&lt;/strong&gt;：HBase优化单点查询延迟，Hive优化批量吞吐量。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;理解这些分类背后的哲学，才能避免“技术选型中的锤子效应”（手里只有一把锤子，看所有问题都是钉子），从而在复杂业务场景中构建合理的数据存储架构。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="数据类型"&gt;&lt;a href="#%e6%95%b0%e6%8d%ae%e7%b1%bb%e5%9e%8b" class="header-anchor"&gt;&lt;/a&gt;数据类型
&lt;/h2&gt;&lt;p&gt;在进行数据库的选择前，你要处理的数据类型是你必须要明确的。&lt;/p&gt;
&lt;p&gt;结构化、半结构化和非结构化数据在存储、查询和处理方式上存在本质差异，直接影响了技术选型的路径。&lt;/p&gt;
&lt;p&gt;在数据管理的实践中，数据类型是决定数据库选型的关键因素之一。结构化、半结构化和非结构化数据在存储、查询和处理方式上存在本质差异，直接影响了技术选型的路径。以下从数据特征、处理需求到典型数据库选择展开系统性分析。&lt;/p&gt;
&lt;h3 id="结构化数据秩序与约束的领域"&gt;&lt;a href="#%e7%bb%93%e6%9e%84%e5%8c%96%e6%95%b0%e6%8d%ae%e7%a7%a9%e5%ba%8f%e4%b8%8e%e7%ba%a6%e6%9d%9f%e7%9a%84%e9%a2%86%e5%9f%9f" class="header-anchor"&gt;&lt;/a&gt;结构化数据：秩序与约束的领域
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;1. 核心特征&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;严格模式（Schema）：数据字段预先定义，类型明确（如整数、日期、枚举值）。&lt;/li&gt;
&lt;li&gt;二维表结构：数据以行和列的形式组织，遵循第一范式（1NF）到第三范式（3NF）的规范。&lt;/li&gt;
&lt;li&gt;强关联性：通过外键建立表间关系，支持JOIN操作实现跨表查询。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;示例：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;银行账户表：&lt;code&gt;账户ID (主键) | 户主姓名 | 余额 | 开户日期&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;电商订单表：&lt;code&gt;订单ID | 用户ID (外键) | 商品ID (外键) | 订单金额 | 支付状态&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;2. 数据库选择&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;首选：关系型数据库（RDBMS）。它的选型逻辑：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;事务完整性：需要ACID保障的场景（如转账操作）。&lt;/li&gt;
&lt;li&gt;复杂查询：涉及多表关联、聚合计算（如财务报表生成）。&lt;/li&gt;
&lt;li&gt;数据一致性：字段之间存在强约束（如库存数量不能为负值）。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;其中代表方案有：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;MySQL/PostgreSQL：适用于中小规模OLTP系统。&lt;/li&gt;
&lt;li&gt;Oracle：企业级高并发、高可靠性需求（如金融核心系统）。&lt;/li&gt;
&lt;li&gt;TiDB：分布式架构下仍需强一致性的场景（如跨境支付平台）。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;3. 反模式案例&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;错误尝试：将用户行为日志（半结构化JSON）存入MySQL。这样做的问题是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;需要为动态字段创建稀疏列，导致存储空间浪费。&lt;/li&gt;
&lt;li&gt;频繁ALTER TABLE修改表结构，引发锁表风险。&lt;/li&gt;
&lt;li&gt;查询嵌套字段需解析JSON字符串，性能低下。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="半结构化数据灵活性与动态性的平衡"&gt;&lt;a href="#%e5%8d%8a%e7%bb%93%e6%9e%84%e5%8c%96%e6%95%b0%e6%8d%ae%e7%81%b5%e6%b4%bb%e6%80%a7%e4%b8%8e%e5%8a%a8%e6%80%81%e6%80%a7%e7%9a%84%e5%b9%b3%e8%a1%a1" class="header-anchor"&gt;&lt;/a&gt;半结构化数据：灵活性与动态性的平衡
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;1. 核心特征&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;松散模式：字段可动态增减，数据类型允许一定灵活性。&lt;/li&gt;
&lt;li&gt;层次化结构：数据以树形或网状形式组织（如JSON、XML）。&lt;/li&gt;
&lt;li&gt;自描述性：数据本身携带元信息（如字段名称、嵌套关系）。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;示例：用户配置文件&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 1&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 2&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;user_id&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1001&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 3&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;preferences&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 4&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;theme&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;dark&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 5&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;notifications&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 6&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;email&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 7&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;sms&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 8&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 9&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;10&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;last_activity&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;11&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nt"&gt;&amp;#34;type&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;login&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;timestamp&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;2023-10-05T08:30:00Z&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;12&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nt"&gt;&amp;#34;type&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;purchase&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;item_id&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;SKU123&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;13&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;14&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;设备传感器元数据：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-html" data-lang="html"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;1&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;device&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;D001&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;2&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;location&lt;/span&gt; &lt;span class="na"&gt;lat&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;39.9042&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;lon&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;116.4074&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;3&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;sensors&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;4&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;sensor&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;temperature&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;unit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;°C&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;5&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;sensor&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;humidity&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;unit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;%&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;6&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;sensors&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;7&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;device&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;2. 数据库选择&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;首选技术：文档型数据库、宽列数据库。它的选型逻辑：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;动态模式支持：无需预定义字段，适应业务快速迭代。&lt;/li&gt;
&lt;li&gt;嵌套查询效率：直接存储层次化数据，避免关联表拆分。&lt;/li&gt;
&lt;li&gt;局部更新能力：修改文档部分字段不影响整体结构。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;代表方案：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;MongoDB：&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;适用场景：CMS内容管理、物联网设备元数据存储。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;优势：BSON二进制存储、聚合管道、地理位置索引。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;限制：事务跨文档操作成本高（需4.0+版本）。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Cassandra：&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;适用场景：时间序列数据（如日志事件流）。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;优势：高写入吞吐、多数据中心复制。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;限制：查询必须指定分区键，二级索引效率低。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Elasticsearch：&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;适用场景：日志分析、全文检索（如电商商品搜索）。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;优势：倒排索引、近实时搜索、分词器定制。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;限制：写入吞吐受分片数限制，不支持事务。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;3. 混合架构实践&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;典型组合：MySQL + MongoDB + Elasticsearch。 数据流示例：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;用户注册信息（结构化）存入MySQL。&lt;/li&gt;
&lt;li&gt;用户行为轨迹（半结构化JSON）写入MongoDB。&lt;/li&gt;
&lt;li&gt;关键字段（如用户ID、行为类型）同步到Elasticsearch供快速检索。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="非结构化数据海量与多元化的挑战"&gt;&lt;a href="#%e9%9d%9e%e7%bb%93%e6%9e%84%e5%8c%96%e6%95%b0%e6%8d%ae%e6%b5%b7%e9%87%8f%e4%b8%8e%e5%a4%9a%e5%85%83%e5%8c%96%e7%9a%84%e6%8c%91%e6%88%98" class="header-anchor"&gt;&lt;/a&gt;非结构化数据：海量与多元化的挑战
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;1. 核心特征&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;无固定模式：数据格式不遵循预定义结构。&lt;/li&gt;
&lt;li&gt;大文件倾向：单个数据单元体积大（如视频、图片）。&lt;/li&gt;
&lt;li&gt;内容多样性：文本、图像、音频、二进制文件等。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;示例：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;媒体文件：监控摄像头的1080P视频流（MP4格式）。&lt;/li&gt;
&lt;li&gt;办公文档：PDF合同、Word报告。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;2. 数据库选择&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;核心矛盾：非结构化数据的管理重点不是“查询”，而是“存储与访问”。它的选型逻辑：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;存储效率：需支持大文件分块存储（如HDFS的128MB块）。&lt;/li&gt;
&lt;li&gt;元数据管理：通过附加结构化信息实现快速检索。&lt;/li&gt;
&lt;li&gt;访问接口：提供HTTP API或对象存储接口（如S3兼容）。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;代表方案：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;对象存储：&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;Amazon S3/阿里云OSS：存储图片、视频等静态资源。&lt;/li&gt;
&lt;li&gt;MinIO：自建私有化对象存储方案。&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start="3"&gt;
&lt;li&gt;分布式文件系统：&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;HDFS：用于Hadoop生态的原始文件存储。&lt;/li&gt;
&lt;li&gt;Ceph：统一存储池支持块、文件、对象接口。&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start="5"&gt;
&lt;li&gt;专用数据库扩展：&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;MongoDB GridFS：将大文件分块存储为文档。&lt;/li&gt;
&lt;li&gt;PostgreSQL大对象（LOB）：通过TOAST机制存储二进制数据。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;3. 元数据关联策略&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;典型架构是：对象存储 + 关系型数据库。分两步：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;数据流：&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;上传视频文件到S3，获得存储路径&lt;code&gt;s3://bucket/video_001.mp4&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;在MySQL中创建记录：&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;1&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;INSERT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;INTO&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;media_files&lt;/span&gt;&lt;span class="w"&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;2&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;s3_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;uploader_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;resolution&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;3&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;VALUES&lt;/span&gt;&lt;span class="w"&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;4&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1001&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;s3://bucket/video_001.mp4&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;501&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;1920x1080&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol&gt;
&lt;li&gt;查询过程：&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;1&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;-- 查找用户501上传的高清视频
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;2&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;s3_path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;media_files&lt;/span&gt;&lt;span class="w"&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;3&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;uploader_id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;501&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AND&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;resolution&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;1920x1080&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="总结-1"&gt;&lt;a href="#%e6%80%bb%e7%bb%93-1" class="header-anchor"&gt;&lt;/a&gt;总结
&lt;/h3&gt;&lt;p&gt;总结一下不同数据类型的特点&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2025-03-14-shu-ju-ku-xuan-xing-zhong-ji-zhi-nan-cong-shu-ju-lei-xing-da/003-ff369010.png"&gt;&lt;/p&gt;
&lt;p&gt;总结来说：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;结构化数据是商业规则的数字化体现，适合通过关系型数据库实现精准控制。&lt;/li&gt;
&lt;li&gt;半结构化数据反映了现实世界的复杂关联，文档型或宽列数据库提供必要的灵活性。&lt;/li&gt;
&lt;li&gt;非结构化数据代表信息的原始形态，需通过对象存储与元数据管理实现规模化处理。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;说了这么多，虽然对于数据是什么类型有了比较清楚的定义和区分，但是数据到底是结构化的还是非结构化的，其实&lt;strong&gt;主要是看 “数据的组织方式”和“处理方式”&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;这里举个例子，比如 &lt;code&gt;用户评论&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;如果我们只是想简单的读写用户评论，可以把它用关系型数据库存储，当作一个表中的一个字段:&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;在评论内容（CommentContent）这个字段中，我们可以存储用户的评论文本。对于包含的表情、图片等多媒体元素，也有一些常见的处理方法。例如，把表情转换为编码存储，而图片可以存储在文件服务器上，并在数据库中保存链接地址。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;如果把用户评论当成非结构化数据，那么它的&lt;strong&gt;处理方式&lt;/strong&gt;就会更加复杂。&lt;/p&gt;
&lt;p&gt;用户评论的内容通常是文本信息，但其实不容易进行有效的结构化处理。评论的长度、格式、语言等都可能差异很大，甚至某些评论可能包含表情符号或者图片等多媒体元素。这些元素都无法通过预定义的数据模型进行有效地分类和组织，因此我们将其当做非结构化数据来处理。&amp;ndash;这里主要是指数据的&lt;strong&gt;组织方式&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;以下是一些具体的例子：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;评论情感分析：通过对用户评论的文本内容分析，我们可以识别出评论者的情绪态度，比如正面的、负面的，或者中性的。这对于公司来说是非常重要的，可以了解产品或者服务在消费者中的口碑和接受程度。&lt;/li&gt;
&lt;li&gt;评论分类：我们还可以将评论分到不同的类别。可以根据情绪分为好评、中评、差评。同时，还可以按照评论的内容将其分为产品评价，客服评价等类别。&lt;/li&gt;
&lt;li&gt;评论的全文搜索：对于用户评论这种非结构化数据的全文搜索，可以帮助我们即时搜索到关于某一产品或者某一特定主题的所有相关评论。&lt;/li&gt;
&lt;li&gt;主题模型：主题模型可以帮助我们从大量的评论中提炼出几个主要的话题，帮助公司了解消费者最关心的问题有哪些。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;具体实现架构如下：&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2025-03-14-shu-ju-ku-xuan-xing-zhong-ji-zhi-nan-cong-shu-ju-lei-xing-da/004-560fe49f.png"&gt;&lt;/p&gt;
&lt;p&gt;用户评论的存储与分析系统需结合多种技术实现高效处理。在存储层设计中，推荐采用混合存储架构以满足非结构化数据的持久化需求。核心存储使用MongoDB文档数据库保存完整的评论内容（如文本、表情编码、图片链接等），其灵活的JSON结构支持动态字段扩展，例如可包含用户设备信息、地理位置等元数据。同时，MongoDB的水平扩展能力和聚合查询功能可有效支持大规模数据管理。对于评论中的图片、视频等二进制文件，则通过对象存储（如Amazon S3或阿里云OSS）存储，结合预签名URL实现安全访问，避免数据库性能损耗。辅助索引层采用Elasticsearch同步关键字段，通过倒排索引和中文分词技术（如IK分词）实现秒级全文检索，并支持模糊搜索与高亮显示。&lt;/p&gt;
&lt;p&gt;在场景化应用中，情感分析可通过多种技术实现：对于中文评论，SnowNLP或Hugging Face的BERT模型能精准识别情感倾向，例如通过预训练模型对“电池续航太差”等文本输出负面标签及置信度评分。评论分类则结合监督学习（如SVM、BERT）与无监督方法（如K-Means聚类），通过FastAPI构建实时分类服务或使用Spark进行批量处理。全文搜索功能由Elasticsearch支撑，通过MongoDB Connector实现实时数据同步，支持用户快速定位包含特定关键词的评论内容。主题模型则利用LDA、BERTopic等算法从海量评论中提取高频主题（如“屏幕质量”“物流服务”），并通过WordCloud等工具可视化呈现，帮助业务方洞察用户关注焦点。整个架构通过混合存储与多技术协同，在保证性能的同时实现成本优化。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="应用场景"&gt;&lt;a href="#%e5%ba%94%e7%94%a8%e5%9c%ba%e6%99%af" class="header-anchor"&gt;&lt;/a&gt;应用场景
&lt;/h2&gt;&lt;p&gt;数据库选型的核心是：理解业务数据的生命周期，把握各类数据库的能力边界，在架构灵活性与技术可控性之间寻找最佳平衡点。任何脱离具体业务场景的数据库对比都是无效的，优秀的架构设计应当像精密钟表般，让每个齿轮（数据库）在最适合的位置发挥最大效能。&lt;/p&gt;
&lt;p&gt;结合典型应用场景，什么场景应该用什么数据库呢？其实在一个业务场景下需要多种类数据库结合使用，总结如下：&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2025-03-14-shu-ju-ku-xuan-xing-zhong-ji-zhi-nan-cong-shu-ju-lei-xing-da/005-00b9fa8f.png"&gt;&lt;/p&gt;
&lt;p&gt;我们以单个数据库为维度再分别讨论一下：&lt;/p&gt;
&lt;h3 id="关系型mysql"&gt;&lt;a href="#%e5%85%b3%e7%b3%bb%e5%9e%8bmysql" class="header-anchor"&gt;&lt;/a&gt;关系型:MySQL
&lt;/h3&gt;&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2025-03-14-shu-ju-ku-xuan-xing-zhong-ji-zhi-nan-cong-shu-ju-lei-xing-da/006-22a3aff1.png"&gt;&lt;/p&gt;
&lt;p&gt;MySQL：高并发事务系统（如电商订单处理）&lt;/p&gt;
&lt;p&gt;核心场景：电商平台的订单系统，需要保证每笔交易的原子性（如扣减库存、生成订单、支付记录必须同时成功或回滚）。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;为什么选择MySQL&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ACID事务支持：通过InnoDB引擎实现强一致性，确保订单状态的准确性。&lt;/li&gt;
&lt;li&gt;复杂查询能力：支持多表JOIN（如查询用户历史订单及商品详情）。&lt;/li&gt;
&lt;li&gt;成熟生态：主从复制、分库分表工具（如ShardingSphere）支持高可用和扩展。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;对比其他数据库&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;MongoDB&lt;/strong&gt;：不支持跨文档事务（早期版本），不适合强一致性场景。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Redis&lt;/strong&gt;：内存数据库，无法持久化复杂事务逻辑。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;示例&lt;/strong&gt;：每秒处理10万笔订单的电商平台，通过MySQL分库分表（按用户ID哈希）实现横向扩展。&lt;/p&gt;
&lt;h3 id="搜索引擎es"&gt;&lt;a href="#%e6%90%9c%e7%b4%a2%e5%bc%95%e6%93%8ees" class="header-anchor"&gt;&lt;/a&gt;搜索引擎：ES
&lt;/h3&gt;&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2025-03-14-shu-ju-ku-xuan-xing-zhong-ji-zhi-nan-cong-shu-ju-lei-xing-da/007-559c2219.png"&gt;&lt;/p&gt;
&lt;p&gt;Elasticsearch：实时商品搜索与日志分析&lt;/p&gt;
&lt;p&gt;核心场景：电商平台商品搜索，用户输入关键词（如“防水运动鞋”）后毫秒级返回结果。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;为什么选择Elasticsearch&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;倒排索引&lt;/strong&gt;：快速匹配关键词，支持分词、同义词扩展、模糊查询。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;聚合分析&lt;/strong&gt;：统计商品类目的平均评分、价格区间分布。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;近实时（NRT）&lt;/strong&gt;：新上架商品1秒内可被搜索。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;对比其他数据库&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;MySQL&lt;/strong&gt;：全文索引性能差，无法支持高并发搜索。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;MongoDB&lt;/strong&gt;：文本搜索功能简单，缺乏分词器和相关性排序。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;示例&lt;/strong&gt;：某跨境电商平台，每日处理1亿次搜索请求，通过ES集群（分片+副本）实现99.9%的查询响应时间&amp;lt;50ms。&lt;/p&gt;
&lt;h3 id="文档型mongodb"&gt;&lt;a href="#%e6%96%87%e6%a1%a3%e5%9e%8bmongodb" class="header-anchor"&gt;&lt;/a&gt;文档型：MongoDB
&lt;/h3&gt;&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2025-03-14-shu-ju-ku-xuan-xing-zhong-ji-zhi-nan-cong-shu-ju-lei-xing-da/008-6afc9d95.png"&gt;&lt;/p&gt;
&lt;p&gt;MongoDB：内容管理系统（CMS）与动态配置存储**&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;核心场景&lt;/strong&gt;：新闻发布平台的文章存储，每篇文章包含标题、正文、多级评论、动态标签。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;为什么选择MongoDB&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;灵活文档模型&lt;/strong&gt;：存储嵌套结构的JSON数据（如评论树形结构）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;水平扩展&lt;/strong&gt;：通过Sharding自动分配数据到多个分片。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;局部更新&lt;/strong&gt;：修改文章某个字段无需重写整个文档。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;对比其他数据库&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;MySQL&lt;/strong&gt;：需要拆分为多张表（文章表、评论表），JOIN查询效率低。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;HBase&lt;/strong&gt;：适合结构化扫描，不适合嵌套数据查询。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;示例&lt;/strong&gt;：某媒体平台存储1000万篇文章，每篇文章包含动态标签（如“科技, 2023趋势”），通过MongoDB的文档结构直接存储。&lt;/p&gt;
&lt;h3 id="键值存储redis"&gt;&lt;a href="#%e9%94%ae%e5%80%bc%e5%ad%98%e5%82%a8redis" class="header-anchor"&gt;&lt;/a&gt;键值存储：Redis
&lt;/h3&gt;&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2025-03-14-shu-ju-ku-xuan-xing-zhong-ji-zhi-nan-cong-shu-ju-lei-xing-da/009-116b15b9.png"&gt;&lt;/p&gt;
&lt;p&gt;Redis：高频访问缓存与会话管理&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;核心场景&lt;/strong&gt;：社交平台的热门帖子缓存，用户访问时优先从缓存读取，减少数据库压力。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;为什么选择Redis&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;内存存储&lt;/strong&gt;：读写延迟&amp;lt;1ms，支持每秒百万级操作。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;数据结构丰富&lt;/strong&gt;：使用Sorted Set存储热门帖子排行榜，Hash存储用户会话信息。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;持久化可选&lt;/strong&gt;：RDB快照或AOF日志保障数据安全。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;对比其他数据库&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;MySQL&lt;/strong&gt;：磁盘存储，无法满足毫秒级响应。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;MongoDB&lt;/strong&gt;：内存占用高，不适合纯缓存场景。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;示例&lt;/strong&gt;：某论坛每日活跃用户500万，通过Redis缓存前1000热门帖子，命中率90%，数据库负载下降70%。&lt;/p&gt;
&lt;h3 id="宽列存储hbasecassandra"&gt;&lt;a href="#%e5%ae%bd%e5%88%97%e5%ad%98%e5%82%a8hbasecassandra" class="header-anchor"&gt;&lt;/a&gt;宽列存储：HBase、Cassandra
&lt;/h3&gt;&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2025-03-14-shu-ju-ku-xuan-xing-zhong-ji-zhi-nan-cong-shu-ju-lei-xing-da/010-9e144a87.png"&gt;&lt;/p&gt;
&lt;p&gt;HBase：海量时序数据存储（如物联网设备监控）&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;核心场景&lt;/strong&gt;：电力公司存储智能电表每秒采集的电流、电压数据。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;为什么选择HBase&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;列族存储&lt;/strong&gt;：按列压缩时序数据，节省存储空间。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;随机读写&lt;/strong&gt;：按设备ID+时间戳快速查询某时刻数据。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;HDFS集成&lt;/strong&gt;：数据自动下沉至HDFS实现低成本归档。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;对比其他数据库&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Cassandra&lt;/strong&gt;：适合跨数据中心写入，但单点查询性能不如HBase。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;MySQL&lt;/strong&gt;：无法支持每秒百万级数据写入。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;示例&lt;/strong&gt;：某物联网平台每日新增1TB传感器数据，通过HBase的RowKey设计（设备ID+时间戳）实现毫秒级查询。&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2025-03-14-shu-ju-ku-xuan-xing-zhong-ji-zhi-nan-cong-shu-ju-lei-xing-da/011-87d2288b.png"&gt;&lt;/p&gt;
&lt;p&gt;Cassandra：多数据中心日志同步（如全球化应用）&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;核心场景&lt;/strong&gt;：跨国社交应用的聊天日志存储，要求数据在欧美亚三地就近写入且最终一致。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;为什么选择Cassandra&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;多活架构&lt;/strong&gt;：数据自动复制到多个数据中心，写入本地即成功。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;高吞吐写入&lt;/strong&gt;：LSM树引擎支持每秒百万级写入。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;无单点故障&lt;/strong&gt;：去中心化架构避免主从瓶颈。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;对比其他数据库&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;HBase&lt;/strong&gt;：依赖HDFS和ZooKeeper，扩展性受限。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;MySQL&lt;/strong&gt;：主从复制跨地域延迟高。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;示例&lt;/strong&gt;：某IM应用每日处理50亿条消息，通过Cassandra实现三地数据中心写入延迟&amp;lt;10ms。&lt;/p&gt;
&lt;h3 id="数据仓库hive"&gt;&lt;a href="#%e6%95%b0%e6%8d%ae%e4%bb%93%e5%ba%93hive" class="header-anchor"&gt;&lt;/a&gt;数据仓库：Hive
&lt;/h3&gt;&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2025-03-14-shu-ju-ku-xuan-xing-zhong-ji-zhi-nan-cong-shu-ju-lei-xing-da/012-a845c0c5.png"&gt;&lt;/p&gt;
&lt;p&gt;Hive：离线数据仓库与ETL批处理&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;核心场景&lt;/strong&gt;：零售企业每月销售数据的批量清洗与报表生成。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;为什么选择Hive&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;SQL兼容&lt;/strong&gt;：通过HiveQL实现类SQL查询，降低学习成本。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;海量数据批处理&lt;/strong&gt;：基于MapReduce或Tez引擎处理TB级数据。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;低成本存储&lt;/strong&gt;：数据存储在HDFS，支持压缩格式（ORC、Parquet）。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;对比其他数据库&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;ClickHouse&lt;/strong&gt;：适合实时分析，但存储成本高。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;MySQL&lt;/strong&gt;：无法处理PB级数据。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;示例&lt;/strong&gt;：某电商每月分析10TB历史订单数据，通过Hive生成“年度区域销售趋势”报表，耗时2小时。&lt;/p&gt;
&lt;h3 id="列式存储clickhouse"&gt;&lt;a href="#%e5%88%97%e5%bc%8f%e5%ad%98%e5%82%a8clickhouse" class="header-anchor"&gt;&lt;/a&gt;列式存储：ClickHouse
&lt;/h3&gt;&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2025-03-14-shu-ju-ku-xuan-xing-zhong-ji-zhi-nan-cong-shu-ju-lei-xing-da/013-c7953de3.png"&gt;&lt;/p&gt;
&lt;p&gt;ClickHouse：实时OLAP与用户行为分析&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;核心场景&lt;/strong&gt;：广告平台的实时点击流分析，每日处理千亿级事件，生成实时报表。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;为什么选择ClickHouse&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;列式存储&lt;/strong&gt;：压缩率高，适合聚合计算（如SUM、COUNT）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;向量化执行&lt;/strong&gt;：利用CPU SIMD指令加速查询。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;实时写入&lt;/strong&gt;：支持Kafka直接导入数据，延迟低至秒级。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;对比其他数据库&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Hive&lt;/strong&gt;：批处理模式，查询延迟分钟级。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;MySQL&lt;/strong&gt;：无法支撑海量数据聚合。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;示例&lt;/strong&gt;：某广告平台分析每日200亿次点击事件，通过ClickHouse集群实现“过去1小时各渠道转化率”秒级响应。&lt;/p&gt;
&lt;h3 id="图数据库neo4j"&gt;&lt;a href="#%e5%9b%be%e6%95%b0%e6%8d%ae%e5%ba%93neo4j" class="header-anchor"&gt;&lt;/a&gt;图数据库：Neo4j
&lt;/h3&gt;&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2025-03-14-shu-ju-ku-xuan-xing-zhong-ji-zhi-nan-cong-shu-ju-lei-xing-da/014-d3af3ea6.png"&gt;&lt;/p&gt;
&lt;p&gt;Neo4j：社交网络关系挖掘（如好友推荐）&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;核心场景&lt;/strong&gt;：社交平台的“六度关系”分析，计算用户A到用户B的最短路径。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;为什么选择Neo4j&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;图遍历优化&lt;/strong&gt;：通过原生图存储引擎高效遍历多跳关系。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cypher查询语言&lt;/strong&gt;：直观表达复杂关系模式（如查找共同好友）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;实时更新&lt;/strong&gt;：支持动态添加节点和边。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;对比其他数据库&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;MySQL&lt;/strong&gt;：需递归JOIN，性能随跳数指数级下降。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;MongoDB&lt;/strong&gt;：无法直接表达关系网络。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;示例&lt;/strong&gt;：某社交平台分析10亿用户关系，Neo4j可在毫秒级返回“用户A的三度人脉中可能认识的人”。&lt;/p&gt;
&lt;h3 id="总结-2"&gt;&lt;a href="#%e6%80%bb%e7%bb%93-2" class="header-anchor"&gt;&lt;/a&gt;总结
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;事务强一致&lt;/strong&gt; → MySQL&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;实时搜索&lt;/strong&gt; → Elasticsearch&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;动态文档&lt;/strong&gt; → MongoDB&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;高频缓存&lt;/strong&gt; → Redis&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;实时OLAP&lt;/strong&gt; → ClickHouse&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;时序海量存储&lt;/strong&gt; → HBase&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;全球化写入&lt;/strong&gt; → Cassandra&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;关系网络&lt;/strong&gt; → Neo4j&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;离线批处理&lt;/strong&gt; → Hive&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2025-03-14-shu-ju-ku-xuan-xing-zhong-ji-zhi-nan-cong-shu-ju-lei-xing-da/015-95068fb0.png"&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="最后总结"&gt;&lt;a href="#%e6%9c%80%e5%90%8e%e6%80%bb%e7%bb%93" class="header-anchor"&gt;&lt;/a&gt;最后总结
&lt;/h2&gt;&lt;p&gt;**数据模型的本质差异是选型的第一道分水岭。**关系型数据库（如MySQL、PostgreSQL）建立在严格的二维表结构之上，通过外键约束和范式理论保障数据完整性。这种结构特别适合需要复杂关联查询的财务系统、ERP等业务场景。例如银行转账操作需要严格遵循ACID事务原则，MySQL的InnoDB引擎通过行级锁和MVCC机制实现事务隔离，配合主从复制架构可以满足多数金融级需求。但在物联网设备日志存储场景下，每天千万级的写入请求会导致关系型数据库的索引维护成本急剧上升，此时文档型数据库MongoDB的BSON自由格式和分片集群优势便显现出来。MongoDB的写操作默认不等待磁盘确认，通过内存映射文件实现高速写入，特别适合内容管理系统或实时分析场景中半结构化数据的快速摄入。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;分布式架构的CAP权衡直接影响系统可用性。&lt;/strong&gt; Elasticsearch作为分布式搜索引擎，其倒排索引结构对文本检索的优化已达到毫秒级响应，在电商商品搜索、日志分析等场景具有不可替代性。但ES的强一致性模型可能导致集群脑裂风险，需要结合zen discovery机制进行节点状态管理。相比之下，HBase作为Hadoop生态的列式存储，通过RegionServer的水平扩展和LSM树的写入优化，能够承载PB级数据量的实时读写。某智慧城市项目曾使用HBase存储数十亿条交通卡口数据，利用其行键有序分布特性实现车辆轨迹的快速回溯，这是传统关系型数据库难以企及的吞吐能力。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;计算与存储的分离趋势重构了数据分析范式。&lt;/strong&gt; Hive建立在HDFS之上的元数据管理机制，通过类SQL语法实现大数据集的离线分析，其分区表和桶表的设计显著提升了TB级数据查询效率。某电商平台的历史订单分析采用Hive进行月度销售统计，配合Tez执行引擎将任务耗时从小时级压缩到分钟级。但Hive的高延迟特性使其不适合实时查询场景，这正是ClickHouse等OLAP数据库的突破方向。需要特别注意的是，数据湖架构的兴起使得Delta Lake、Hudi等解决方案开始融合事务管理和批流一体处理，这对传统数仓选型提出了新的挑战。&lt;/p&gt;
&lt;p&gt;**事务完整性与系统弹性的平衡艺术。**当业务需要跨数据库操作时，如电商订单系统同时涉及MySQL库存扣减和MongoDB订单日志记录，分布式事务管理就成为关键挑战。Saga模式通过补偿机制实现最终一致性，而Seata框架的AT模式能在业务侵入性较低的情况下保障事务边界。但在高并发场景下，这类方案的性能损耗可能达到20%-30%，这就需要架构师在一致性级别和系统吞吐之间做出权衡。例如社交平台的点赞功能更适合使用Redis的原子计数器，完全放弃强一致性以换取百万级QPS的处理能力。&lt;/p&gt;
&lt;p&gt;**硬件成本与运维复杂度的隐藏成本。**云原生时代，AWS Aurora通过计算存储分离架构实现了MySQL兼容数据库的自动扩缩容，其存储层可自动扩展到128TB，这种托管服务显著降低了运维负担。但对于需要定制化优化的场景，如金融行业的风控模型计算，仍需要基于物理机部署的Oracle RAC集群来保障IOPS性能。开源方案的隐性成本同样不容忽视，Elasticsearch集群的JVM堆内存配置直接影响索引性能，不当的分片设置可能导致磁盘空间浪费，这需要运维团队积累足够的调优经验。&lt;/p&gt;
&lt;p&gt;在具体选型实践中，建议采用四维评估法：首先明确数据结构化程度（结构化、半结构化、非结构化），其次分析读写比例和并发量级，再次确定一致性要求（强一致、最终一致），最后考量扩展性和生态集成需求。例如智能穿戴设备数据采集场景，设备标识符作为MongoDB文档的天然主键，时间序列数据采用嵌套文档存储，既避免了关系型数据库的表关联开销，又利用TTL索引实现自动过期清理。而在用户画像分析场景，HBase 的宽表结构可以存储数千个用户标签，配合Phoenix的SQL层实现灵活查询，这种架构组合充分发挥了列式存储的高压缩比优势。&lt;/p&gt;
&lt;p&gt;最后我们用一个简单的流程图来说明一下这个选型过程：&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2025-03-14-shu-ju-ku-xuan-xing-zhong-ji-zhi-nan-cong-shu-ju-lei-xing-da/016-d55b1bf4.png"&gt;&lt;/p&gt;</description></item><item><title>面试必问：ACID 你真的懂了吗？</title><link>https://xiaobox.github.io/p/2025-02-19-mian-shi-bi-wen-acid-ni-zhen-de-dong-le-ma/</link><pubDate>Wed, 19 Feb 2025 07:45:55 +0000</pubDate><guid>https://xiaobox.github.io/p/2025-02-19-mian-shi-bi-wen-acid-ni-zhen-de-dong-le-ma/</guid><description>&lt;img src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2025-02-19-mian-shi-bi-wen-acid-ni-zhen-de-dong-le-ma/cover.jpg" alt="Featured image of post 面试必问：ACID 你真的懂了吗？" /&gt;&lt;h2 id="acid-是什么"&gt;&lt;a href="#acid-%e6%98%af%e4%bb%80%e4%b9%88" class="header-anchor"&gt;&lt;/a&gt;ACID 是什么？
&lt;/h2&gt;&lt;p&gt;事务处理中的 ACID 是确保数据库操作可靠性和完整性的四个核心特性&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;&lt;strong&gt;属性&lt;/strong&gt;&lt;/th&gt;
 &lt;th&gt;&lt;strong&gt;说明&lt;/strong&gt;&lt;/th&gt;
 &lt;th&gt;&lt;strong&gt;示例&lt;/strong&gt;&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;原子性（Atomicity）&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;事务是不可分割的最小操作单元，事务中的所有操作要么全部成功完成，要么全部失败回滚。&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;用户在线购买书籍时的支付流程：&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;①支付扣款 ②库存扣减 ③快递下单三个步骤必须全部成功——任一步骤失败时（如库存不足），系统自动取消已付金额，退回到购物未进行状态。&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;一致性（Consistency）&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;事务必须保证数据库从一个一致性状态转变到另一个一致性状态。一致性是指数据必须符合预定义的规则和约束，例如完整性约束、业务规则等。&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;银行转账场景：&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;账户A向账户B转200元后，两人账户总额保持不变（若A+B原为1000元，操作完成后仍为1000）。即便系统中途崩溃，恢复后也不会出现A扣200元但B未入账的金额&amp;quot;凭空消失&amp;quot;。&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;隔离性（Isolation）&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;多个事务并发执行时，每个事务都应该感觉不到其他事务的存在，就像在隔离的环境中执行一样。事务之间互相隔离，不会互相影响。&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;航班订座系统：&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;当乘客A和B同时选择最后一个座位，先完成支付者的订单立即锁定座位，另一用户将实时看到&amp;quot;无余票&amp;quot;提示——避免出现系统误判导致超售。&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;持久性（Durability）&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;一旦事务提交成功，对数据库的修改就应该是永久性的，即使系统发生崩溃或重启等意外情况，数据也不会丢失。&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;线上预约挂号确认：&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;用户成功提交预约后，即便医院服务器遭遇断电，重启后系统依然保留该条预约记录并发送确认短信，不会因突发意外丢失数据。&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id="mysql-是如何保证-acid-的"&gt;&lt;a href="#mysql-%e6%98%af%e5%a6%82%e4%bd%95%e4%bf%9d%e8%af%81-acid-%e7%9a%84" class="header-anchor"&gt;&lt;/a&gt;MySQL 是如何保证 ACID 的？
&lt;/h2&gt;&lt;p&gt;MySQL 实现 ACID 特性主要依赖 日志系统（undo log 和 redo log）、锁机制 和 MVCC 多版本并发控制。下面是具体实现原理的详细分析：&lt;/p&gt;
&lt;h3 id="一atomicity原子性"&gt;&lt;a href="#%e4%b8%80atomicity%e5%8e%9f%e5%ad%90%e6%80%a7" class="header-anchor"&gt;&lt;/a&gt;一、Atomicity（原子性）
&lt;/h3&gt;&lt;p&gt;事务是不可分割的最小执行单位。原子性确保事务中的所有操作要么全部成功完成，要么全部失败回滚。不允许中间状态。MySQL 通过 &lt;strong&gt;Undo Log + 事务回滚&lt;/strong&gt; 实现原子性：&lt;/p&gt;
&lt;p&gt;当事务开始时，InnoDB 会记录事务修改前的数据（旧版本）到 &lt;code&gt;Undo Log&lt;/code&gt; 中，用于事务回滚时恢复原始状态。&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;&lt;code&gt;Undo Log&lt;/code&gt; 记录结构包含：原始数据值、事务ID（trx_id）、回滚指针（roll_pointer）。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2025-02-19-mian-shi-bi-wen-acid-ni-zhen-de-dong-le-ma/001-8d841891.png"&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Undo Log&lt;/code&gt; 记录的是逻辑操作，例如 &amp;ldquo;删除第 10 行&amp;rdquo;，&amp;ldquo;将字段 &amp;rsquo;name&amp;rsquo; 从 &amp;lsquo;old&amp;rsquo; 更新为 &amp;rsquo;new&amp;rsquo;&amp;rdquo; 等。&lt;/p&gt;
&lt;p&gt;举个简单例子：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;1&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;-- 事务未提交时，其他事务通过Undo Log读取原始数据（MVCC）
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;2&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;BEGIN&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;3&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;UPDATE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;accounts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;SET&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;balance&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;balance&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;4&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;-- 此时Undo Log会记录balance的旧值（如200）
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;5&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;ROLLBACK&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;-- 使用Undo Log恢复数据
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;如果事务执行过程中发生错误或者用户显式执行 ROLLBACK，InnoDB 可以根据 &lt;code&gt;Undo Log&lt;/code&gt; 中的记录将数据恢复到事务开始之前的状态，从而实现事务的回滚&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="二isolation隔离性"&gt;&lt;a href="#%e4%ba%8cisolation%e9%9a%94%e7%a6%bb%e6%80%a7" class="header-anchor"&gt;&lt;/a&gt;二、Isolation（隔离性）
&lt;/h3&gt;&lt;p&gt;隔离性 (Isolation) 是 ACID 特性中的关键一环，它确保在多个事务并发执行时，每个事务都仿佛独立运行，互不干扰。 换句话说，一个事务的中间状态和操作不应该被其他并发事务感知到，从而避免数据混乱和不一致。 为了实现这种隔离效果，MySQL 的 InnoDB 存储引擎主要依赖于两大核心机制：&lt;strong&gt;锁机制 (Locking)&lt;/strong&gt; 和 &lt;strong&gt;多版本并发控制 (MVCC)&lt;/strong&gt;&lt;/p&gt;
&lt;h4 id="1-锁机制"&gt;&lt;a href="#1-%e9%94%81%e6%9c%ba%e5%88%b6" class="header-anchor"&gt;&lt;/a&gt;1. 锁机制
&lt;/h4&gt;&lt;p&gt;首先，&lt;strong&gt;锁机制是最基础的隔离手段&lt;/strong&gt;。InnoDB 实现了多种锁类型，以适应不同的并发场景和隔离需求。 其中，&lt;code&gt;行级锁&lt;/code&gt; 是 InnoDB 并发控制的核心，它允许事务仅锁定需要修改的数据行，最大程度地提高了并发度。 行级锁又细分为 &lt;code&gt;共享锁 (S 锁)&lt;/code&gt; 和 &lt;code&gt;排他锁 (X 锁)&lt;/code&gt;，前者允许多个事务同时读取同一行数据，而后者则保证在更新或删除数据时，只有一个事务可以独占该行。&lt;/p&gt;
&lt;p&gt;除了行级锁，MySQL 还提供 &lt;code&gt;表级锁&lt;/code&gt;，它会锁定整个表，虽然并发度较低，但在某些特定场景（如执行 LOCK TABLES 语句）下仍然适用。&lt;/p&gt;
&lt;p&gt;为了更高效地管理锁，InnoDB 引入了 &lt;code&gt;意向锁 (Intention Locks)&lt;/code&gt;，它在表级别上预先声明事务对行级锁的意图，从而优化锁的检查和兼容性。&lt;/p&gt;
&lt;p&gt;此外，在 REPEATABLE READ 和 SERIALIZABLE 这两个较高的隔离级别下，为了解决幻读问题，InnoDB 还使用了 &lt;code&gt;间隙锁 (Gap Locking)&lt;/code&gt;，它不仅锁定已存在的记录，还锁定索引记录之间的间隙，防止其他事务在该间隙中插入新记录，从而彻底避免幻读。&lt;/p&gt;
&lt;p&gt;我们通过一个具体的例子来说明 InnoDB 的 间隙锁（Gap Locking） 如何解决幻读问题：&lt;/p&gt;
&lt;p&gt;假设有一张表 &lt;code&gt;students&lt;/code&gt;，存储学生信息，主键为 &lt;code&gt;id&lt;/code&gt;，当前数据如下：&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;id&lt;/th&gt;
 &lt;th&gt;name&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;1&lt;/td&gt;
 &lt;td&gt;Alice&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;3&lt;/td&gt;
 &lt;td&gt;Bob&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;5&lt;/td&gt;
 &lt;td&gt;Charlie&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;现在有两个事务 &lt;strong&gt;事务A&lt;/strong&gt; 和 &lt;strong&gt;事务A&lt;/strong&gt;，操作顺序如下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;事务A&lt;/strong&gt; 执行范围查询并加锁：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;1&lt;/span&gt;&lt;span class="cl"&gt;BEGIN;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;2&lt;/span&gt;&lt;span class="cl"&gt;SELECT * FROM students WHERE id BETWEEN 1 AND 5 FOR UPDATE;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;3&lt;/span&gt;&lt;span class="cl"&gt;-- 查询结果：id=1, 3, 5
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;InnoDB 会为 &lt;code&gt;id&lt;/code&gt; 索引加上 &lt;strong&gt;Next-Key Lock&lt;/strong&gt;（行锁 + 间隙锁），锁定的范围包括：&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;(-∞, 1]&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;(1, 3]&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;(3, 5]&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;(5, +∞)&lt;/code&gt;
（注：假设表中无其他数据）&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start="3"&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;事务B&lt;/strong&gt; 尝试插入新数据：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;1&lt;/span&gt;&lt;span class="cl"&gt;BEGIN;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;2&lt;/span&gt;&lt;span class="cl"&gt;INSERT INTO students (id, name) VALUES (2, &amp;#39;David&amp;#39;); -- 尝试插入到间隙 (1,3)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;3&lt;/span&gt;&lt;span class="cl"&gt;-- 或者
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;4&lt;/span&gt;&lt;span class="cl"&gt;INSERT INTO students (id, name) VALUES (4, &amp;#39;Eve&amp;#39;); -- 尝试插入到间隙 (3,5)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;由于 &lt;strong&gt;事务A&lt;/strong&gt; 的间隙锁锁定了 &lt;code&gt;(1,3)&lt;/code&gt; 和 &lt;code&gt;(3,5)&lt;/code&gt; 的间隙，&lt;strong&gt;事务B&lt;/strong&gt; 的插入操作会被阻塞，直到 &lt;strong&gt;事务A&lt;/strong&gt; 提交或回滚。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;事务A&lt;/strong&gt; 提交：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;1&lt;/span&gt;&lt;span class="cl"&gt;COMMIT;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;事务B&lt;/strong&gt; 的插入操作才会继续执行。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;结果对比&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;没有间隙锁&lt;/strong&gt;：
若事务A未加间隙锁（例如使用 &lt;code&gt;READ COMMITTED&lt;/code&gt; 隔离级别），事务B可以插入 &lt;code&gt;id=2&lt;/code&gt; 或 &lt;code&gt;id=4&lt;/code&gt;。当事务A再次执行 &lt;code&gt;SELECT&lt;/code&gt; 时，会看到新插入的行（id=2 或 4），导致&lt;strong&gt;幻读&lt;/strong&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;有间隙锁&lt;/strong&gt;：
事务B的插入操作被阻塞，直到事务A释放锁。事务A在事务执行期间始终看到相同的数据（id=1,3,5），&lt;strong&gt;避免幻读&lt;/strong&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="关键点"&gt;&lt;a href="#%e5%85%b3%e9%94%ae%e7%82%b9" class="header-anchor"&gt;&lt;/a&gt;&lt;strong&gt;关键点&lt;/strong&gt;
&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;间隙锁的锁定范围&lt;/strong&gt;：
InnoDB 的间隙锁不仅锁定已存在的行，还会锁定索引记录之间的“间隙”（例如 &lt;code&gt;(1,3)&lt;/code&gt; 和 &lt;code&gt;(3,5)&lt;/code&gt;），阻止其他事务在间隙中插入新数据。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Next-Key Lock 的作用&lt;/strong&gt;：
Next-Key Lock = 行锁（锁定已存在记录） + 间隙锁（锁定间隙）。例如，对 &lt;code&gt;id=3&lt;/code&gt; 的行锁会锁定范围 &lt;code&gt;(1,3]&lt;/code&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;隔离级别的影响&lt;/strong&gt;：
间隙锁仅在 &lt;code&gt;REPEATABLE READ&lt;/code&gt; 隔离级别下生效。在 &lt;code&gt;READ COMMITTED&lt;/code&gt; 级别下，InnoDB 会禁用间隙锁，幻读仍可能发生。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;在实际场景中，比如在电商系统中，若一个事务正在统计某商品（例如库存范围在 100~200）的订单数量，间隙锁可以防止其他事务插入新的订单记录（例如库存为 150 的商品），确保统计结果的一致性。&lt;/p&gt;
&lt;h4 id="2-mvcc"&gt;&lt;a href="#2-mvcc" class="header-anchor"&gt;&lt;/a&gt;2. MVCC
&lt;/h4&gt;&lt;p&gt;为了进一步提升并发性能，尤其是在读多写少的场景下，InnoDB 引入了 &lt;code&gt;多版本并发控制 (MVCC)&lt;/code&gt;。 &lt;strong&gt;MVCC 的核心思想是允许事务在读取数据时，访问数据在某个时间点的快照版本，而不是直接读取最新的数据。&lt;/strong&gt; 这样，读操作就不需要等待写操作完成，从而实现读写并发执行，显著提高了系统吞吐量。 MVCC 的实现依赖于 &lt;code&gt;Undo Log&lt;/code&gt; 和 &lt;code&gt;Read View (快照读)&lt;/code&gt;。 &lt;code&gt;Undo Log&lt;/code&gt; 用于记录数据的历史版本，而 &lt;code&gt;Read View&lt;/code&gt; 则定义了事务在读取数据时应该看到哪个版本的数据。MVCC 主要应用于 READ COMMITTED 和 REPEATABLE READ 这两个隔离级别，在这两个级别下，MVCC 可以有效减少锁的竞争，提升并发性能。&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2025-02-19-mian-shi-bi-wen-acid-ni-zhen-de-dong-le-ma/002-9516a24c.png"&gt;&lt;/p&gt;
&lt;p&gt;总结来说：MVCC 就是基于隐藏字段、undo_log 链和 ReadView 来实现的&lt;/p&gt;
&lt;h4 id="3-隔离级别与策略对比"&gt;&lt;a href="#3-%e9%9a%94%e7%a6%bb%e7%ba%a7%e5%88%ab%e4%b8%8e%e7%ad%96%e7%95%a5%e5%af%b9%e6%af%94" class="header-anchor"&gt;&lt;/a&gt;3. 隔离级别与策略对比
&lt;/h4&gt;&lt;p&gt;最后，为了满足不同应用场景对隔离程度和性能的不同需求，MySQL 提供了 四种事务隔离级别。 从最低的 READ UNCOMMITTED (读未提交) 到最高的 SERIALIZABLE (串行化)，隔离级别依次增强，但并发性能也随之降低。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;READ UNCOMMITTED 允许脏读，隔离性最弱，但性能最高；&lt;/li&gt;
&lt;li&gt;READ COMMITTED 避免了脏读，但可能出现不可重复读；&lt;/li&gt;
&lt;li&gt;REPEATABLE READ (&lt;strong&gt;InnoDB 默认级别&lt;/strong&gt;) 在 READ COMMITTED 的基础上解决了不可重复读，但仍可能存在幻读（在某些情况下，InnoDB 通过 Next-Key Locking 尝试解决幻读）；&lt;/li&gt;
&lt;li&gt;SERIALIZABLE 通过强制事务串行执行，彻底避免了所有并发问题，但并发性能也最低。&lt;/li&gt;
&lt;/ul&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;隔离级别&lt;/th&gt;
 &lt;th&gt;脏读&lt;/th&gt;
 &lt;th&gt;不可重复读&lt;/th&gt;
 &lt;th&gt;幻读&lt;/th&gt;
 &lt;th&gt;实现方式&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;READ UNCOMMITTED&lt;/td&gt;
 &lt;td&gt;✔️&lt;/td&gt;
 &lt;td&gt;✔️&lt;/td&gt;
 &lt;td&gt;✔️&lt;/td&gt;
 &lt;td&gt;无锁&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;READ COMMITTED&lt;/td&gt;
 &lt;td&gt;✖️&lt;/td&gt;
 &lt;td&gt;✔️&lt;/td&gt;
 &lt;td&gt;✔️&lt;/td&gt;
 &lt;td&gt;每个SELECT生成新Read View&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;REPEATABLE READ*&lt;/td&gt;
 &lt;td&gt;✖️&lt;/td&gt;
 &lt;td&gt;✖️&lt;/td&gt;
 &lt;td&gt;✖️△&lt;/td&gt;
 &lt;td&gt;首SELECT生成Read View + 间隙锁&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;SERIALIZABLE&lt;/td&gt;
 &lt;td&gt;✖️&lt;/td&gt;
 &lt;td&gt;✖️&lt;/td&gt;
 &lt;td&gt;✖️&lt;/td&gt;
 &lt;td&gt;所有SELECT隐式转成&lt;code&gt;SELECT ... FOR UPDATE&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;

 &lt;blockquote&gt;
 &lt;p&gt;△：MySQL通过Next-Key Lock（行锁+间隙锁组合）在REPEATABLE READ级别实际消除幻读。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;hr&gt;
&lt;h3 id="三durability持久性"&gt;&lt;a href="#%e4%b8%89durability%e6%8c%81%e4%b9%85%e6%80%a7" class="header-anchor"&gt;&lt;/a&gt;三、Durability（持久性）
&lt;/h3&gt;&lt;p&gt;持久性 (Durability) 是 ACID 特性中保障数据安全性的最后一道防线。 它确保一旦事务成功提交，对数据库所做的所有更改都必须被永久地保存下来，即使系统随后发生崩溃、断电或任何其他类型的故障，已提交的数据也绝不会丢失。 为了实现这种强大的数据保障，MySQL 的 InnoDB 存储引擎采用了一系列精密的机制，其中最核心的是 &lt;code&gt;Redo Log&lt;/code&gt; (重做日志)，并辅以 &lt;code&gt;Write-Ahead Logging (WAL)&lt;/code&gt; 策略、 &lt;code&gt;Doublewrite Buffer&lt;/code&gt; (双写缓冲区) 和灵活的 刷盘 (Flush to Disk) 机制，同时，&lt;code&gt;Binlog&lt;/code&gt; (二进制日志) 也从更广泛的层面为数据持久性提供了支持。&lt;/p&gt;
&lt;p&gt;首先，Redo Log 是 InnoDB 实现持久性的基石。 当一个事务执行过程中，InnoDB 并不会立即将数据页的修改直接写入磁盘上的数据文件，而是先将这些修改操作，例如插入、更新或删除的具体内容，以一种紧凑、高效的形式，顺序地记录到 Redo Log Buffer 中。 这里的 Redo Log 记录的是物理层面的修改，例如“将数据页 X 的偏移量 Y 处的 Z 个字节修改为新的值”。 为了保证效率，Redo Log Buffer 存在于内存中，但为了确保持久性，InnoDB 会定期或者在事务提交时，将 Redo Log Buffer 中的内容刷新到 Redo Log 文件 这一磁盘上的持久化存储。&lt;/p&gt;
&lt;p&gt;为了进一步确保数据在极端情况下的安全性，InnoDB 遵循 &lt;code&gt;Write-Ahead Logging&lt;/code&gt; (WAL) 预写式日志 策略。 这意味着，在任何数据页的实际修改被写入磁盘数据文件之前，必须先将相应的 Redo Log 记录落盘到 Redo Log 文件中。 &lt;strong&gt;这种 “先写日志，后写数据” 的机制至关重要，它保证了即使在数据页尚未完全刷入磁盘时系统发生崩溃，已经提交的事务的所有修改操作也已经安全地记录在 Redo Log 中，从而为后续的数据恢复提供了保障。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2025-02-19-mian-shi-bi-wen-acid-ni-zhen-de-dong-le-ma/003-3900f38d.png"&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Doublewrite Buffer&lt;/code&gt; (双写缓冲区) 是 InnoDB 为了应对数据页“部分写失效 (Partial Write)” 问题而引入的增强机制。 在数据页从内存刷新到磁盘数据文件的过程中，可能会因为断电等意外情况，导致数据页只写入了一部分，造成数据损坏。 为了避免这种情况，InnoDB 在数据页最终写入数据文件之前，会先将其完整地写入 Doublewrite Buffer 区域。 Doublewrite Buffer 是磁盘上一个连续的存储区域，InnoDB 会先顺序写入，保证写入的原子性。 之后，再将数据页从 Doublewrite Buffer 拷贝到真正的数据文件位置。 这样，即使在数据页写入过程中发生崩溃，InnoDB 在重启恢复时，可以通过 Doublewrite Buffer 检查数据页的完整性。 如果发现数据页写入不完整或已损坏，可以从 Doublewrite Buffer 中找到该数据页的完整副本进行恢复，从而有效地避免了数据页部分写入导致的数据丢失。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Flush to Disk&lt;/code&gt; 机制 则提供了对 Redo Log 和数据页刷盘行为的精细控制。 MySQL 提供了多个参数，例如 innodb_flush_log_at_trx_commit 参数控制 Redo Log 何时刷盘，可以设置为每次事务提交都刷盘 (最安全，但性能较低)，或者定期刷盘 (性能较高，但可能在崩溃时丢失少量已提交事务)。 innodb_flush_method 参数则控制数据页刷盘的具体方式，例如是否绕过操作系统缓存直接写入磁盘，以满足不同的性能和可靠性需求。 通过调整这些刷盘策略，用户可以在数据安全性和性能之间进行权衡，根据实际业务场景选择合适的配置。&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2025-02-19-mian-shi-bi-wen-acid-ni-zhen-de-dong-le-ma/004-7797e2c0.png"&gt;&lt;/p&gt;
&lt;p&gt;最后，虽然 Binlog (二进制日志) 的主要用途是用于数据库的主从复制和时间点恢复，但它也间接地为数据持久性做出了贡献。 Binlog 记录了数据库中所有的数据变更操作 (逻辑操作，例如 SQL 语句)，这些日志可以用于数据库的备份和恢复，特别是当需要进行全量或增量备份，或者需要恢复到某个特定的时间点时，Binlog 就显得至关重要。 虽然 Binlog 的关注点和 Redo Log 略有不同 (Redo Log 侧重于崩溃恢复，Binlog 侧重于时间点恢复和复制)，但它们都为确保数据的长期安全性和可恢复性提供了重要的支持。&lt;/p&gt;
&lt;p&gt;总结来说：&lt;strong&gt;MySQL InnoDB 通过 Redo Log + WAL 策略 保障事务提交的修改能够被可靠地记录下来， Doublewrite Buffer 增强了数据页写入的可靠性，Flush to Disk 机制 提供了灵活的刷盘控制，而 Binlog 则从更广泛的层面支持数据备份和时间点恢复&lt;/strong&gt;。 这些机制相互配合，共同构建了持久性保障体系。&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="四consistency一致性"&gt;&lt;a href="#%e5%9b%9bconsistency%e4%b8%80%e8%87%b4%e6%80%a7" class="header-anchor"&gt;&lt;/a&gt;四、Consistency（一致性）
&lt;/h3&gt;&lt;p&gt;事务必须保证数据库从一个一致性状态转变到另一个一致性状态。一致性是指数据库的完整性约束没有被破坏。例如，主键唯一性、外键约束、CHECK 约束等。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;约束 (Constraints): MySQL 支持各种约束，如主键 (PRIMARY KEY)、外键 (FOREIGN KEY)、唯一键 (UNIQUE)、非空 (NOT NULL)、检查约束 (CHECK) 等。这些约束在数据写入时被强制执行，确保数据满足预定义的规则。&lt;/li&gt;
&lt;li&gt;触发器 (Triggers): 触发器是与表关联的存储程序，在特定事件 (如 INSERT、UPDATE、DELETE) 发生时自动执行。触发器可以用于实现更复杂的业务规则和一致性检查。&lt;/li&gt;
&lt;li&gt;应用程序逻辑: 虽然 MySQL 提供了约束和触发器，但最终的数据一致性也需要应用程序逻辑来保证。例如，业务逻辑需要确保事务操作符合业务规则，才能维持数据库的一致性状态。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;其实对于一致性来说，&lt;strong&gt;它是其他三者（原子性、隔离性、持久性）的综合结果，辅以数据库约束和应用校验来共同保障最终一致性。&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id="总结"&gt;&lt;a href="#%e6%80%bb%e7%bb%93" class="header-anchor"&gt;&lt;/a&gt;&lt;strong&gt;总结&lt;/strong&gt;
&lt;/h2&gt;&lt;p&gt;MySQL通过以下核心机制实现ACID：&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;ACID特性&lt;/th&gt;
 &lt;th&gt;核心机制&lt;/th&gt;
 &lt;th&gt;关键组件&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;原子性&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;Undo Log + 事务状态管理&lt;/td&gt;
 &lt;td&gt;Undo Log、事务控制块&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;一致性&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;约束 + ACID协同&lt;/td&gt;
 &lt;td&gt;主键、外键、触发器&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;隔离性&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;MVCC + 锁 + Next-Key Locks&lt;/td&gt;
 &lt;td&gt;Read View、行锁、间隙锁&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;持久性&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;Redo Log + Doublewrite Buffer&lt;/td&gt;
 &lt;td&gt;Redo Log、双写缓冲区&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;我们经常说的&lt;strong&gt;最终一致性&lt;/strong&gt;是其他三个特性协同作用的结果，而非独立机制。当你理解了这些底层原理将会有助于优化事务设计（如合理选择隔离级别）和故障排查（如分析锁冲突）。&lt;/p&gt;</description></item><item><title>数据库选型必看：MySQL 与 MariaDB 功能对比全解析</title><link>https://xiaobox.github.io/p/2024-09-09-shu-ju-ku-xuan-xing-bi-kan-mysql-yu-mariadb-gong-neng-dui-bi/</link><pubDate>Mon, 09 Sep 2024 04:12:18 +0000</pubDate><guid>https://xiaobox.github.io/p/2024-09-09-shu-ju-ku-xuan-xing-bi-kan-mysql-yu-mariadb-gong-neng-dui-bi/</guid><description>&lt;img src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2024-09-09-shu-ju-ku-xuan-xing-bi-kan-mysql-yu-mariadb-gong-neng-dui-bi/cover.jpg" alt="Featured image of post 数据库选型必看：MySQL 与 MariaDB 功能对比全解析" /&gt;&lt;h2 id="引言"&gt;&lt;a href="#%e5%bc%95%e8%a8%80" class="header-anchor"&gt;&lt;/a&gt;引言
&lt;/h2&gt;&lt;p&gt;在当今的数据驱动世界中，数据库的选择对任何企业和开发者来说都是一个至关重要的决策。MySQL 和 MariaDB，这两款数据库管理系统（DBMS）因其高性能、稳定性和广泛的应用场景而广受欢迎。尽管它们有着共同的起源，但随着时间的推移，两者在功能特性和发展路线上逐渐展现出差异。本文将深入探讨 MySQL 与 MariaDB 在表格定义和数据定义语言（DDL）方面的不同，并针对模式变更操作提供实用的指南。&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2024-09-09-shu-ju-ku-xuan-xing-bi-kan-mysql-yu-mariadb-gong-neng-dui-bi/001-edac8409.png"&gt;&lt;/p&gt;
&lt;h2 id="一表格功能差异详解"&gt;&lt;a href="#%e4%b8%80%e8%a1%a8%e6%a0%bc%e5%8a%9f%e8%83%bd%e5%b7%ae%e5%bc%82%e8%af%a6%e8%a7%a3" class="header-anchor"&gt;&lt;/a&gt;一、表格功能差异详解
&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;JSON 列类型&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;MySQL 的 JSON 支持&lt;/strong&gt;：MySQL 从 5.7 版本开始引入了原生的 JSON 数据类型，这使得存储和查询 JSON 文档变得更加高效。这一特性对于需要处理复杂数据结构的现代 Web 应用来说尤为重要。MySQL 的 JSON 类型支持多种 JSON 函数，如 JSON_SET、JSON_INSERT、JSON_REPLACE 等，这些函数允许用户直接在数据库层面进行 JSON 文档的修改，无需将整个文档加载到应用层。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;MariaDB 的 JSON 处理&lt;/strong&gt;：相比之下，MariaDB 采取了不同的策略。在 MariaDB 中，JSON 被视为 LONGTEXT 类型的一个别名，并通过 CHECK 约束来确保存储的数据是有效的 JSON 格式。这种方法虽然不如 MySQL 的原生 JSON 类型高效，但它提供了更高的灵活性。例如，用户可以在不更改表结构的情况下，将现有的 LONGTEXT 列转换为 JSON 类型。&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start="3"&gt;
&lt;li&gt;&lt;strong&gt;IP 地址和 UUID 列类型&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;MariaDB 的创新&lt;/strong&gt;：MariaDB 在数据类型方面进行了一些创新，其中包括提供了专门的列类型来存储 IPv4 和 IPv6 地址，以及 UUID 值。这些类型分别为 INET_ATON、INET6_ATON 和 UUID。使用这些专用类型可以简化网络相关数据的存储和查询，同时提高性能。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;MySQL 的传统处理&lt;/strong&gt;：在 MySQL 中，存储 IP 地址和 UUID 通常需要使用 VARCHAR 或 CHAR 类型，并依赖于应用层或数据库函数来进行格式验证和转换。这种方法虽然通用，但在处理大量网络数据时可能不够高效。&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start="5"&gt;
&lt;li&gt;&lt;strong&gt;数值列类型&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;MySQL 的简化&lt;/strong&gt;：从 MySQL 8.0 版本开始，数值列类型不再关注显示宽度。这意味着，例如，INT(11) 和 INT 的存储空间和范围是相同的。这一变化旨在简化数据类型的使用，避免用户对显示宽度的误解。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;MariaDB 的传统保留&lt;/strong&gt;：与此相反，MariaDB 仍然保留了数值列类型的显示宽度。这意味着在 MariaDB 中，INT(11) 和 INT 可能具有不同的含义，尤其是在进行数据迁移或模式兼容性测试时需要特别注意。&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start="7"&gt;
&lt;li&gt;&lt;strong&gt;时间列类型&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;处理 Y2K38 问题&lt;/strong&gt;：Y2K38 问题是指 32 位时间戳在 2038 年 1 月 19 日将达到其最大值，从而导致日期和时间处理上的问题。MariaDB 通过提供 TIMESTAMP 类型的新存储格式来解决这个问题，该格式支持更大的时间范围。而 MySQL 则依赖于用户自行处理这个问题，例如通过使用 BIGINT 类型来存储时间戳。&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start="9"&gt;
&lt;li&gt;&lt;strong&gt;空间列类型&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;空间数据支持&lt;/strong&gt;：MySQL 和 MariaDB 都提供了空间列类型，如 POINT、LINESTRING、POLYGON 等，用于存储地理空间数据。然而，在空间参考系统（SRID）的支持上，MySQL 提供了更广泛的选择，这使得它在处理复杂的空间数据时更具优势。&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start="11"&gt;
&lt;li&gt;&lt;strong&gt;字符集和校对规则&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;差异显著&lt;/strong&gt;：字符集和校对规则是数据库国际化支持的重要组成部分。在这两个方面，MySQL 和 MariaDB 存在显著差异。MariaDB 提供了一些 MySQL 不支持的字符集和校对规则，这使得它在处理特定语言和字符集时更加灵活。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="二压缩功能对比"&gt;&lt;a href="#%e4%ba%8c%e5%8e%8b%e7%bc%a9%e5%8a%9f%e8%83%bd%e5%af%b9%e6%af%94" class="header-anchor"&gt;&lt;/a&gt;二、压缩功能对比
&lt;/h2&gt;&lt;p&gt;数据库压缩是提高存储效率、降低存储成本的重要手段。MySQL 和 MariaDB 在压缩功能上都进行了创新和优化，但各有侧重点。&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2024-09-09-shu-ju-ku-xuan-xing-bi-kan-mysql-yu-mariadb-gong-neng-dui-bi/002-7e5a2535.png"&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;MySQL 的压缩技术&lt;/strong&gt;：MySQL 支持 InnoDB 存储引擎的传统压缩表，这种压缩可以显著减少磁盘空间的使用。在创建表时，可以通过指定&lt;code&gt;ROW_FORMAT=COMPRESSED&lt;/code&gt;来启用压缩。这种压缩技术在处理大量静态数据或归档数据时尤其有效。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;MariaDB 的列级压缩&lt;/strong&gt;：MariaDB 不仅支持 InnoDB 的压缩表，还引入了列级压缩功能。这意味着用户可以针对表中的特定列进行压缩，而不是整个行。这种精细化的压缩策略可以在节省存储空间的同时，减少对性能的影响。列级压缩特别适合于那些具有不同数据访问模式的大型表，可以针对不常访问或数据重复性高的列进行压缩。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="三默认值和生成列的差异"&gt;&lt;a href="#%e4%b8%89%e9%bb%98%e8%ae%a4%e5%80%bc%e5%92%8c%e7%94%9f%e6%88%90%e5%88%97%e7%9a%84%e5%b7%ae%e5%bc%82" class="header-anchor"&gt;&lt;/a&gt;三、默认值和生成列的差异
&lt;/h2&gt;&lt;p&gt;默认值和生成列是数据库设计中的重要概念，它们可以帮助确保数据的完整性和一致性。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;默认值的使用&lt;/strong&gt;：在 MySQL 和 MariaDB 中，都可以为列指定默认值。这些默认值可以是常量，也可以是复杂的表达式。然而，两者在支持的函数和表达式方面存在差异。例如，MySQL 可能支持某些特定的内置函数作为默认值，而 MariaDB 则可能不支持。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;生成列的特性&lt;/strong&gt;：生成列是 MariaDB 5.2 版本引入的特性，MySQL 从 5.7 版本开始也支持这一特性。生成列的值是由表中其他列的值计算得出的，这意味着它们是虚拟的，不需要实际存储在磁盘上。生成列在处理计算字段时非常有用，可以减少应用层的计算负担。不过，MySQL 和 MariaDB 在生成列的实现细节上有所不同，例如在支持的函数和表达式方面。&lt;/li&gt;
&lt;li&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="四外键和-check-约束的应用"&gt;&lt;a href="#%e5%9b%9b%e5%a4%96%e9%94%ae%e5%92%8c-check-%e7%ba%a6%e6%9d%9f%e7%9a%84%e5%ba%94%e7%94%a8" class="header-anchor"&gt;&lt;/a&gt;四、外键和 CHECK 约束的应用
&lt;/h2&gt;&lt;p&gt;外键和 CHECK 约束是保证数据库数据完整性的重要工具。它们在 MySQL 和 MariaDB 中的实现和应用有所不同。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;外键约束&lt;/strong&gt;：MySQL 和 MariaDB 都支持外键约束，用于强制执行表之间的关系。然而，两者在外键约束的语法、性能和错误处理上存在差异。例如，MariaDB 在某些情况下可能提供了更灵活的外键约束选项。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CHECK 约束&lt;/strong&gt;：CHECK 约束用于限制列的取值范围。在 MySQL 8.0 之前，CHECK 约束是语法糖，并不实际执行。而从 MySQL 8.0 开始，CHECK 约束得到了实际的支持。MariaDB 则一直支持 CHECK 约束，并且在某些情况下提供了更丰富的功能。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="五其他功能差异"&gt;&lt;a href="#%e4%ba%94%e5%85%b6%e4%bb%96%e5%8a%9f%e8%83%bd%e5%b7%ae%e5%bc%82" class="header-anchor"&gt;&lt;/a&gt;五、其他功能差异
&lt;/h2&gt;&lt;p&gt;除了上述差异外，MySQL 和 MariaDB 在许多其他功能上也存在差异，这些差异在某些特定场景下可能非常关键。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;系统版本化表&lt;/strong&gt;：MariaDB 提供了系统版本化表的功能，允许用户查询数据的历史版本。这对于需要跟踪数据变更历史的应用非常有用。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;应用时间周期表&lt;/strong&gt;：这是 MariaDB 的一个独特功能，允许用户定义数据的有效时间范围。这种表对于处理具有时间限制的数据非常有用，例如合同、订阅和价格信息。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;二时态表&lt;/strong&gt;：MariaDB 的二时态表功能允许用户查询数据的历史状态，这对于历史数据分析非常有用。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="六操作差异分析"&gt;&lt;a href="#%e5%85%ad%e6%93%8d%e4%bd%9c%e5%b7%ae%e5%bc%82%e5%88%86%e6%9e%90" class="header-anchor"&gt;&lt;/a&gt;六、操作差异分析
&lt;/h2&gt;&lt;p&gt;在实际操作中，MySQL 和 MariaDB 在执行 DDL 操作时存在一些差异，这些差异可能会影响到数据库的性能和可用性。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;ALTER TABLE 操作&lt;/strong&gt;：ALTER TABLE 是数据库维护中常见的操作，用于修改表结构。MySQL 和 MariaDB 在执行 ALTER TABLE 操作时，尤其是在线 DDL 变更方面，存在差异。例如，MariaDB 的 ALGORITHM 选项允许用户控制 DDL 操作的执行方式，而 MySQL 则提供了 INSTANT 算法来减少 DDL 操作对性能的影响。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;索引构建&lt;/strong&gt;：在创建索引时，MySQL 8.0.27+支持并行构建索引，这可以显著提高索引创建的速度。而 MariaDB 在索引构建方面的优化则有所不同，它可能提供了不同的性能特点和选项。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;DROP TABLE 操作&lt;/strong&gt;：在某些情况下，MySQL 在执行 DROP TABLE 操作时可能会遇到与 InnoDB 缓冲池大小相关的问题，导致系统停滞。MariaDB 则可能通过不同的机制来避免这些问题。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="七模式元数据差异"&gt;&lt;a href="#%e4%b8%83%e6%a8%a1%e5%bc%8f%e5%85%83%e6%95%b0%e6%8d%ae%e5%b7%ae%e5%bc%82" class="header-anchor"&gt;&lt;/a&gt;七、模式元数据差异
&lt;/h2&gt;&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2024-09-09-shu-ju-ku-xuan-xing-bi-kan-mysql-yu-mariadb-gong-neng-dui-bi/003-85a1cea8.png"&gt;&lt;/p&gt;
&lt;p&gt;数据库的模式元数据是数据库结构的信息，它对于数据库工具和监控系统的兼容性至关重要。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;信息模式表&lt;/strong&gt;：MySQL 和 MariaDB 在信息模式表（INFORMATION_SCHEMA）中提供的信息存在差异。这些差异可能会影响到依赖于这些信息的数据库工具和脚本。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SHOW 查询&lt;/strong&gt;：SHOW 查询是获取数据库状态和配置信息的常用方法。在 MySQL 和 MariaDB 中，SHOW 查询返回的结果可能会有所不同，这可能会影响到数据库监控和管理工具。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="八结论与建议"&gt;&lt;a href="#%e5%85%ab%e7%bb%93%e8%ae%ba%e4%b8%8e%e5%bb%ba%e8%ae%ae" class="header-anchor"&gt;&lt;/a&gt;八、结论与建议
&lt;/h2&gt;&lt;p&gt;通过上述分析，我们可以看到，尽管 MySQL 和 MariaDB 在许多方面都非常相似，但它们在表格定义、DDL 操作、功能特性和性能优化上都有各自的特点和优势。对于数据库管理员和开发者来说，了解这些差异对于选择合适的数据库系统至关重要。&lt;/p&gt;
&lt;p&gt;以下是一些基于本文分析的结论和建议：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;选择合适的数据库&lt;/strong&gt;：如果你的应用需要处理大量的 JSON 数据，或者你更倾向于使用原生的 JSON 类型，那么 MySQL 可能是更好的选择。相反，如果你的应用需要处理 IP 地址和 UUID 数据，并且希望使用列级压缩，MariaDB 可能更适合你的需求。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;考虑兼容性问题&lt;/strong&gt;：在进行数据库迁移时，兼容性是一个重要的考虑因素。如果你的应用依赖于特定的字符集或校对规则，或者使用了特定的 DDL 操作，那么在迁移前进行详细的兼容性测试是非常重要的。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;性能优化&lt;/strong&gt;：对于性能敏感的应用，了解不同数据库系统的性能特点是非常关键的。例如，MySQL 的并行索引构建和 MariaDB 的列级压缩都可以显著提高性能。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;持续学习和关注&lt;/strong&gt;：数据库技术是不断发展的，新的版本可能会引入新的特性和改进。因此，持续学习和关注 MySQL 和 MariaDB 的发展动态，可以帮助你更好地利用这些数据库系统。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="结语"&gt;&lt;a href="#%e7%bb%93%e8%af%ad" class="header-anchor"&gt;&lt;/a&gt;结语
&lt;/h2&gt;&lt;p&gt;数据库的选择和管理是一个复杂的过程，需要综合考虑多种因素。希望本文能够为你提供关于 MySQL 和 MariaDB 在表格定义和数据定义语言方面的差异的深入理解，并在你的数据库设计和维护工作中提供帮助。无论是选择 MySQL 还是 MariaDB，关键是要确保所选的数据库系统能够满足你的业务需求，同时提供良好的性能和可扩展性。&lt;/p&gt;</description></item><item><title>数据库分片：是什么？如何运作？</title><link>https://xiaobox.github.io/p/2024-08-30-shu-ju-ku-fen-pian-shi-shen-me-ru-he-yun-zuo/</link><pubDate>Fri, 30 Aug 2024 23:00:00 +0000</pubDate><guid>https://xiaobox.github.io/p/2024-08-30-shu-ju-ku-fen-pian-shi-shen-me-ru-he-yun-zuo/</guid><description>&lt;img src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2024-08-30-shu-ju-ku-fen-pian-shi-shen-me-ru-he-yun-zuo/cover.jpg" alt="Featured image of post 数据库分片：是什么？如何运作？" /&gt;&lt;h2 id="数据库分片概念与实现"&gt;&lt;a href="#%e6%95%b0%e6%8d%ae%e5%ba%93%e5%88%86%e7%89%87%e6%a6%82%e5%bf%b5%e4%b8%8e%e5%ae%9e%e7%8e%b0" class="header-anchor"&gt;&lt;/a&gt;数据库分片：概念与实现
&lt;/h2&gt;&lt;p&gt;在现代应用程序中，数据库的性能和可扩展性至关重要。随着用户数量的增加和数据量的激增，传统的单一数据库架构往往无法满足需求。这时，&lt;strong&gt;数据库分片&lt;/strong&gt;（Sharding）作为一种有效的解决方案，逐渐被广泛采用。本文将深入探讨数据库分片的概念、工作原理、实施策略以及常用工具，帮助读者理解如何通过分片来提升数据库性能和可扩展性。&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2024-08-30-shu-ju-ku-fen-pian-shi-shen-me-ru-he-yun-zuo/001-0c0bc74e.png"&gt;&lt;/p&gt;
&lt;h3 id="什么是数据库分片"&gt;&lt;a href="#%e4%bb%80%e4%b9%88%e6%98%af%e6%95%b0%e6%8d%ae%e5%ba%93%e5%88%86%e7%89%87" class="header-anchor"&gt;&lt;/a&gt;什么是数据库分片？
&lt;/h3&gt;&lt;p&gt;数据库分片是一种将数据分散存储在多个服务器上的策略，而不是将所有数据集中在一个庞大的数据库中。每个数据分区称为一个&lt;strong&gt;分片&lt;/strong&gt;（Shard）。通过将数据库拆分成多个分片，可以有效降低单个数据库的负载，从而提升整体性能。&lt;/p&gt;
&lt;p&gt;例如，在一个用户表中，如果所有用户数据都存储在一台服务器上，随着用户数量的增加，查询和写入操作的性能将受到影响。通过分片，可以将用户数据分布到多台服务器上，每台服务器只处理其对应的用户数据，从而提高响应速度和处理能力。&lt;/p&gt;
&lt;h3 id="数据库分片的必要性"&gt;&lt;a href="#%e6%95%b0%e6%8d%ae%e5%ba%93%e5%88%86%e7%89%87%e7%9a%84%e5%bf%85%e8%a6%81%e6%80%a7" class="header-anchor"&gt;&lt;/a&gt;数据库分片的必要性
&lt;/h3&gt;&lt;p&gt;随着业务的发展，许多公司发现单一数据库的扩展性有限。以下是一些常见的场景，说明何时需要考虑数据库分片：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;频繁的性能瓶颈&lt;/strong&gt;：当数据库频繁出现性能瓶颈，导致响应时间延长时，分片可以帮助分散负载。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;数据量激增&lt;/strong&gt;：当数据量迅速增长，单一数据库无法存储或处理时，分片可以将数据分散到多个服务器上。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;高并发访问&lt;/strong&gt;：在高并发访问场景下，分片能够有效分散请求，减少单一数据库的压力。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;地理分布&lt;/strong&gt;：在全球范围内运营的应用程序可能需要将数据存储在不同地区，以降低延迟。通过分片，可以将数据分布在离用户更近的服务器上。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="数据库分片的工作原理"&gt;&lt;a href="#%e6%95%b0%e6%8d%ae%e5%ba%93%e5%88%86%e7%89%87%e7%9a%84%e5%b7%a5%e4%bd%9c%e5%8e%9f%e7%90%86" class="header-anchor"&gt;&lt;/a&gt;数据库分片的工作原理
&lt;/h3&gt;&lt;p&gt;实现数据库分片需要考虑几个关键步骤：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;选择分片方案&lt;/strong&gt;：决定哪些数据需要分片，如何组织这些数据。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;组织目标基础设施&lt;/strong&gt;：确定将数据分片到多少台服务器上，以及每台服务器上存储多少数据。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;创建路由层&lt;/strong&gt;：设计应用程序如何知道新数据存储的位置，以及如何查询现有数据。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;规划和执行迁移&lt;/strong&gt;：如何在最小的停机时间内，从单一数据库迁移到多个数据库。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="分片方案与算法"&gt;&lt;a href="#%e5%88%86%e7%89%87%e6%96%b9%e6%a1%88%e4%b8%8e%e7%ae%97%e6%b3%95" class="header-anchor"&gt;&lt;/a&gt;分片方案与算法
&lt;/h3&gt;&lt;p&gt;选择合适的分片方案是成功实施分片的关键。以下是几种常见的分片策略：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;基于哈希的分片&lt;/strong&gt;：通过对某个列的值进行哈希处理，将哈希值相同的数据存储在同一服务器上。此方法能够有效地均匀分布数据，但可能会导致某些查询变得复杂。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;基于范围的分片&lt;/strong&gt;：选择一个列，创建范围，将数据分配到不同的分片中。适合于数值列的均匀分布，例如按用户 ID 范围分片。此方法的缺点是可能导致某些分片的数据量不均。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;基于目录的分片&lt;/strong&gt;：手动选择列，分配分片，并维护一个查找表，以便知道每行数据存储的位置。这种方法灵活性高，但维护成本较大。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;混合分片&lt;/strong&gt;：结合以上几种方法，根据实际需求灵活选择分片策略。例如，可以先使用哈希分片，然后在某些情况下使用范围分片。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;选择分片方案时，需要考虑业务模型和查询负载的分布。例如，对于 B2B SaaS 公司，按组织划分数据可能更为合理；而对于消费者公司，随机哈希分片可能更有效。&lt;/p&gt;
&lt;h3 id="服务器选择与配置"&gt;&lt;a href="#%e6%9c%8d%e5%8a%a1%e5%99%a8%e9%80%89%e6%8b%a9%e4%b8%8e%e9%85%8d%e7%bd%ae" class="header-anchor"&gt;&lt;/a&gt;服务器选择与配置
&lt;/h3&gt;&lt;p&gt;在确定分片方案后，接下来需要决定使用多少台服务器来存储数据。这个决策取决于预算、未来数据库负载的预测以及云服务提供商的选择。&lt;/p&gt;
&lt;p&gt;一种常见的方法是&lt;strong&gt;最大化灵活性&lt;/strong&gt;。可以从少量服务器开始，随着需求的增加逐步扩展。在添加新服务器时，需要重新平衡分片，以确保数据均匀分布。&lt;/p&gt;
&lt;h4 id="服务器配置"&gt;&lt;a href="#%e6%9c%8d%e5%8a%a1%e5%99%a8%e9%85%8d%e7%bd%ae" class="header-anchor"&gt;&lt;/a&gt;服务器配置
&lt;/h4&gt;&lt;p&gt;在配置服务器时，需要考虑以下几个方面：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;硬件配置&lt;/strong&gt;：选择合适的 CPU、内存和存储设备，以满足预期的负载需求。高性能的 SSD 存储可以显著提高数据库的读写速度。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;网络带宽&lt;/strong&gt;：确保服务器之间的网络连接速度足够快，以减少数据传输的延迟。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;备份与恢复&lt;/strong&gt;：设计合理的备份策略，以防止数据丢失。可以使用定期备份和增量备份相结合的方法。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;监控与报警&lt;/strong&gt;：配置监控工具，实时监测数据库的性能指标，如 CPU 使用率、内存使用情况和磁盘 I/O 等。一旦出现异常情况，及时报警并采取措施。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="路由分片查询"&gt;&lt;a href="#%e8%b7%af%e7%94%b1%e5%88%86%e7%89%87%e6%9f%a5%e8%af%a2" class="header-anchor"&gt;&lt;/a&gt;路由分片查询
&lt;/h3&gt;&lt;p&gt;当数据分布在多个数据库中时，如何让应用程序知道查询哪个数据库呢？这需要构建一个路由层。通常，这种逻辑是在应用程序层实现的。&lt;/p&gt;
&lt;p&gt;例如，可以通过以下伪代码实现路由逻辑：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;1&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;route_query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;2&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sharding_key&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;database_1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sharding_keys&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;3&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;connect_to_database_1&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;4&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sharding_key&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;database_2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sharding_keys&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;5&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;connect_to_database_2&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;6&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;7&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="ne"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Data not found in any database&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这种逻辑可以相对简单地存储在配置文件或数据库的查找表中。重要的是要确保应用程序能够根据数据的分片键找到相应的数据库。&lt;/p&gt;
&lt;h3 id="迁移到分片解决方案"&gt;&lt;a href="#%e8%bf%81%e7%a7%bb%e5%88%b0%e5%88%86%e7%89%87%e8%a7%a3%e5%86%b3%e6%96%b9%e6%a1%88" class="header-anchor"&gt;&lt;/a&gt;迁移到分片解决方案
&lt;/h3&gt;&lt;p&gt;在完成上述步骤并确保服务器正常运行后，接下来面临的挑战是如何在最小的停机时间内进行迁移。迁移到分片架构通常比迁移到单一新数据库提供商复杂得多，因为可能出现多种问题。&lt;/p&gt;
&lt;h4 id="迁移步骤"&gt;&lt;a href="#%e8%bf%81%e7%a7%bb%e6%ad%a5%e9%aa%a4" class="header-anchor"&gt;&lt;/a&gt;迁移步骤
&lt;/h4&gt;&lt;p&gt;Notion 的工程团队提出了一种有效的迁移框架，具体步骤包括：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;双写&lt;/strong&gt;：将新数据同时写入旧数据库和新数据库。这一过程可以在一定时间内并行进行，以确保新旧数据的一致性。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;回填&lt;/strong&gt;：在开始双写后，将旧数据迁移到新数据库。这一过程可能需要根据数据量的大小分批进行，以避免对系统性能的影响。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;验证&lt;/strong&gt;：确保新数据库中的数据完整性。可以通过对比新旧数据库的数据记录，确保没有遗漏或错误。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;切换&lt;/strong&gt;：实际切换到新数据库，可以逐步进行，例如先进行双读，再迁移所有读取操作。切换后，监控新数据库的性能，确保其正常运行。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="分片框架与工具"&gt;&lt;a href="#%e5%88%86%e7%89%87%e6%a1%86%e6%9e%b6%e4%b8%8e%e5%b7%a5%e5%85%b7" class="header-anchor"&gt;&lt;/a&gt;分片框架与工具
&lt;/h3&gt;&lt;p&gt;尽管许多团队会从头开始构建分片架构，但也有一些成熟的工具可以帮助实现分片。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Vitess&lt;/strong&gt;：最初为 YouTube 开发的 Vitess，现已成为一个开源项目，提供了 MySQL 的分片解决方案，并支持连接池、动态重新分片和监控工具等功能。Vitess 通过将数据分片到多个 MySQL 实例中，解决了大规模数据存储的问题。&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2024-08-30-shu-ju-ku-fen-pian-shi-shen-me-ru-he-yun-zuo/002-0047e39d.png"&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Citus&lt;/strong&gt;：为 Postgres 提供分片支持的开源扩展，能够在单节点或多个节点上运行，适合需要分片的 Postgres 用户。Citus 允许用户将 Postgres 数据库水平扩展，并提供了查询路由和分片管理的功能。&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2024-08-30-shu-ju-ku-fen-pian-shi-shen-me-ru-he-yun-zuo/003-e382f694.png"&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;无服务器数据库&lt;/strong&gt;：近年来，许多“无服务器”数据库逐渐兴起，例如 CockroachDB 和 Google Cloud Spanner，这些数据库本身内置了分片功能，简化了开发者的工作。这些数据库能够自动处理数据分片和负载均衡，极大地降低了运维成本。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="分片的挑战与解决方案"&gt;&lt;a href="#%e5%88%86%e7%89%87%e7%9a%84%e6%8c%91%e6%88%98%e4%b8%8e%e8%a7%a3%e5%86%b3%e6%96%b9%e6%a1%88" class="header-anchor"&gt;&lt;/a&gt;分片的挑战与解决方案
&lt;/h3&gt;&lt;p&gt;尽管数据库分片能够带来许多好处，但在实施过程中也面临一些挑战。以下是一些常见的挑战及其解决方案：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;数据不均衡&lt;/strong&gt;：在某些情况下，数据可能会在分片之间分布不均，导致某些分片的负载过重。解决方案是定期监测数据分布情况，并根据需要进行重新分片。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;复杂的查询&lt;/strong&gt;：当查询涉及多个分片时，可能会导致复杂的查询逻辑。解决方案是优化查询，尽量减少跨分片的操作，或者在应用层实现聚合逻辑。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;故障恢复&lt;/strong&gt;：在分片架构中，单个分片的故障可能影响整体系统的可用性。解决方案是实现高可用性架构，例如使用主从复制或分布式一致性协议。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;运维成本&lt;/strong&gt;：管理多个分片可能增加运维成本。解决方案是使用自动化工具来简化运维流程，例如使用监控工具和自动化备份工具。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="未来发展趋势"&gt;&lt;a href="#%e6%9c%aa%e6%9d%a5%e5%8f%91%e5%b1%95%e8%b6%8b%e5%8a%bf" class="header-anchor"&gt;&lt;/a&gt;未来发展趋势
&lt;/h3&gt;&lt;p&gt;随着技术的不断进步，数据库分片的概念和实现也在不断演化。以下是一些未来的发展趋势：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;智能分片&lt;/strong&gt;：未来的数据库系统可能会采用机器学习算法，根据实时数据访问模式自动调整分片策略，以实现更高的性能和可扩展性。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;多模型数据库&lt;/strong&gt;：随着对多样化数据存储需求的增加，未来的数据库可能会支持多种数据模型（如关系型、文档型、图形型等），并能够在同一系统中实现分片。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;云原生数据库&lt;/strong&gt;：随着云计算的普及，越来越多的数据库将采用云原生架构，自动处理分片、负载均衡和故障恢复等任务，降低开发者的运维负担。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;边缘计算与分片&lt;/strong&gt;：随着物联网和边缘计算的发展，未来的数据库可能会在边缘设备上实现分片，以降低延迟并提高数据处理效率。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="结论"&gt;&lt;a href="#%e7%bb%93%e8%ae%ba" class="header-anchor"&gt;&lt;/a&gt;结论
&lt;/h3&gt;&lt;p&gt;数据库分片是一种强大的技术，可以帮助企业在面对大规模数据和高并发请求时提升性能和可扩展性。通过合理选择分片方案、配置服务器、构建路由层以及规划迁移策略，企业可以有效地实现数据库的分片架构。随着技术的不断发展，分片工具和框架也在不断成熟，未来将为更多企业提供便利。在实施分片时，企业应根据自身业务需求和数据特点，灵活选择合适的分片策略，以确保系统的高效运行。&lt;/p&gt;
&lt;p&gt;通过本文的深入探讨，希望读者能够全面理解数据库分片的概念和实现方法，并在实际应用中有效应用这一技术，提升数据库的性能和可扩展性。无论是初创企业还是大型企业，合理的数据库分片策略都将为其业务发展提供强有力的支持。&lt;/p&gt;</description></item><item><title>如何让 Nacos 支持达梦数据库作为外置数据源</title><link>https://xiaobox.github.io/p/2023-11-23-ru-he-rang-nacos-zhi-chi-da-meng-shu-ju-ku-zuo-wei-wai-zhi-s/</link><pubDate>Thu, 23 Nov 2023 05:08:51 +0000</pubDate><guid>https://xiaobox.github.io/p/2023-11-23-ru-he-rang-nacos-zhi-chi-da-meng-shu-ju-ku-zuo-wei-wai-zhi-s/</guid><description>&lt;img src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2023-11-23-ru-he-rang-nacos-zhi-chi-da-meng-shu-ju-ku-zuo-wei-wai-zhi-s/cover.jpg" alt="Featured image of post 如何让 Nacos 支持达梦数据库作为外置数据源" /&gt;&lt;p&gt;Nacos 支持两种数据持久化方式，一种是利用内置的数据，一种是利用外置的数据源。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;内置数据库支持:&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;Nacos 默认内置了一些数据存储解决方案，如内嵌的 &lt;code&gt;Derby&lt;/code&gt; 数据库。&lt;/li&gt;
&lt;li&gt;这种内置方式主要用于轻量级或测试环境。&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start="3"&gt;
&lt;li&gt;外置数据库支持:&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;对于生产环境，Nacos 支持外置数据库以提供更高的可靠性和伸缩性。&lt;/li&gt;
&lt;li&gt;常见的外置数据库包括 MySQL 等，这些数据库通过标准的 JDBC 接口与 Nacos 集成。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;然而 达梦数据库 Nacos 原生是不支持的，或者说不能通过简单配置使 Nacos + 达梦数据库这样的组合生效。&lt;/p&gt;
&lt;h3 id="达梦数据库-是什么"&gt;&lt;a href="#%e8%be%be%e6%a2%a6%e6%95%b0%e6%8d%ae%e5%ba%93-%e6%98%af%e4%bb%80%e4%b9%88" class="header-anchor"&gt;&lt;/a&gt;达梦数据库 是什么
&lt;/h3&gt;&lt;p&gt;达梦数据库（DMDB），是一款由中国国内团队自主研发的关系型数据库管理系统（RDBMS）。它旨在提供高性能、高可靠性和高安全性的数据库解决方案，特别是对于在政府、金融、电信等行业的应用。&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2023-11-23-ru-he-rang-nacos-zhi-chi-da-meng-shu-ju-ku-zuo-wei-wai-zhi-s/001-e47e4976.png"&gt;&lt;/p&gt;
&lt;p&gt;介绍一下达梦数据库的几个关键方面：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;自主研发：达梦数据库是由中国国内团队自主研发的，这意味着它在设计和开发过程中更加注重符合国内市场的需求和标准。它的出现也代表了中国在关键技术领域自主创新的重要成果。&lt;/li&gt;
&lt;li&gt;高性能：达梦数据库采用了先进的数据库技术，比如高效的存储引擎、智能的查询优化器等，以提供高速的数据处理和查询性能。这使得它适合处理大规模数据和高并发访问，满足企业级应用的需求。&lt;/li&gt;
&lt;li&gt;高可靠性：在设计上，达梦数据库强调数据的可靠性和持久性。它提供了严格的事务控制、灾难恢复和备份机制，确保在各种环境下数据的完整性和安全性。&lt;/li&gt;
&lt;li&gt;高安全性：达梦数据库特别注重数据安全。它提供了包括数据加密、访问控制、审计日志等多重安全机制，帮助用户防范数据泄露和非法访问。&lt;/li&gt;
&lt;li&gt;兼容性和易用性：为了更好地适应现有的企业环境，达梦数据库支持广泛的操作系统和平台，并且与主流的编程语言和开发工具具有良好的兼容性。此外，它还提供了易于使用的管理工具和丰富的文档支持。&lt;/li&gt;
&lt;li&gt;应用场景：达梦数据库广泛应用于政府、金融、电信、能源、教育等多个行业，特别是在那些对数据安全性和可靠性有高要求的领域。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="为什么使用达梦数据库"&gt;&lt;a href="#%e4%b8%ba%e4%bb%80%e4%b9%88%e4%bd%bf%e7%94%a8%e8%be%be%e6%a2%a6%e6%95%b0%e6%8d%ae%e5%ba%93" class="header-anchor"&gt;&lt;/a&gt;为什么使用达梦数据库
&lt;/h3&gt;&lt;p&gt;在数据库的选型方面，通常我们会使用业内广泛使用的产品，如开源的 MySQL, 甚至收费的如 Oracle、SQL Server，直到 “信创” 的到来，打破了这些传统产品在数据库市场的垄断地位。&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;“&lt;/p&gt;
&lt;p&gt;“信创”这个词最早来源于“信创工委会”。该组织的全称是：信息技术应用创新工作委员会，是在 2016 年，由 24 家专业从事软硬件关键技术研究及应用的国内单位，共同发起成立的一个非营利性社会组织。&lt;/p&gt;
&lt;p&gt;”&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;后来，除了数据安全、网络安全，信创是把之前的一些软硬件等行业放到了一起，重新起了一个名字叫：信息技术应用创新产业，简称“信创”。&lt;/p&gt;
&lt;p&gt;也因此，一般来说，信创包括基础硬件、基础软件、应用软件、信息安全四大板块。其中，基础硬件主要包括：芯片、服务器/PC、存储等；基础软件包括：数据库、操作系统、中间件等；应用软件包括：办公软件、ERP 和其它软件等；信息安全包括硬件安全、软件安全、安全服务等各类产品。&lt;/p&gt;
&lt;p&gt;针对安全可控，我们国家提出的是“2+8”体系。“2”指党、政；“8”指关于国计民生的八大行业：金融、电力、电信、石油、交通、教育、医疗、航空航天。&lt;/p&gt;
&lt;p&gt;发展信创，先在党政等封闭市场进行应用信创产品，打磨产品和生态；接着在产品好用和生态相对成熟之后，进入金融、电力、电信、石油、交通、教育、医疗、航空航天重点行业市场；最后才是将信创产品全面应用到消费市场。&lt;/p&gt;
&lt;p&gt;而数据库就是我们常说的 “信创” 四件套（芯片、操作系统 、数据库、中间件）之一。达梦数据库就是这样一个符合 “国产化” 要求的自主研发的数据库。所以，由于国家信息安全的要求，我们的客户需要符合这些要求，也必然要进行软件的替换。&lt;/p&gt;
&lt;h2 id="实现方案"&gt;&lt;a href="#%e5%ae%9e%e7%8e%b0%e6%96%b9%e6%a1%88" class="header-anchor"&gt;&lt;/a&gt;实现方案
&lt;/h2&gt;&lt;p&gt;首先看一下 Nacos 原生支持的外置数据库有哪些，是否支持达梦？&lt;/p&gt;
&lt;p&gt;根据以下 Nacos 官方文档，无论是单机还是集群模式，貌似只支持 &lt;code&gt;MySQL&lt;/code&gt; 作为外置数据源&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://nacos.io/zh-cn/docs/v2/guide/admin/deployment.html" target="_blank" rel="noopener"
 &gt;https://nacos.io/zh-cn/docs/v2/guide/admin/deployment.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://nacos.io/zh-cn/docs/v2/guide/admin/cluster-mode-quick-start.html" target="_blank" rel="noopener"
 &gt;https://nacos.io/zh-cn/docs/v2/guide/admin/cluster-mode-quick-start.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2023-11-23-ru-he-rang-nacos-zhi-chi-da-meng-shu-ju-ku-zuo-wei-wai-zhi-s/002-d112b950.png"&gt;&lt;/p&gt;
&lt;p&gt;只支持 MySQL 吗？不是说还支持其他像 Oracle 之类的数据库吗？&lt;/p&gt;
&lt;p&gt;在调研的过程中，发现 github 上 Nacos 的源码有这样一个功能分支 &lt;code&gt;feature_multiple_datasource_support&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2023-11-23-ru-he-rang-nacos-zhi-chi-da-meng-shu-ju-ku-zuo-wei-wai-zhi-s/003-32e47164.png"&gt;&lt;/p&gt;
&lt;p&gt;很明显，它就是用来支持多数据源的，通过源码我们可以看到它支持的多种数据源都有哪些：&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2023-11-23-ru-he-rang-nacos-zhi-chi-da-meng-shu-ju-ku-zuo-wei-wai-zhi-s/004-2ff5f297.png"&gt;&lt;/p&gt;
&lt;p&gt;这个分支能够支持的外部数据源分别是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;oracle&lt;/li&gt;
&lt;li&gt;mysql&lt;/li&gt;
&lt;li&gt;postgresql&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;我分析了 Nacos 1.0 及 2.0 主要版本，发现 多数据源的这个功能并没有被合并到主要的开发及 &lt;code&gt;release&lt;/code&gt; 分支上。也就是说 &lt;code&gt;Nacos&lt;/code&gt; 现有的主要版本的 &lt;code&gt;release&lt;/code&gt; 并没有多数据源的这个功能，外置数据源只兼容 MySQL。&lt;/p&gt;
&lt;p&gt;根据前面的分析我们知道即使是 &lt;code&gt;feature_multiple_datasource_support&lt;/code&gt; 分支也只支持三个数据源，如果想用非 MySQL 的数据源，比如用 Oracle 就需要自己修改和编译源代码。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;1&lt;/span&gt;&lt;span class="cl"&gt;mvn -Prelease-nacos -Dmaven.test.skip=true -Dcheckstyle.skip=true clean install -U
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;具体修改的部分也主要是配置文件 &lt;code&gt;application.properties&lt;/code&gt; 没有其他地方了。&lt;/p&gt;
&lt;p&gt;Nacos 是支持 Oracle 和 PostgreSQL 的，只不过需要手动修改配置和编译。虽然这种方法可行，但由于功能分支长时间未更新，最新版本的代码未合并过来，可能会造成一些安全和功能上的问题。更重要的是，通过上述的分析我们知道，Nacos 在原生的模式下，确实是不支持达梦数据库的。&lt;/p&gt;
&lt;h3 id="方案一-修改源代码方式"&gt;&lt;a href="#%e6%96%b9%e6%a1%88%e4%b8%80-%e4%bf%ae%e6%94%b9%e6%ba%90%e4%bb%a3%e7%a0%81%e6%96%b9%e5%bc%8f" class="header-anchor"&gt;&lt;/a&gt;方案一 修改源代码方式
&lt;/h3&gt;&lt;p&gt;根据前文我们知道，Nacos 原生是不支持达梦数据库的，所以就要想办法让它 “支持”，直觉上因为是开源软件，我们还是会从源码入手。&lt;/p&gt;
&lt;p&gt;既然可以修改源代码，我们就不需要从 &lt;code&gt;feature_multiple_datasource_support&lt;/code&gt; 分支开始了，可以在流行的 1.x 、2.x 或最新版本代码的基本上修改。&lt;/p&gt;
&lt;p&gt;主要涉及到以下内容的修改：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;com/alibaba/nacos/persistence/datasource/ExternalDataSourceProperties.java&lt;/li&gt;
&lt;li&gt;console/src/main/resources/application.properties&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;代码具体的修改方式和内容可以是多样的，下面举几个例子，供参考：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://developer.aliyun.com/article/976299" target="_blank" rel="noopener"
 &gt;https://developer.aliyun.com/article/976299&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.cnblogs.com/hi-gdl/p/nacos-02.html" target="_blank" rel="noopener"
 &gt;https://www.cnblogs.com/hi-gdl/p/nacos-02.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://cloud.tencent.com/developer/article/1912024" target="_blank" rel="noopener"
 &gt;https://cloud.tencent.com/developer/article/1912024&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://codeantenna.com/a/SJdgkqAbZt" target="_blank" rel="noopener"
 &gt;https://codeantenna.com/a/SJdgkqAbZt&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;核心思路是：由于达梦数据库良好的支持了 JDBC 驱动，所以我们只需要把 jdbcDriver 进行更换就可以了。然后同样手动进行编译，使用自己编译好的构建物进行部署。&lt;/p&gt;
&lt;p&gt;这里涉及到的 Nacos 数据库初始化脚本可以参考：https://gitee.com/tangjingshan/nacos/blob/tjs-study-fetch-master/distribution/conf/dm-schema.sql&lt;/p&gt;
&lt;p&gt;总结：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;源代码修改方案并不复杂，相对比较简单，但需要做好相关功能的完整测试。&lt;/li&gt;
&lt;li&gt;使用这种方式不但可以支持达梦数据库也可以在同样原理下支持其他国产数据库，如 &lt;code&gt;人大金仓&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;这种方式的问题是由于自行修改了源代码，在进行版本升级时会比较麻烦，每一次升级都要手动合并最新的代码再进行编译，未来甚至有可能出现 Nacos 官方源码进行大规模重构，自行编译的代码无法合并的情况。虽然也有解决办法，但是个麻烦点。&lt;/li&gt;
&lt;li&gt;数据迁移，这个后面我具体再详细说明&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="方案二-多数据源插件"&gt;&lt;a href="#%e6%96%b9%e6%a1%88%e4%ba%8c-%e5%a4%9a%e6%95%b0%e6%8d%ae%e6%ba%90%e6%8f%92%e4%bb%b6" class="header-anchor"&gt;&lt;/a&gt;方案二 多数据源插件
&lt;/h3&gt;&lt;p&gt;Nacos 从 2.2.0 版本开始，可通过 SPI 机制注入多数据源实现 插件，它的原理是：&lt;/p&gt;
&lt;p&gt;在原来的 Config 模块中，所有的 SQL 操作的执行是通过直接使用 JdbcTemplate 执行固定 SQL 语句的形式，使得 SQL 语句与业务逻辑高度耦合，并且只支持 Derby 与 MySQL 两种数据源，原有 Config 模块架构如下。&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2023-11-23-ru-he-rang-nacos-zhi-chi-da-meng-shu-ju-ku-zuo-wei-wai-zhi-s/005-83c9e702.png"&gt;&lt;/p&gt;
&lt;p&gt;现在的多数据源插件通过 SPI 机制，将 SQL 操作按照数据表进行抽象出多个 Mapper 接口，Mapper 接口的实现类需要按照不同的数据源编写对应的 SQL 方言实现；现在插件默认提供 Derby 以及 MySQL 的 Mapper 实现，可直接使用；而其他的数据源则需要用户使用数据源插件进行加载，其改造后架构图如下。&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2023-11-23-ru-he-rang-nacos-zhi-chi-da-meng-shu-ju-ku-zuo-wei-wai-zhi-s/006-5b6348c2.png"&gt;&lt;/p&gt;
&lt;p&gt;我们这里详细描述一下原理&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2023-11-23-ru-he-rang-nacos-zhi-chi-da-meng-shu-ju-ku-zuo-wei-wai-zhi-s/007-66cf215c.png"&gt;&lt;/p&gt;
&lt;p&gt;上图是 Nacos 的源码包中 plugin 模块，可以看到在 datasource 包下有不同的数据库实现类。这里其实就是抽象了 Nacos 操作的各个表的 Mapper 接口实现，你可以看到具体的 SQL 语句都在里面。&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2023-11-23-ru-he-rang-nacos-zhi-chi-da-meng-shu-ju-ku-zuo-wei-wai-zhi-s/008-696a8914.png"&gt;&lt;/p&gt;
&lt;p&gt;既然有 MySQL、derby 的实现，也可以有我们自己的实现，具体来说就是达梦数据库的实现，我们只需要把这几个类重写就可以了，当然具体重写的内容中的 SQL 要根据达梦数据库的方言情况，修改或者不修改。&lt;/p&gt;
&lt;p&gt;那么是否可以直接在源码的基础上添加 DM 的实现类进行开发呢？&lt;/p&gt;
&lt;p&gt;理论上当然可以，但既然叫插件就有插件的形式。在 Nacos 源码的基础上开发耦合太重了，这不是插件化的表现形式。&lt;/p&gt;
&lt;p&gt;我们要把与多数据源相关的自定义代码专门写一个包，然后在 Nacos 的代码中依赖，这样就解耦了，也与上文 Nacos 插件架构图中的描述相符。&lt;/p&gt;
&lt;p&gt;插件化是如何实现的呢，或者说动态替换实现类是如何实现的？&lt;/p&gt;
&lt;p&gt;这就要利用到 Java 的 SPI 知识了，由于是基础理论这里就不展开讲了。Nacos 在源码中已然利用 SPI 进行数据源 Mapper 的加载了，可以参考下图：&lt;/p&gt;
&lt;p&gt;源码位置：com.alibaba.nacos.plugin.datasource.MapperManager#loadInitial&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2023-11-23-ru-he-rang-nacos-zhi-chi-da-meng-shu-ju-ku-zuo-wei-wai-zhi-s/009-cfaeb6e1.png"&gt;&lt;/p&gt;
&lt;p&gt;我们可以看到，源码是利用 ServiceLoader 加载插件包，而这些实现类也写在 &lt;code&gt;plugin/datasource/src/main/resources/META-INF/services/com.alibaba.nacos.plugin.datasource.mapper.Mapper&lt;/code&gt; 这个文件里&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2023-11-23-ru-he-rang-nacos-zhi-chi-da-meng-shu-ju-ku-zuo-wei-wai-zhi-s/010-d4851c25.png"&gt;&lt;/p&gt;
&lt;p&gt;那么如果我们也利用 SPI 配置好 DM 的实现类，然后根据数据源参数找到相应的实现类是不是就可以了？&lt;/p&gt;
&lt;p&gt;是的，所以源码中也正是这么做的&lt;/p&gt;
&lt;p&gt;源码位置：com.alibaba.nacos.plugin.datasource.MapperManager#findMapper&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2023-11-23-ru-he-rang-nacos-zhi-chi-da-meng-shu-ju-ku-zuo-wei-wai-zhi-s/011-0eb5b472.png"&gt;&lt;/p&gt;
&lt;p&gt;这里我们讲一下具体的实现方法：&lt;/p&gt;
&lt;p&gt;1 初始化达梦数据库，具体脚本可以参考 ：https://github.com/nacos-group/nacos-plugin/blob/develop/nacos-datasource-plugin-ext/nacos-dm-datasource-plugin-ext/src/main/resources/schema/nacos-dm.sql&lt;/p&gt;
&lt;p&gt;2 编写插件包，利用 SPI 的原理，自定义实现各个表的 Mapper 实现类，这里其实 Nacos 的社区 nacos-group 中已经有现成的实现了，可以参考他们的项目和代码，实际上的代码都比较简单，甚至不需要做什么改动，因为基本的 SQL 达梦都是兼容的。&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2023-11-23-ru-he-rang-nacos-zhi-chi-da-meng-shu-ju-ku-zuo-wei-wai-zhi-s/012-b5df1816.png"&gt;&lt;/p&gt;
&lt;p&gt;3 插件引入，有两种方式&lt;/p&gt;
&lt;p&gt;第一种：&lt;/p&gt;
&lt;p&gt;直接用 nacos-group 的现成的实现包，然后用 maven 进行依赖就可以了，例如：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-html" data-lang="html"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;1&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;dependency&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;2&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;groupId&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;com.alibaba.nacos&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;groupId&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;3&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;artifactId&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;nacos-dm-datasource-plugin-ext&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;artifactId&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;4&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;version&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;1.0.0-SNAPSHOT&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;version&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;5&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;dependency&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;第二种：&lt;/p&gt;
&lt;p&gt;将插件源码打包为 jar 包，将该文件的路径配置到 &lt;code&gt;startup.sh&lt;/code&gt; 文件中，使用 Nacos 的 &lt;code&gt;loader.path&lt;/code&gt;机制指定该插件的路径，可修改 &lt;code&gt;startup.sh&lt;/code&gt; 中的 &lt;code&gt;loader.path&lt;/code&gt; 参数的位置进行指定。&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2023-11-23-ru-he-rang-nacos-zhi-chi-da-meng-shu-ju-ku-zuo-wei-wai-zhi-s/013-92ff4969.png"&gt;&lt;/p&gt;
&lt;p&gt;启动脚本会指定插件包位置为：&lt;code&gt;-Dloader.path=${BASE_DIR}/plugins&lt;/code&gt; loader.path 机制为打包插件 spring-boot-maven-plugin 提供的，该机制下实际启动类会变成&lt;code&gt;org.springframework.boot.loader.PropertiesLauncher#main&lt;/code&gt;，且类会由&lt;code&gt;org.springframework.boot.loader.LaunchedURLClassLoader&lt;/code&gt;这个类加载器加载&lt;/p&gt;
&lt;p&gt;4 修改数据库配置文件，在 application.properties 文件中声明 dameng 的配置信息：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;1&lt;/span&gt;&lt;span class="cl"&gt;spring.datasource.platform=dm
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;2&lt;/span&gt;&lt;span class="cl"&gt; db.url.0=jdbc:dm://127.0.0.1:5236/DMSERVER?schema=NACOS&amp;amp;compatibleMode=mysql&amp;amp;ignoreCase=true&amp;amp;ENCODING=utf-8
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;3&lt;/span&gt;&lt;span class="cl"&gt; db.user.0=SYSDBA
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;4&lt;/span&gt;&lt;span class="cl"&gt; db.password.0=SYSDBA
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;5&lt;/span&gt;&lt;span class="cl"&gt; db.pool.config.driverClassName=dm.jdbc.driver.DmDriver 
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;5 如果用 maven 依赖的方式引入了插件包，就需要源码重新编译，如果使用 loader.path 指定路径的方式就可以重启进行测试了&lt;/p&gt;
&lt;h3 id="数据迁移"&gt;&lt;a href="#%e6%95%b0%e6%8d%ae%e8%bf%81%e7%a7%bb" class="header-anchor"&gt;&lt;/a&gt;数据迁移
&lt;/h3&gt;&lt;p&gt;无论使用哪种解决方案很大可能性都需要进行数据迁移，即将旧的非 达梦数据库的数据迁移到达梦数据库。&lt;/p&gt;
&lt;p&gt;我们要把 &lt;code&gt;Nacos&lt;/code&gt; 的数据或者 &lt;code&gt;SQL&lt;/code&gt; 语句迁移到达梦数据库。借助 &lt;code&gt;DM 数据迁移工具&lt;/code&gt; ，完成 &lt;code&gt;Nacos&lt;/code&gt; 配置数据表迁移到达梦数据库。&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2023-11-23-ru-he-rang-nacos-zhi-chi-da-meng-shu-ju-ku-zuo-wei-wai-zhi-s/014-4780c6a9.png"&gt;&lt;/p&gt;</description></item><item><title>一条SQL 最多能查询出来多少条记录？</title><link>https://xiaobox.github.io/p/2023-07-14-yi-tiao-sql-zui-duo-neng-cha-xun-chu-lai-duo-shao-tiao-ji-lu/</link><pubDate>Fri, 14 Jul 2023 04:16:40 +0000</pubDate><guid>https://xiaobox.github.io/p/2023-07-14-yi-tiao-sql-zui-duo-neng-cha-xun-chu-lai-duo-shao-tiao-ji-lu/</guid><description>&lt;img src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2023-07-14-yi-tiao-sql-zui-duo-neng-cha-xun-chu-lai-duo-shao-tiao-ji-lu/cover.jpg" alt="Featured image of post 一条SQL 最多能查询出来多少条记录？" /&gt;&lt;h2 id="问题"&gt;&lt;a href="#%e9%97%ae%e9%a2%98" class="header-anchor"&gt;&lt;/a&gt;问题
&lt;/h2&gt;&lt;p&gt;一条这样的 SQL 语句能查询出多少条记录？&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;1&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;select&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;user&lt;/span&gt;&lt;span class="w"&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;表中有 100 条记录的时候能全部查询出来返回给客户端吗？&lt;/p&gt;
&lt;p&gt;如果记录数是 1w 呢？10w 呢？100w 、1000w 呢？&lt;/p&gt;
&lt;p&gt;虽然在实际业务操作中我们不会这么干，尤其对于数据量大的表不会这样干，但这是个值得想一想的问题。&lt;/p&gt;
&lt;h2 id="寻找答案"&gt;&lt;a href="#%e5%af%bb%e6%89%be%e7%ad%94%e6%a1%88" class="header-anchor"&gt;&lt;/a&gt;寻找答案
&lt;/h2&gt;&lt;p&gt;前提：以下所涉及资料全部基于 MySQL 8&lt;/p&gt;
&lt;h3 id="max_allowed_packet"&gt;&lt;a href="#max_allowed_packet" class="header-anchor"&gt;&lt;/a&gt;max_allowed_packet
&lt;/h3&gt;&lt;p&gt;在查询资料的过程中发现了这个参数 &lt;code&gt;max_allowed_packet&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2023-07-14-yi-tiao-sql-zui-duo-neng-cha-xun-chu-lai-duo-shao-tiao-ji-lu/001-5dd1c370.png"&gt;&lt;/p&gt;
&lt;p&gt;上图参考了 MySQL 的官方文档，根据文档我们知道：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;MySQL 客户端 &lt;code&gt;max_allowed_packet&lt;/code&gt; 值的默认大小为 16M（不同的客户端可能有不同的默认值，但最大不能超过 1G）&lt;/li&gt;
&lt;li&gt;MySQL 服务端 &lt;code&gt;max_allowed_packet&lt;/code&gt; 值的默认大小为 64M&lt;/li&gt;
&lt;li&gt;&lt;code&gt;max_allowed_packet&lt;/code&gt; 值最大可以设置为 1G（1024 的倍数）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;然而 根据上图的文档中所述&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;“&lt;/p&gt;
&lt;p&gt;The maximum size of one packet or any generated/intermediate string,or any parameter sent by the mysql_smt_send_long_data() C API function&lt;/p&gt;
&lt;p&gt;”&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;one packet&lt;/li&gt;
&lt;li&gt;generated/intermediate string&lt;/li&gt;
&lt;li&gt;any parameter sent by the mysql_smt_send_long_data() C API function&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这三个东东具体都是什么呢？&lt;code&gt;packet&lt;/code&gt; 到底是结果集大小，还是网络包大小还是什么？于是 google 了一下，搜索排名第一的是这个：&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2023-07-14-yi-tiao-sql-zui-duo-neng-cha-xun-chu-lai-duo-shao-tiao-ji-lu/002-7826aeed.png"&gt;&lt;/p&gt;
&lt;p&gt;根据 “Packet Too Large” 的说明， 通信包 (communication packet) 是&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;一个被发送到 MySQL 服务器的单个 SQL 语句&lt;/li&gt;
&lt;li&gt;或者是一个被发送到客户端的单行记录&lt;/li&gt;
&lt;li&gt;或者是一个从主服务器 (replication source server) 被发送到从属服务器 (replica) 的二进制日志事件。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;1、3 点好理解，这也同时解释了，如果你发送的一条 SQL 语句特别大可能会执行不成功的原因，尤其是&lt;code&gt;insert&lt;/code&gt; &lt;code&gt;update&lt;/code&gt; 这种，单个 SQL 语句不是没有上限的，不过这种情况一般不是因为 SQL 语句写的太长，主要是由于某个字段的值过大，比如有 BLOB 字段。&lt;/p&gt;
&lt;p&gt;那么第 2 点呢，单行记录，默认值是 64M，会不会太大了啊，一行记录有可能这么大的吗？有必要设置这么大吗？单行最大存储空间限制又是多少呢？&lt;/p&gt;
&lt;h3 id="单行最大存储空间"&gt;&lt;a href="#%e5%8d%95%e8%a1%8c%e6%9c%80%e5%a4%a7%e5%ad%98%e5%82%a8%e7%a9%ba%e9%97%b4" class="header-anchor"&gt;&lt;/a&gt;单行最大存储空间
&lt;/h3&gt;&lt;p&gt;MySQL 单行最大宽度是 65535 个字节，也就是 64KB 。无论是 InnoDB 引擎还是 MyISAM 引擎。&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2023-07-14-yi-tiao-sql-zui-duo-neng-cha-xun-chu-lai-duo-shao-tiao-ji-lu/003-fc3ce2e6.png"&gt;&lt;/p&gt;
&lt;p&gt;通过上图可以看到 超过 65535 不行，不过请注意其中的错误提示：“Row size too large. The maximum row size for the used table type, not counting BLOBs, is 65535” ，如果字段是变长类型的如 BLOB 和 TEXT 就不包括了，那么我们试一下用和上图一样的字段长度，只把最后一个字段的类型改成 BLOB 和 TEXT&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;1&lt;/span&gt;&lt;span class="cl"&gt;mysql&amp;gt; CREATE TABLE t (a VARCHAR(10000), b VARCHAR(10000),
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;2&lt;/span&gt;&lt;span class="cl"&gt; c VARCHAR(10000), d VARCHAR(10000), e VARCHAR(10000),
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;3&lt;/span&gt;&lt;span class="cl"&gt; f VARCHAR(10000), g TEXT(6000)) ENGINE=InnoDB CHARACTER SET latin1;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;4&lt;/span&gt;&lt;span class="cl"&gt;Query OK, 0 rows affected (0.02 sec)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;可见无论 是改成 BLOB 还是 TEXT 都可以成功。但这里请注意，字符集是 &lt;code&gt;latin1&lt;/code&gt; 可以成功，如果换成 &lt;code&gt;utf8mb4&lt;/code&gt; 或者 &lt;code&gt;utf8mb3&lt;/code&gt; 就不行了，会报错，仍然是 ：“Row size too large. The maximum row size for the used table type, not counting BLOBs, is 65535.” 为什么呢？&lt;/p&gt;
&lt;p&gt;因为虽然不包括 TEXT 和 BLOB, 但总长度还是超了！&lt;/p&gt;
&lt;p&gt;我们先看一下这个熟悉的 VARCHAR(255) ， 你有没有想过为什么用 255，不用 256？&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;“&lt;/p&gt;
&lt;p&gt;在 4.0 版本以下，varchar(255) 指的是 255 个字节，使用 1 个字节存储长度即可。当大于等于 256 时，要使用 2 个字节存储长度。所以定义 varchar(255) 比 varchar(256) 更好。&lt;/p&gt;
&lt;p&gt;但是在 5.0 版本以上，varchar(255) 指的是 255 个字符，每个字符可能占用多个字节，例如使用 UTF8 编码时每个汉字占用 3 字节，使用 GBK 编码时每个汉字占 2 字节。&lt;/p&gt;
&lt;p&gt;”&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;例子中我们用的是 MySQL8 ，由于字符集是 utf8mb3 ，存储一个字要用三个字节， 长度为 255 的话（列宽），总长度要 765 字节 ，再加上用 2 个字节存储长度，那么这个列的总长度就是 767 字节。所以用 latin1 可以成功，是因为一个字符对应一个字节，而 utf8mb3 或 utf8mb4 一个字符对应三个或四个字节，VARCHAR(10000) 就可能等于要占用 30000 多 40000 多字节，比原来大了 3、4 倍，肯定放不下了。&lt;/p&gt;
&lt;p&gt;另外，还有一个要求，列的宽度不要超过 MySQL 页大小 （默认 16K）的一半，要比一半小一点儿。例如，对于默认的 16KB &lt;code&gt;InnoDB&lt;/code&gt; 页面大小，最大行大小略小于 8KB。&lt;/p&gt;
&lt;p&gt;下面这个例子就是超过了一半，所以报错，当然解决办法也在提示中给出了。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 1&lt;/span&gt;&lt;span class="cl"&gt;mysql&amp;gt; CREATE TABLE t4 (
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 2&lt;/span&gt;&lt;span class="cl"&gt; c1 CHAR(255),c2 CHAR(255),c3 CHAR(255),
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 3&lt;/span&gt;&lt;span class="cl"&gt; c4 CHAR(255),c5 CHAR(255),c6 CHAR(255),
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 4&lt;/span&gt;&lt;span class="cl"&gt; c7 CHAR(255),c8 CHAR(255),c9 CHAR(255),
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 5&lt;/span&gt;&lt;span class="cl"&gt; c10 CHAR(255),c11 CHAR(255),c12 CHAR(255),
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 6&lt;/span&gt;&lt;span class="cl"&gt; c13 CHAR(255),c14 CHAR(255),c15 CHAR(255),
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 7&lt;/span&gt;&lt;span class="cl"&gt; c16 CHAR(255),c17 CHAR(255),c18 CHAR(255),
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 8&lt;/span&gt;&lt;span class="cl"&gt; c19 CHAR(255),c20 CHAR(255),c21 CHAR(255),
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 9&lt;/span&gt;&lt;span class="cl"&gt; c22 CHAR(255),c23 CHAR(255),c24 CHAR(255),
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;10&lt;/span&gt;&lt;span class="cl"&gt; c25 CHAR(255),c26 CHAR(255),c27 CHAR(255),
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;11&lt;/span&gt;&lt;span class="cl"&gt; c28 CHAR(255),c29 CHAR(255),c30 CHAR(255),
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;12&lt;/span&gt;&lt;span class="cl"&gt; c31 CHAR(255),c32 CHAR(255),c33 CHAR(255)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;13&lt;/span&gt;&lt;span class="cl"&gt; ) ENGINE=InnoDB ROW_FORMAT=DYNAMIC DEFAULT CHARSET latin1;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;14&lt;/span&gt;&lt;span class="cl"&gt;ERROR 1118 (42000): Row size too large (&amp;gt; 8126). Changing some columns to TEXT or BLOB may help.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;15&lt;/span&gt;&lt;span class="cl"&gt;In current row format, BLOB prefix of 0 bytes is stored inline.
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;那么为什么是 8K，不是 7K，也不是 9K 呢？ 这么设计的原因可能是：MySQL 想让一个数据页中能存放更多的数据行，至少也得要存放两行数据（16K）。否则就失去了 B+Tree 的意义。B+Tree 会退化成一个低效的链表。&lt;/p&gt;
&lt;p&gt;你可能还会奇怪，不超过 8K ？你前面的例子明明都快 64K 也能存下，那 8K 到 64K 中间这部分怎么解释？&lt;/p&gt;
&lt;p&gt;答：如果包含可变长度列的行超过 &lt;code&gt;InnoDB&lt;/code&gt; 最大行大小， &lt;code&gt;InnoDB&lt;/code&gt; 会选择可变长度列进行页外存储，直到该行适合 &lt;code&gt;InnoDB&lt;/code&gt; ，这也就是为什么前面有超过 8K 的也能成功，那是因为用的是&lt;code&gt;VARCHAR&lt;/code&gt;这种可变长度类型。&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2023-07-14-yi-tiao-sql-zui-duo-neng-cha-xun-chu-lai-duo-shao-tiao-ji-lu/004-d87d64d7.png"&gt;&lt;/p&gt;
&lt;p&gt;当你往这个数据页中写入一行数据时，即使它很大将达到了数据页的极限，但是通过行溢出机制。依然能保证你的下一条数据还能写入到这个数据页中。&lt;/p&gt;
&lt;p&gt;我们通过 Compact 格式，简单了解一下什么是 &lt;code&gt;页外存储&lt;/code&gt; 和 &lt;code&gt;行溢出&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;MySQL8 InnoDB 引擎目前有 4 种 行记录格式：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;REDUNDANT&lt;/li&gt;
&lt;li&gt;COMPACT&lt;/li&gt;
&lt;li&gt;DYNAMIC（默认 default 是这个）&lt;/li&gt;
&lt;li&gt;COMPRESSED&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;行记录格式&lt;/code&gt; 决定了其行的物理存储方式，这反过来又会影响查询和 DML 操作的性能。&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2023-07-14-yi-tiao-sql-zui-duo-neng-cha-xun-chu-lai-duo-shao-tiao-ji-lu/005-8bab65ff.png"&gt;&lt;/p&gt;
&lt;p&gt;Compact 格式的实现思路是：当列的类型为 VARCHAR、 VARBINARY、 BLOB、TEXT 时，该列超过 768byte 的数据放到其他数据页中去。&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2023-07-14-yi-tiao-sql-zui-duo-neng-cha-xun-chu-lai-duo-shao-tiao-ji-lu/006-c96aed44.png"&gt;&lt;/p&gt;
&lt;p&gt;在 MySQL 设定中，当 varchar 列长度达到 768byte 后，会将该列的前 768byte 当作当作 prefix 存放在行中，多出来的数据溢出存放到溢出页中，然后通过一个偏移量指针将两者关联起来，这就是 &lt;code&gt;行溢出&lt;/code&gt;机制&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;“&lt;/p&gt;
&lt;p&gt;假如你要存储的数据行很大超过了 65532byte 那么你是写入不进去的。假如你要存储的单行数据小于 65535byte 但是大于 16384byte，这时你可以成功 insert，但是一个数据页又存储不了你插入的数据。这时肯定会行溢出！&lt;/p&gt;
&lt;p&gt;”&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;MySQL 这样做，有效的防止了单个 varchar 列或者 Text 列太大导致单个数据页中存放的行记录过少的情况，避免了 IO 飙升的窘境。&lt;/p&gt;
&lt;h3 id="单行最大列数限制"&gt;&lt;a href="#%e5%8d%95%e8%a1%8c%e6%9c%80%e5%a4%a7%e5%88%97%e6%95%b0%e9%99%90%e5%88%b6" class="header-anchor"&gt;&lt;/a&gt;单行最大列数限制
&lt;/h3&gt;&lt;p&gt;mysql 单表最大列数也是有限制的，是 4096 ，但 InnoDB 是 1017&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2023-07-14-yi-tiao-sql-zui-duo-neng-cha-xun-chu-lai-duo-shao-tiao-ji-lu/007-83736702.png"&gt;&lt;/p&gt;
&lt;h3 id="实验"&gt;&lt;a href="#%e5%ae%9e%e9%aa%8c" class="header-anchor"&gt;&lt;/a&gt;实验
&lt;/h3&gt;&lt;p&gt;前文中我们疑惑 &lt;code&gt;max_allowed_packet&lt;/code&gt; 在 MySQL8 的默认值是 64M，又说这是限制单行数据的，单行数据有这么大吗？在前文我们介绍了行溢出， 由于有了 &lt;code&gt;行溢出&lt;/code&gt; ，单行数据确实有可能比较大。&lt;/p&gt;
&lt;p&gt;那么还剩下一个问题，&lt;code&gt;max_allowed_packet&lt;/code&gt; 限制的确定是单行数据吗，难道不是查询结果集的大小吗 ? 下面我们做个实验，验证一下。&lt;/p&gt;
&lt;p&gt;建表&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 1&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;TABLE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;t1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 2&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;c1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;CHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="n"&gt;c2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;CHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="n"&gt;c3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;CHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 3&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;c4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;CHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="n"&gt;c5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;CHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="n"&gt;c6&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;CHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 4&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;c7&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;CHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="n"&gt;c8&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;CHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="n"&gt;c9&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;CHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 5&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;c10&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;CHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="n"&gt;c11&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;CHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="n"&gt;c12&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;CHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 6&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;c13&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;CHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="n"&gt;c14&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;CHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="n"&gt;c15&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;CHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 7&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;c16&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;CHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="n"&gt;c17&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;CHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="n"&gt;c18&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;CHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 8&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;c19&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;CHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="n"&gt;c20&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;CHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="n"&gt;c21&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;CHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 9&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;c22&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;CHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="n"&gt;c23&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;CHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="n"&gt;c24&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;CHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;10&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;c25&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;CHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="n"&gt;c26&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;CHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="n"&gt;c27&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;CHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;11&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;c28&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;CHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="n"&gt;c29&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;CHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="n"&gt;c30&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;CHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;12&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;c31&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;CHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="n"&gt;c32&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;CHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;192&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;13&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ENGINE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;InnoDB&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ROW_FORMAT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;DYNAMIC&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;DEFAULT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;CHARSET&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;latin1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;经过测试虽然提示的是 &lt;code&gt;Row size too large (&amp;gt; 8126)&lt;/code&gt; 但如果全部长度加起来是 8126 建表不成功，最终我试到 8097 是能建表成功的。为什么不是 8126 呢 ？可能是还需要存储一些其他的东西占了一些字节吧，比如隐藏字段什么的。&lt;/p&gt;
&lt;p&gt;用存储过程造一些测试数据，把表中的所有列填满&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 1&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;create&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 2&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;definer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="o"&gt;@`%`&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;procedure&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;generate_test_data&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 3&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;BEGIN&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 4&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;DECLARE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;INT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;DEFAULT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 5&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;DECLARE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;col_value&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;DEFAULT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;REPEAT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;a&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 6&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;WHILE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;DO&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 7&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;INSERT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;INTO&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;t1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;VALUES&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 8&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 9&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;col_value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;col_value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;col_value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;10&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;col_value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;REPEAT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;b&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;192&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;11&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;12&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;SET&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;13&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;WHILE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;14&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;将 &lt;code&gt;max_allowed_packet&lt;/code&gt; 设置的小一些，先用 &lt;code&gt;show VARIABLES like '%max_allowed_packet%';&lt;/code&gt; 看一下当前的大小，我的是 &lt;code&gt;67108864&lt;/code&gt; 这个单位是字节，等于 64M，然后用 &lt;code&gt;set global max_allowed_packet =1024&lt;/code&gt; 将它设置成允许的最小值 1024 byte。设置好后，关闭当前查询窗口再新建一个，然后再查看：&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2023-07-14-yi-tiao-sql-zui-duo-neng-cha-xun-chu-lai-duo-shao-tiao-ji-lu/008-6bdee1ca.png"&gt;&lt;/p&gt;
&lt;p&gt;这时我用 &lt;code&gt;select * from t1;&lt;/code&gt; 查询表数据时就会报错：&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2023-07-14-yi-tiao-sql-zui-duo-neng-cha-xun-chu-lai-duo-shao-tiao-ji-lu/009-cdde1aef.png"&gt;&lt;/p&gt;
&lt;p&gt;因为我们一条记录的大小就是 8K 多了，所以肯定超过 1024byte。可见文档的说明是对的， &lt;code&gt;max_allowed_packet&lt;/code&gt; 确实是可以约束单行记录大小的。&lt;/p&gt;
&lt;h2 id="答案"&gt;&lt;a href="#%e7%ad%94%e6%a1%88" class="header-anchor"&gt;&lt;/a&gt;答案
&lt;/h2&gt;&lt;p&gt;文章写到这里，我有点儿写不下去了，一是因为懒，另外一个原因是关于这个问题：“一条 SQL 最多能查询出来多少条记录？” 肯定没有标准答案&lt;/p&gt;
&lt;p&gt;目前我们可以知道的是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;你的单行记录大小不能超过 &lt;code&gt;max_allowed_packet&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;一个表最多可以创建 1017 列 （InnoDB）&lt;/li&gt;
&lt;li&gt;建表时定义列的固定长度不能超过 页的一半（8k,16k&amp;hellip;）&lt;/li&gt;
&lt;li&gt;建表时定义列的总长度不能超过 65535 个字节&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如果这些条件我们都满足了，然后发出了一个没有 where 条件的全表查询 &lt;code&gt;select *&lt;/code&gt; 那么&amp;hellip;..&lt;/p&gt;
&lt;p&gt;首先，你我都知道，这种情况不会发生在生产环境的，如果真发生了，一定是你写错了，忘了加条件。因为几乎没有这种要查询出所有数据的需求。如果有，也不能开发，因为这不合理。&lt;/p&gt;
&lt;p&gt;我考虑的也就是个理论情况，从理论上讲能查询出多少数据不是一个确定的值，除了前文提到的一些条件外，它肯定与以下几项有直接的关系&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;数据库的可用内存&lt;/li&gt;
&lt;li&gt;数据库内部的缓存机制，比如缓存区的大小&lt;/li&gt;
&lt;li&gt;数据库的查询超时机制&lt;/li&gt;
&lt;li&gt;应用的可用物理内存&lt;/li&gt;
&lt;li&gt;&amp;hellip;&amp;hellip;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;说到这儿，我确实可以再做个实验验证一下，但因为懒就不做了，大家有兴趣可以自己设定一些条件做个实验试一下，比如在特定内存和特定参数的情况下，到底能查询出多少数据，就能看得出来了。&lt;/p&gt;
&lt;p&gt;虽然我没能给出文章开头问题的答案，但通过寻找答案也弄清楚了 MySQL 的一些限制条件，并加以了验证，也算是有所收获了。&lt;/p&gt;
&lt;h2 id="参考"&gt;&lt;a href="#%e5%8f%82%e8%80%83" class="header-anchor"&gt;&lt;/a&gt;参考
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://dev.mysql.com/doc/refman/8.0/en/packet-too-large.html" target="_blank" rel="noopener"
 &gt;https://dev.mysql.com/doc/refman/8.0/en/packet-too-large.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://dev.mysql.com/doc/refman/8.0/en/server-system-variables.html#sysvar" target="_blank" rel="noopener"
 &gt;https://dev.mysql.com/doc/refman/8.0/en/server-system-variables.html#sysvar&lt;/a&gt;_max_allowed_packet&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.cnblogs.com/ZhuChangwu/p/14035330.html" target="_blank" rel="noopener"
 &gt;https://www.cnblogs.com/ZhuChangwu/p/14035330.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://dev.mysql.com/doc/refman/8.0/en/column-count-limit.html" target="_blank" rel="noopener"
 &gt;https://dev.mysql.com/doc/refman/8.0/en/column-count-limit.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>ZooKeeper 并没有直接采用 Paxos 算法</title><link>https://xiaobox.github.io/p/2022-05-23-zookeeper-bing-mei-you-zhi-jie-cai-yong-paxos-suan-fa/</link><pubDate>Mon, 23 May 2022 07:58:58 +0000</pubDate><guid>https://xiaobox.github.io/p/2022-05-23-zookeeper-bing-mei-you-zhi-jie-cai-yong-paxos-suan-fa/</guid><description>&lt;img src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2022-05-23-zookeeper-bing-mei-you-zhi-jie-cai-yong-paxos-suan-fa/cover.jpg" alt="Featured image of post ZooKeeper 并没有直接采用 Paxos 算法" /&gt;&lt;p&gt;ZooKeeper 并没有直接采用 Paxos 算法，而是采用一种被称为 ZAB（ZooKeeper Atomic Broadcast）的一致性协议&lt;/p&gt;
&lt;p&gt;Paxos 算法是基于消息传递的分布式一致性算法，很多大型的网络技术公司和开源框架都采用 Paxos 算法作为其各自的底层解决方案，比如 Chubby 、 Megastore 以及 MySQL Group Replication 。Paxos 算法运行在服务器发生宕机故障的时候，能够保证数据的完整性，不要求可靠的消息传递，可容忍消息丢失、延迟、乱序以及重复，保证服务的高可用性。&lt;/p&gt;
&lt;p&gt;ZAB 协议并不像 Paxos 算法那样，一种通用的分布式一致性算法，而是一种特别为 ZooKeeper 设计的崩溃可恢复的原子消息广播算法&lt;/p&gt;
&lt;p&gt;当 Leader 服务器不可用或者已经不存在过半服务器与该 Leader 服务器保持正常通信时，在重新开始新一轮的原子广播事务操作之前，ZAB 会进入恢复模式选举新的 Leader 服务器，使集群彼此达到一个一致的状态，从消息广播模式进入到崩溃恢复模式。当集群过半机器都与新的 Leader 服务器完成了状态同步操作后 ZAB 协议会退出恢复模式&lt;/p&gt;
&lt;p&gt;两者相同之处是，在执行事务会话的处理中，两种算法最开始都需要一台服务器或者线程针对该会话，在集群中发起提案或是投票。只有当集群中的过半数服务器对该提案投票通过后，才能执行接下来的处理。&lt;/p&gt;
&lt;p&gt;而 Paxos 算法与 ZAB 协议不同的是，Paxos 算法的发起者可以是一个或多个。当集群中的 Acceptor 服务器中的大多数可以执行会话请求后，提议者服务器只负责发送提交指令，事务的执行实际发生在 Acceptor 服务器。这与 ZooKeeper 服务器上事务的执行发生在 Leader 服务器上不同。Paxos 算法在数据同步阶段，是多台 Acceptor 服务器作为数据源同步给集群中的多台 Learner 服务器，而 ZooKeeper 则是单台 Leader 服务器作为数据源同步给集群中的其他角色服务器。&lt;/p&gt;
&lt;h2 id="参考"&gt;&lt;a href="#%e5%8f%82%e8%80%83" class="header-anchor"&gt;&lt;/a&gt;参考
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://www.cnblogs.com/aspirant/p/13423780.html" target="_blank" rel="noopener"
 &gt;https://www.cnblogs.com/aspirant/p/13423780.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://learn.lianglianglee.com/%E4%B8%93%E6%A0%8F/ZooKeeper%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90%E4%B8%8E%E5%AE%9E%E6%88%98-%E5%AE%8C/30%20ZAB%20%E4%B8%8E%20Paxos%20%E7%AE%97%E6%B3%95%E7%9A%84%E8%81%94%E7%B3%BB%E4%B8%8E%E5%8C%BA%E5%88%AB.md" target="_blank" rel="noopener"
 &gt;https://learn.lianglianglee.com/%E4%B8%93%E6%A0%8F/ZooKeeper%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90%E4%B8%8E%E5%AE%9E%E6%88%98-%E5%AE%8C/30%20ZAB%20%E4%B8%8E%20Paxos%20%E7%AE%97%E6%B3%95%E7%9A%84%E8%81%94%E7%B3%BB%E4%B8%8E%E5%8C%BA%E5%88%AB.md&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>MySQL B+树叶子结点使用单向链表进行串连？错！</title><link>https://xiaobox.github.io/p/2022-05-20-mysql-b-shu-ye-zi-jie-dian-shi-yong-dan-xiang-lian-biao-jin/</link><pubDate>Fri, 20 May 2022 04:15:49 +0000</pubDate><guid>https://xiaobox.github.io/p/2022-05-20-mysql-b-shu-ye-zi-jie-dian-shi-yong-dan-xiang-lian-biao-jin/</guid><description>&lt;img src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2022-05-20-mysql-b-shu-ye-zi-jie-dian-shi-yong-dan-xiang-lian-biao-jin-/cover.jpg" alt="Featured image of post MySQL B+树叶子结点使用单向链表进行串连？错！" /&gt;&lt;p&gt;&lt;img class="gallery-image" data-flex-basis="372px" data-flex-grow="155" height="537" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://xiaobox.github.io/p/2022-05-20-mysql-b-shu-ye-zi-jie-dian-shi-yong-dan-xiang-lian-biao-jin/001-cca6a65b.jpg" width="833"&gt;&lt;/p&gt;
&lt;p&gt;先看一下上面这个图，大家是不是觉得没什么毛病？&lt;/p&gt;
&lt;p&gt;如题，就是叶子结点用单向链表连接起来是吧。&lt;/p&gt;
&lt;p&gt;很多文章是这么讲的，很多图也是这么画的，但其实不正确，或者说不严谨。&lt;/p&gt;
&lt;p&gt;正确的说法应该是：B+ 树中各个页之间是通过双向链表连接的，叶子节点中的数据是通过单向链表连接的&lt;/p&gt;
&lt;p&gt;我们来看下正确的图：&lt;/p&gt;
&lt;p&gt;&lt;img class="gallery-image" data-flex-basis="330px" data-flex-grow="137" height="680" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://xiaobox.github.io/p/2022-05-20-mysql-b-shu-ye-zi-jie-dian-shi-yong-dan-xiang-lian-biao-jin/002-d1ced06d.jpg" width="936"&gt;&lt;/p&gt;
&lt;p&gt;或者下面这个：&lt;/p&gt;
&lt;p&gt;&lt;img class="gallery-image" data-flex-basis="351px" data-flex-grow="146" height="737" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://xiaobox.github.io/p/2022-05-20-mysql-b-shu-ye-zi-jie-dian-shi-yong-dan-xiang-lian-biao-jin/003-09674de7.jpg" width="1080"&gt;&lt;/p&gt;
&lt;p&gt;希望能够帮到一直对B+tree 有误解的同学。&lt;/p&gt;
&lt;p&gt;&lt;img class="gallery-image" data-flex-basis="328px" data-flex-grow="137" height="788" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://xiaobox.github.io/p/2022-05-20-mysql-b-shu-ye-zi-jie-dian-shi-yong-dan-xiang-lian-biao-jin/004-ec6e6036.jpg" width="1080"&gt;&lt;/p&gt;</description></item><item><title>我就想存个文件，怎么这么麻烦 ？- k8s PV、PVC、StorageClass 的关系</title><link>https://xiaobox.github.io/p/2021-11-26-wo-jiu-xiang-cun-ge-wen-jian-zen-me-zhe-me-ma-fan-k8s-pv-pvc/</link><pubDate>Fri, 26 Nov 2021 09:59:13 +0000</pubDate><guid>https://xiaobox.github.io/p/2021-11-26-wo-jiu-xiang-cun-ge-wen-jian-zen-me-zhe-me-ma-fan-k8s-pv-pvc/</guid><description>&lt;img src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2021-11-26-wo-jiu-xiang-cun-ge-wen-jian-zen-me-zhe-me-ma-fan-k8s-pv-pvc/cover.jpg" alt="Featured image of post 我就想存个文件，怎么这么麻烦 ？- k8s PV、PVC、StorageClass 的关系" /&gt;&lt;h2 id="docker"&gt;&lt;a href="#docker" class="header-anchor"&gt;&lt;/a&gt;Docker
&lt;/h2&gt;&lt;p&gt;当我们使用 Docker 时，设置数据卷（Volume）还是比较简单的，只需要在容器映射指定卷的路径，然后在容器中使用该路径即可。&lt;/p&gt;
&lt;p&gt;比如这种：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 1&lt;/span&gt;&lt;span class="cl"&gt;# tomcat
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 2&lt;/span&gt;&lt;span class="cl"&gt; tomcat01:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 3&lt;/span&gt;&lt;span class="cl"&gt; hostname: tomcat01
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 4&lt;/span&gt;&lt;span class="cl"&gt; restart: always
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 5&lt;/span&gt;&lt;span class="cl"&gt; image: jdk-tomcat:v8
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 6&lt;/span&gt;&lt;span class="cl"&gt; container_name: tomcat8-1
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 7&lt;/span&gt;&lt;span class="cl"&gt; links:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 8&lt;/span&gt;&lt;span class="cl"&gt; - mysql:mysql
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 9&lt;/span&gt;&lt;span class="cl"&gt; volumes:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;10&lt;/span&gt;&lt;span class="cl"&gt; - /home/soft/docker/tomcat/webapps:/usr/local/apache-tomcat-8.5.39/webapps
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;11&lt;/span&gt;&lt;span class="cl"&gt; - /home/soft/docker/tomcat/logs:/usr/local/apache-tomcat-8.5.39/logs
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;12&lt;/span&gt;&lt;span class="cl"&gt; - /etc/localtime:/etc/localtime
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;13&lt;/span&gt;&lt;span class="cl"&gt; environment:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;14&lt;/span&gt;&lt;span class="cl"&gt; JAVA_OPTS: -Dspring.profiles.active=prod
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;15&lt;/span&gt;&lt;span class="cl"&gt; TZ: Asia/Shanghai
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;16&lt;/span&gt;&lt;span class="cl"&gt; LANG: C.UTF-8
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;17&lt;/span&gt;&lt;span class="cl"&gt; LC_ALL: zh_CN.UTF-8
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;18&lt;/span&gt;&lt;span class="cl"&gt; env_file:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;19&lt;/span&gt;&lt;span class="cl"&gt; - /home/soft/docker/env/tomcat.env
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;为什么要设置 Volume？当然是因为我们要持久化数据，要把数据存储到硬盘上。&lt;/p&gt;
&lt;h2 id="k8s"&gt;&lt;a href="#k8s" class="header-anchor"&gt;&lt;/a&gt;k8s
&lt;/h2&gt;&lt;p&gt;到了 k8s 这儿，你会发现事情没那么简单了，涌现出了一堆概念：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Pv&lt;/li&gt;
&lt;li&gt;Pvc&lt;/li&gt;
&lt;li&gt;StorageClass&lt;/li&gt;
&lt;li&gt;Provisioner&lt;/li&gt;
&lt;li&gt;&amp;hellip;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;先不管这些复杂的概念，我只想存个文件，有没有简单的方式？&lt;/p&gt;
&lt;p&gt;有，我们先回顾下基本概念。&lt;/p&gt;
&lt;p&gt;我们知道，Container 中的文件在磁盘上是临时存放的，当容器崩溃时文件丢失。kubelet 会重新启动容器， 但容器会以干净的状态重启。所以我们要使用 Volume 来持久化数据。&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;“&lt;/p&gt;
&lt;p&gt;Docker 也有 卷（Volume） 的概念，但对它只有少量且松散的管理。Docker 卷是磁盘上或者另外一个容器内的一个目录 Docker 提供卷驱动程序，但是其功能非常有限。&lt;/p&gt;
&lt;p&gt;”&lt;/p&gt;

 &lt;/blockquote&gt;

 &lt;blockquote&gt;
 &lt;p&gt;“&lt;/p&gt;
&lt;p&gt;Kubernetes 支持很多类型的卷。Pod 可以同时使用任意数目的卷类型。&lt;/p&gt;
&lt;p&gt;”&lt;/p&gt;

 &lt;/blockquote&gt;

 &lt;blockquote&gt;
 &lt;p&gt;“&lt;/p&gt;
&lt;p&gt;临时卷类型的生命周期与 Pod 相同，但持久卷可以比 Pod 的存活期长。当 Pod 不再存在时，Kubernetes 也会销毁临时卷；不过 Kubernetes 不会销毁 持久卷。对于给定 Pod 中任何类型的卷，在容器重启期间数据都不会丢失。&lt;/p&gt;
&lt;p&gt;”&lt;/p&gt;

 &lt;/blockquote&gt;

 &lt;blockquote&gt;
 &lt;p&gt;“&lt;/p&gt;
&lt;p&gt;卷的核心是一个目录，其中可能存有数据，Pod 中的容器可以访问该目录中的数据。所采用的特定的卷类型将决定该目录如何形成的、使用何种介质保存数据以及目录中存放 的内容。&lt;/p&gt;
&lt;p&gt;”&lt;/p&gt;

 &lt;/blockquote&gt;

 &lt;blockquote&gt;
 &lt;p&gt;“&lt;/p&gt;
&lt;p&gt;使用卷时，在 .spec.volumes 字段中设置为 Pod 提供的卷，并在 .spec.containers[*].volumeMounts 字段中声明卷在容器中的挂载位置。各个卷则挂载在镜像内的指定路径上。卷不能挂载到其他卷之上，也不能与其他卷有硬链接。Pod 配置中的每个容器必须独立指定各个卷的挂载位置。&lt;/p&gt;
&lt;p&gt;”&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;通过上面的概念我们知道 Volume 有不同的类型，有临时的，也有持久的，那么我们先说说简单的，即解决“我只想存个文件，有没有简单的方式”的需求。&lt;/p&gt;
&lt;h3 id="hostpath"&gt;&lt;a href="#hostpath" class="header-anchor"&gt;&lt;/a&gt;hostPath
&lt;/h3&gt;&lt;p&gt;hostPath 卷能将主机节点文件系统上的文件或目录挂载到你的 Pod 中。看个示例：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 1&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;v1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 2&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Pod&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 3&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 4&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;test-webserver&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 5&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 6&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;containers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 7&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;test-webserver&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 8&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;k8s.gcr.io/test-webserver:latest&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 9&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;volumeMounts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;10&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;mountPath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;/var/local/aaa&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;11&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;mydir&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;12&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;mountPath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;/var/local/aaa/1.txt&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;13&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;myfile&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;14&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;15&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;mydir&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;16&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;hostPath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;17&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# 确保文件所在目录成功创建。&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;18&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;/var/local/aaa&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;19&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;DirectoryOrCreate&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;20&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;myfile&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;21&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;hostPath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;22&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;/var/local/aaa/1.txt&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;23&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;FileOrCreate&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;通过 hostPath 能够简单解决文件在宿主机上存储的问题。&lt;/p&gt;
&lt;p&gt;不过需要注意的是：&lt;/p&gt;
&lt;p&gt;HostPath 卷存在许多安全风险，最佳做法是尽可能避免使用 HostPath。当必须使用 HostPath 卷时，它的范围应仅限于所需的文件或目录，并以只读方式挂载。&lt;/p&gt;
&lt;p&gt;使用 hostPath 还有一个局限性就是，我们的 Pod 不能随便漂移，需要固定到一个节点上，因为一旦漂移到其他节点上去了宿主机上面就没有对应的数据了，所以我们在使用 hostPath 的时候都会搭配 nodeSelector 来进行使用。&lt;/p&gt;
&lt;h3 id="emptydir"&gt;&lt;a href="#emptydir" class="header-anchor"&gt;&lt;/a&gt;emptyDir
&lt;/h3&gt;&lt;p&gt;emptyDir 也是比较常见的一种存储类型。&lt;/p&gt;
&lt;p&gt;上面的 hostPath 显示的定义了宿主机的目录。emptyDir 类似隐式的指定。&lt;/p&gt;
&lt;p&gt;Kubernetes 会在宿主机上创建一个临时目录，这个目录将来就会被绑定挂载到容器所声明的 Volume 目录上。而 Pod 中的容器，使用的是 volumeMounts 字段来声明自己要挂载哪个 Volume，并通过 mountPath 字段来定义容器内的 Volume 目录&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;“&lt;/p&gt;
&lt;p&gt;当 Pod 分派到某个 Node 上时，emptyDir 卷会被创建，并且在 Pod 在该节点上运行期间，卷一直存在。就像其名称表示的那样，卷最初是空的。尽管 Pod 中的容器挂载 emptyDir 卷的路径可能相同也可能不同，这些容器都可以读写 emptyDir 卷中相同的文件。当 Pod 因为某些原因被从节点上删除时，emptyDir 卷中的数据也会被永久删除。&lt;/p&gt;
&lt;p&gt;”&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 1&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;v1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 2&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Pod&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 3&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 4&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;test-pd&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 5&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 6&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;containers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 7&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;k8s.gcr.io/test-webserver&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 8&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;test-container&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 9&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;volumeMounts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;10&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;mountPath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;/cache&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;11&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;cache-volume&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;12&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;13&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;cache-volume&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;14&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;emptyDir&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;{}&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;如果执行 &lt;code&gt;kubectl describe&lt;/code&gt; 命令查看 pod 信息的话，可以验证前面我们说的内容：&amp;ldquo;EmptyDir (a temporary directory that shares a pod&amp;rsquo;s lifetime)&amp;rdquo;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 1&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="nn"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 2&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;Containers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 3&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;nginx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 4&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;Container ID&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;docker://07b4f89248791c2aa47787e3da3cc94b48576cd173018356a6ec8db2b6041343&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 5&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;nginx:1.8&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 6&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 7&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;Environment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;&amp;lt;none&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 8&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;Mounts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 9&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;/usr/share/nginx/html from nginx-vol (rw)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;10&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="nn"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;11&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;Volumes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;12&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;nginx-vol&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;13&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;Type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;EmptyDir (a temporary directory that shares a pod&amp;#39;s lifetime)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="pv-和-pvc"&gt;&lt;a href="#pv-%e5%92%8c-pvc" class="header-anchor"&gt;&lt;/a&gt;PV 和 PVC
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;PV(PersistentVolume): 持久化卷&lt;/li&gt;
&lt;li&gt;PVC(PersistentVolumeClaim): 持久化卷声明&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;PV 和 PVC 的关系就像 java 中接口和实现的关系类似。&lt;/p&gt;
&lt;p&gt;PVC 是用户存储的一种声明，PVC 和 Pod 比较类似，Pod 消耗的是节点，PVC 消耗的是 PV 资源，Pod 可以请求 CPU 和内存，而 PVC 可以请求特定的存储空间和访问模式。对于真正使用存储的用户不需要关心底层的存储实现细节，只需要直接使用 PVC 即可。&lt;/p&gt;
&lt;p&gt;PV 是对底层共享存储的一种抽象，由管理员进行创建和配置，它和具体的底层的共享存储技术的实现方式有关，比如 Ceph、GlusterFS、NFS、hostPath 等，都是通过插件机制完成与共享存储的对接。&lt;/p&gt;
&lt;p&gt;我们来看一个例子：&lt;/p&gt;
&lt;p&gt;比如，运维人员可以定义这样一个 NFS 类型的 PV&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 1&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;v1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 2&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;PersistentVolume&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 3&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 4&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;nfs&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 5&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 6&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;storageClassName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;manual&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 7&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;capacity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 8&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;1Gi&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 9&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;accessModes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;10&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;ReadWriteMany&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;11&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;nfs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;12&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;server&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;10.244.1.4&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;13&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;/&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;PVC 描述的，则是 Pod 所希望使用的持久化存储的属性。比如，Volume 存储的大小、可读写权限等等。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 1&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 2&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;v1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 3&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;PersistentVolumeClaim&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 4&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 5&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;nfs&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 6&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 7&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;accessModes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 8&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;ReadWriteMany&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 9&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;storageClassName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;manual&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;10&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;resources&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;11&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;12&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;1Gi&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;用户创建的 PVC 要真正被容器使用起来，就必须先和某个符合条件的 PV 进行绑定。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;第一个条件是 PV 和 PVC 的 spec 字段。比如，PV 的存储（storage）大小，就必须满足 PVC 的要求。&lt;/li&gt;
&lt;li&gt;第二个条件，则是 PV 和 PVC 的 storageClassName 字段必须一样&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在成功地将 PVC 和 PV 进行绑定之后，Pod 就能够像使用 hostPath 等常规类型的 Volume 一样，在自己的 YAML 文件里声明使用这个 PVC 了&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 1&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;v1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 2&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Pod&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 3&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 4&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;labels&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 5&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;web-frontend&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 6&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 7&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;containers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 8&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;web&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 9&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;nginx&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;10&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;11&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;web&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;12&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;containerPort&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;80&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;13&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;volumeMounts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;14&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;nfs&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;15&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;mountPath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;/usr/share/nginx/html&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;16&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;17&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;nfs&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;18&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;persistentVolumeClaim&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;19&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;claimName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;nfs&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;我们前面使用的 hostPath 和 emptyDir 类型的 Volume 并不具备“持久化”特征，既有可能被 kubelet 清理掉，也不能被“迁移”到其他节点上。所以，大多数情况下，持久化 Volume 的实现，往往依赖于一个远程存储服务，比如：远程文件存储（比如，NFS、GlusterFS）、远程块存储（比如，公有云提供的远程磁盘）等等。&lt;/p&gt;
&lt;h3 id="storageclass"&gt;&lt;a href="#storageclass" class="header-anchor"&gt;&lt;/a&gt;StorageClass
&lt;/h3&gt;&lt;p&gt;前面我们人工管理 PV 的方式就叫作 Static Provisioning。&lt;/p&gt;
&lt;p&gt;一个大规模的 Kubernetes 集群里很可能有成千上万个 PVC，这就意味着运维人员必须得事先创建出成千上万个 PV。更麻烦的是，随着新的 PVC 不断被提交，运维人员就不得不继续添加新的、能满足条件的 PV，否则新的 Pod 就会因为 PVC 绑定不到 PV 而失败。在实际操作中，这几乎没办法靠人工做到。所以，Kubernetes 为我们提供了一套可以自动创建 PV 的机制，即：Dynamic Provisioning。&lt;/p&gt;
&lt;p&gt;Dynamic Provisioning 机制工作的核心，在于一个名叫 StorageClass 的 API 对象。而 StorageClass 对象的作用，其实就是创建 PV 的模板。&lt;/p&gt;
&lt;p&gt;具体地说，StorageClass 对象会定义如下两个部分内容：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;第一，PV 的属性。比如，存储类型、Volume 的大小等等。&lt;/li&gt;
&lt;li&gt;第二，创建这种 PV 需要用到的存储插件。比如，Ceph 等等。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;有了这样两个信息之后，Kubernetes 就能够根据用户提交的 PVC，找到一个对应的 StorageClass 了。然后，Kubernetes 就会调用该 StorageClass 声明的存储插件，创建出需要的 PV。&lt;/p&gt;
&lt;p&gt;在下面的例子中，PV 是被自动创建出来的。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 1&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;v1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 2&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;PersistentVolumeClaim&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 3&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 4&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;claim1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 5&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 6&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;accessModes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 7&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;ReadWriteOnce&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 8&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# 指定所使用的存储类，此存储类将会自动创建符合要求的 PV&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 9&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;storageClassName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;fast&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;10&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;resources&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;11&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;12&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;30Gi&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;13&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;14&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;storage.k8s.io/v1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;15&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;StorageClass&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;16&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;17&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;fast&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;18&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;provisioner&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;kubernetes.io/gce-pd&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;19&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;parameters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;20&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;pd-ssd&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;StorageClass 的作用，则是充当 PV 的模板。并且，只有同属于一个 StorageClass 的 PV 和 PVC，才可以绑定在一起。StorageClass 的另一个重要作用，是指定 PV 的 Provisioner（存储插件）。这时候，如果你的存储插件支持 Dynamic Provisioning 的话，Kubernetes 就可以自动为你创建 PV 了。&lt;/p&gt;
&lt;h3 id="local-pv"&gt;&lt;a href="#local-pv" class="header-anchor"&gt;&lt;/a&gt;Local PV
&lt;/h3&gt;&lt;p&gt;Kubernetes 依靠 PV、PVC 实现了一个新的特性，这个特性的名字叫作：Local Persistent Volume，也就是 Local PV。&lt;/p&gt;
&lt;p&gt;Local PV 实现的功能就非常类似于 hostPath 加上 nodeAffinity，比如，一个 Pod 可以声明使用类型为 Local 的 PV，而这个 PV 其实就是一个 hostPath 类型的 Volume。如果这个 hostPath 对应的目录，已经在节点 A 上被事先创建好了，那么，我只需要再给这个 Pod 加上一个 nodeAffinity=nodeA，不就可以使用这个 Volume 了吗？理论上确实是可行的，但是事实上，我们绝不应该把一个宿主机上的目录当作 PV 来使用，因为本地目录的存储行为是完全不可控，它所在的磁盘随时都可能被应用写满，甚至造成整个宿主机宕机。所以，一般来说 Local PV 对应的存储介质是一块额外挂载在宿主机的磁盘或者块设备，我们可以认为就是“一个 PV 一块盘”。&lt;/p&gt;
&lt;p&gt;Local PV 和普通的 PV 有一个很大的不同在于 Local PV 可以保证 Pod 始终能够被正确地调度到它所请求的 Local PV 所在的节点上面，对于普通的 PV 来说，Kubernetes 都是先调度 Pod 到某个节点上，然后再持久化节点上的 Volume 目录，进而完成 Volume 目录与容器的绑定挂载，但是对于 Local PV 来说，节点上可供使用的磁盘必须是提前准备好的，因为它们在不同节点上的挂载情况可能完全不同，甚至有的节点可以没这种磁盘，所以，这时候，调度器就必须能够知道所有节点与 Local PV 对应的磁盘的关联关系，然后根据这个信息来调度 Pod，实际上就是在调度的时候考虑 Volume 的分布。&lt;/p&gt;
&lt;p&gt;例子：&lt;/p&gt;
&lt;p&gt;先创建本地磁盘对应的 pv&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 1&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;v1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 2&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;PersistentVolume&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 3&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 4&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;example-pv&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 5&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 6&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;capacity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 7&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;5Gi&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 8&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;volumeMode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Filesystem&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 9&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;accessModes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;10&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;ReadWriteOnce&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;11&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;persistentVolumeReclaimPolicy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Delete&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;12&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;storageClassName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;local-storage&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;13&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;local&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;14&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;/mnt/disks/vol1 &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;15&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;nodeAffinity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;16&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;required&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;17&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;nodeSelectorTerms&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;18&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;matchExpressions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;19&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;kubernetes.io/hostname&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;20&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;operator&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;In&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;21&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;values&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;22&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;node-1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;其中：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;lcal.path 写对应的磁盘路径&lt;/li&gt;
&lt;li&gt;必须指定对应的 node , 用 .spec.nodeAffinity 来对应的 node&lt;/li&gt;
&lt;li&gt;.spec.volumeMode 可以是 FileSystem（Default）和 Block&lt;/li&gt;
&lt;li&gt;确保先运行了 StorageClass （即下面写的文件）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;再写对于的 StorageClass 文件&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;1&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;StorageClass&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;2&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;storage.k8s.io/v1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;3&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;4&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;local-storage&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;5&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;provisioner&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;kubernetes.io/no-provisioner&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;6&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;volumeBindingMode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;WaitForFirstConsumer&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;其中：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;provisioner 是 kubernetes.io/no-provisioner , 这是因为 local pv 不支持 Dynamic Provisioning, 所以它没有办法在创建出 pvc 的时候，自动创建对应 pv&lt;/li&gt;
&lt;li&gt;volumeBindingMode 是 WaitForFirstConsumer , WaitForFirstConsumer 即延迟绑定 , 这样可以既保证推迟到调度的时候再进行绑定 , 又可以保证调度到指定的 pod 上 , 其实 WaitForFirstConsumer 又 2 种：一种是 WaitForFirstConsumer , 一种是 Immediate , 这里必须用延迟绑定模式。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;再创建一个 pvc&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 1&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;PersistentVolumeClaim&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 2&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;v1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 3&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 4&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;example-local-claim&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 5&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 6&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;accessModes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 7&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;ReadWriteOnce&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 8&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;resources&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 9&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;10&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;5Gi&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;11&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;storageClassName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;local-storage&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这里需要注意的地方就是 storageClassName 要写出我们之前自己创建的 storageClassName 的名字：local-storage&lt;/p&gt;
&lt;p&gt;之后应用这个文件 , 使用命令 kubectl get pvc 可以看到他的状态是 Pending , 这个时候虽然有了匹配的 pv , 但是也不会进行绑定 , 依然在等待。&lt;/p&gt;
&lt;p&gt;之后我们写个 pod 应用这个 pvc&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 1&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Pod&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 2&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;v1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 3&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 4&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;example-pv-pod&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 5&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 6&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 7&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;example-pv-storage&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 8&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;persistentVolumeClaim&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 9&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;claimName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;example-local-claim&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;10&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;containers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;11&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;example-pv-container&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;12&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;nginx&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;13&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;14&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;containerPort&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;80&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;15&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;http-server&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;16&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;volumeMounts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;17&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;mountPath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;/usr/share/nginx/html&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;18&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;example-pv-storage&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这样就部署好了一个 local pv 在 pod 上 , 这样即使 pod 没有了 , 再次重新在这个 node 上创建，写入的文件也能持久化的存储在特定位置。&lt;/p&gt;
&lt;p&gt;如何删除这个 pv 一定要按照流程来 , 要不然会删除失败&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;删除使用这个 pv 的 pod&lt;/li&gt;
&lt;li&gt;从 node 上移除这个磁盘（按照一个 pv 一块盘）&lt;/li&gt;
&lt;li&gt;删除 pvc&lt;/li&gt;
&lt;li&gt;删除 pv&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="总结"&gt;&lt;a href="#%e6%80%bb%e7%bb%93" class="header-anchor"&gt;&lt;/a&gt;总结
&lt;/h2&gt;&lt;p&gt;本文我们讨论了 kubernetes 存储的几种类型，有临时存储如：hostPath、emptyDir,也有真正的持久化存储，还讨论了相关的概念，如：PVC、PV、StorageClass等,下图是对这些概念的一个概括：&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2021-11-26-wo-jiu-xiang-cun-ge-wen-jian-zen-me-zhe-me-ma-fan-k8s-pv-pvc/001-1e4e81b7.jpg"&gt;、&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2021-11-26-wo-jiu-xiang-cun-ge-wen-jian-zen-me-zhe-me-ma-fan-k8s-pv-pvc/002-68f3b10a.jpg"&gt;&lt;/p&gt;
&lt;h2 id="参考"&gt;&lt;a href="#%e5%8f%82%e8%80%83" class="header-anchor"&gt;&lt;/a&gt;参考
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;极客时间：深入剖析 Kubernetes 课程&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://kubernetes.io/zh/docs/concepts/storage/volumes/#emptydir" target="_blank" rel="noopener"
 &gt;https://kubernetes.io/zh/docs/concepts/storage/volumes/#emptydir&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.qikqiak.com/k8strain/storage/local/" target="_blank" rel="noopener"
 &gt;https://www.qikqiak.com/k8strain/storage/local/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.kubernetes.org.cn/4078.html" target="_blank" rel="noopener"
 &gt;https://www.kubernetes.org.cn/4078.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://haojianxun.github.io/2019/01/10/kubernetes%E7%9A%84%E6%9C%AC%E5%9C%B0%E6%8C%81%E4%B9%85%E5%8C%96%E5%AD%98%E5%82%A8--Local%20Persistent%20Volume%E8%A7%A3%E6%9E%90/" target="_blank" rel="noopener"
 &gt;https://haojianxun.github.io/2019/01/10/kubernetes%E7%9A%84%E6%9C%AC%E5%9C%B0%E6%8C%81%E4%B9%85%E5%8C%96%E5%AD%98%E5%82%A8--Local%20Persistent%20Volume%E8%A7%A3%E6%9E%90/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Kubernetes监控体系总结</title><link>https://xiaobox.github.io/p/2021-11-15-kubernetes-jian-kong-ti-xi-zong-jie/</link><pubDate>Mon, 15 Nov 2021 12:21:02 +0000</pubDate><guid>https://xiaobox.github.io/p/2021-11-15-kubernetes-jian-kong-ti-xi-zong-jie/</guid><description>&lt;img src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2021-11-15-kubernetes-jian-kong-ti-xi-zong-jie/cover.jpg" alt="Featured image of post Kubernetes监控体系总结" /&gt;&lt;h2 id="基本概念"&gt;&lt;a href="#%e5%9f%ba%e6%9c%ac%e6%a6%82%e5%bf%b5" class="header-anchor"&gt;&lt;/a&gt;基本概念
&lt;/h2&gt;&lt;h3 id="cadvisor"&gt;&lt;a href="#cadvisor" class="header-anchor"&gt;&lt;/a&gt;cAdvisor
&lt;/h3&gt;&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2021-11-15-kubernetes-jian-kong-ti-xi-zong-jie/001-46ee78b3.jpg"&gt;&lt;/p&gt;
&lt;p&gt;Docker 是一个开源的应用容器引擎，让开发者可以打包他们的应用以及依赖包到一个可移植的容器中，然后发布到任何流行的 Linux/Windows/Mac 机器上。容器镜像正成为一个新的标准化软件交付方式。为了能够获取到 Docker 容器的运行状态，用户可以通过 Docker 的 stats 命令获取到当前主机上运行容器的统计信息，可以查看容器的 CPU 利用率、内存使用量、网络 IO 总量以及磁盘 IO 总量等信息。&lt;/p&gt;
&lt;p&gt;显然如果我们想对监控数据做存储以及可视化的展示，那么 docker 的 stats 是不能满足的。&lt;/p&gt;
&lt;p&gt;为了解决 docker stats 的问题（存储、展示），谷歌开源的 cadvisor 诞生了，cadvisor 不仅可以搜集一台机器上所有运行的容器信息，还提供基础查询界面和 http 接口，方便其他组件如 Prometheus 进行数据抓取，或者 cAdvisor + influxDB + grafana 搭配使用。cAdvisor 可以对节点机器上的资源及容器进行实时监控和性能数据采集，包括 CPU 使用情况、内存使用情况、网络吞吐量及文件系统使用情况&lt;/p&gt;
&lt;p&gt;监控原理&lt;/p&gt;
&lt;p&gt;cAdvisor 使用 Go 语言开发，利用 Linux 的 cgroups 获取容器的资源使用信息，在 K8S 中集成在 Kubelet 里作为默认启动项，官方标配。&lt;/p&gt;
&lt;p&gt;Docker 是基于 Namespace、Cgroups 和联合文件系统实现的&lt;/p&gt;
&lt;p&gt;Cgroups 不仅可以用于容器资源的限制，还可以提供容器的资源使用率。不管用什么监控方案，底层数据都来源于 Cgroups&lt;/p&gt;
&lt;p&gt;Cgroups 的工作目录 /sys/fs/cgroup 下包含了 Cgroups 的所有内容。Cgroups 包含了很多子系统，可以对 CPU，内存，PID，磁盘 IO 等资源进行限制和监控。&lt;/p&gt;
&lt;p&gt;cAdvisor 运行原理，如下图&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2021-11-15-kubernetes-jian-kong-ti-xi-zong-jie/002-3731be65.jpg"&gt;&lt;/p&gt;
&lt;h3 id="prometheus"&gt;&lt;a href="#prometheus" class="header-anchor"&gt;&lt;/a&gt;Prometheus
&lt;/h3&gt;&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2021-11-15-kubernetes-jian-kong-ti-xi-zong-jie/003-9dea7ba3.jpg"&gt;&lt;/p&gt;
&lt;p&gt;Prometheus 是一套开源的监控报警系统。主要特点包括多维数据模型、灵活查询语句 PromQL 以及数据可视化展示等&lt;/p&gt;
&lt;p&gt;架构图&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2021-11-15-kubernetes-jian-kong-ti-xi-zong-jie/004-b737ebd1.jpg"&gt;&lt;/p&gt;
&lt;p&gt;基本原理&lt;/p&gt;
&lt;p&gt;Prometheus 的基本原理是通过 HTTP 协议周期性抓取被监控组件的状态，任意组件只要提供对应的 HTTP 接口就可以接入监控。不需要任何 SDK 或者其他的集成过程。这样做非常适合做虚拟化环境监控系统，比如 VM、Docker、Kubernetes 等。输出被监控组件信息的 HTTP 接口被叫做 exporter 。目前互联网公司常用的组件大部分都有 exporter 可以直接使用，比如 Varnish、Haproxy、Nginx、MySQL、Linux 系统信息（包括磁盘、内存、CPU、网络等等）。&lt;/p&gt;
&lt;p&gt;服务过程&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Prometheus Daemon 负责定时去目标上抓取 metrics（指标）数据，每个抓取目标需要暴露一个 http 服务的接口给它定时抓取。Prometheus 支持通过配置文件、文本文件、Zookeeper、Consul、DNS SRV Lookup 等方式指定抓取目标。Prometheus 采用 PULL 的方式进行监控，即服务器可以直接通过目标 PULL 数据或者间接地通过中间网关来 Push 数据。&lt;/li&gt;
&lt;li&gt;Prometheus 在本地存储抓取的所有数据，并通过一定规则进行清理和整理数据，并把得到的结果存储到新的时间序列中。&lt;/li&gt;
&lt;li&gt;Prometheus 通过 PromQL 和其他 API 可视化地展示收集的数据。Prometheus 支持很多方式的图表可视化，例如 Grafana、自带的 Promdash 以及自身提供的模版引擎等等。Prometheus 还提供 HTTP API 的查询方式，自定义所需要的输出。&lt;/li&gt;
&lt;li&gt;PushGateway 支持 Client 主动推送 metrics 到 PushGateway，而 Prometheus 只是定时去 Gateway 上抓取数据。&lt;/li&gt;
&lt;li&gt;Alertmanager 是独立于 Prometheus 的一个组件，可以支持 Prometheus 的查询语句，提供十分灵活的报警方式。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="operator"&gt;&lt;a href="#operator" class="header-anchor"&gt;&lt;/a&gt;Operator
&lt;/h3&gt;&lt;p&gt;Operator 是 CoreOS 推出的旨在简化复杂有状态应用管理的框架，它是一个感知应用状态的控制器，通过扩展 Kubernetes API 来自动创建、管理和配置应用实例。&lt;/p&gt;
&lt;p&gt;Operator 基于 CustomResourceDefinition(CRD) 扩展了新的应用资源，并通过控制器来保证应用处于预期状态。比如 etcd operator 通过下面的三个步骤模拟了管理 etcd 集群的行为：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;通过 Kubernetes API 观察集群的当前状态；&lt;/li&gt;
&lt;li&gt;分析当前状态与期望状态的差别；&lt;/li&gt;
&lt;li&gt;调用 etcd 集群管理 API 或 Kubernetes API 消除这些差别。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2021-11-15-kubernetes-jian-kong-ti-xi-zong-jie/005-15eafb88.jpg"&gt;&lt;/p&gt;
&lt;h3 id="prometheus-operator"&gt;&lt;a href="#prometheus-operator" class="header-anchor"&gt;&lt;/a&gt;Prometheus Operator
&lt;/h3&gt;&lt;p&gt;为了在 Kubernetes 能够方便的管理和部署 Prometheus，我们使用 ConfigMap 了管理 Prometheus 配置文件。每次对 Prometheus 配置文件进行升级时，我们需要手动移除已经运行的 Pod 实例，从而让 Kubernetes 可以使用最新的配置文件创建 Prometheus。而如果当应用实例的数量更多时，通过手动的方式部署和升级 Prometheus 过程繁琐并且效率低下。&lt;/p&gt;
&lt;p&gt;从本质上来讲 Prometheus 属于是典型的有状态应用，而其又包含了一些自身特有的运维管理和配置管理方式。而这些都无法通过 Kubernetes 原生提供的应用管理概念实现自动化。为了简化这类应用程序的管理复杂度，CoreOS 率先引入了 Operator 的概念，并且首先推出了针对在 Kubernetes 下运行和管理 Etcd 的 Etcd Operator。并随后推出了 Prometheus Operator。&lt;/p&gt;
&lt;p&gt;从概念上来讲 Operator 就是针对管理特定应用程序的，在 Kubernetes 基本的 Resource 和 Controller 的概念上，以扩展 Kubernetes api 的形式。帮助用户创建，配置和管理复杂的有状态应用程序。从而实现特定应用程序的常见操作以及运维自动化。&lt;/p&gt;
&lt;p&gt;在 Kubernetes 中我们使用 Deployment、DamenSet，StatefulSet 来管理应用 Workload，使用 Service，Ingress 来管理应用的访问方式，使用 ConfigMap 和 Secret 来管理应用配置。我们在集群中对这些资源的创建，更新，删除的动作都会被转换为事件 (Event)，Kubernetes 的 Controller Manager 负责监听这些事件并触发相应的任务来满足用户的期望。这种方式我们成为声明式，用户只需要关心应用程序的最终状态，其它的都通过 Kubernetes 来帮助我们完成，通过这种方式可以大大简化应用的配置管理复杂度。&lt;/p&gt;
&lt;p&gt;而除了这些原生的 Resource 资源以外，Kubernetes 还允许用户添加自己的自定义资源 (Custom Resource)。并且通过实现自定义 Controller 来实现对 Kubernetes 的扩展。&lt;/p&gt;
&lt;p&gt;如下所示，是 Prometheus Operator 的架构示意图：&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2021-11-15-kubernetes-jian-kong-ti-xi-zong-jie/006-69c36f88.jpg"&gt;&lt;/p&gt;
&lt;p&gt;Prometheus 的本职就是一组用户自定义的 CRD 资源以及 Controller 的实现，Prometheus Operator 负责监听这些自定义资源的变化，并且根据这些资源的定义自动化的完成如 Prometheus Server 自身以及配置的自动化管理工作。&lt;/p&gt;
&lt;p&gt;简言之，Prometheus Operator 能够帮助用户自动化的创建以及管理 Prometheus Server 以及其相应的配置。&lt;/p&gt;
&lt;h3 id="hpa"&gt;&lt;a href="#hpa" class="header-anchor"&gt;&lt;/a&gt;HPA
&lt;/h3&gt;&lt;p&gt;Horizontal Pod Autoscaler ，K8S 中的一个概念，可以自动调整 Pod 的数量，以达到指定的目标值。&lt;/p&gt;
&lt;p&gt;Pod 水平自动扩缩（Horizontal Pod Autoscaler） 可以基于 CPU 利用率自动扩缩 ReplicationController、Deployment、ReplicaSet 和 StatefulSet 中的 Pod 数量。除了 CPU 利用率，也可以基于其他应程序提供的 &lt;code&gt;自定义度量指标&lt;/code&gt;来执行自动扩缩。Pod 自动扩缩不适用于无法扩缩的对象，比如 DaemonSet。&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2021-11-15-kubernetes-jian-kong-ti-xi-zong-jie/007-8dbad0a8.jpg"&gt;&lt;/p&gt;
&lt;h3 id="heapster"&gt;&lt;a href="#heapster" class="header-anchor"&gt;&lt;/a&gt;Heapster
&lt;/h3&gt;&lt;p&gt;Heapster 是容器集群监控和性能分析工具，天然的支持 Kubernetes 和 CoreOS。&lt;/p&gt;
&lt;p&gt;Heapster 首先从 K8S Master 获取集群中所有 Node 的信息，然后通过这些 Node 上的 kubelet 获取有用数据，而 kubelet 本身的数据则是从 cAdvisor 得到。所有获取到的数据都被推到 Heapster 配置的后端存储中，并还支持数据的可视化。现在后端存储 + 可视化的方法，如 InfluxDB + grafana。&lt;/p&gt;
&lt;p&gt;Heapster 可以收集 Node 节点上的 cAdvisor 数据，还可以按照 kubernetes 的资源类型来集合资源，比如 Pod、Namespace 域，可以分别获取它们的 CPU、内存、网络和磁盘的 metric。默认的 metric 数据聚合时间间隔是 1 分钟。&lt;/p&gt;
&lt;p&gt;注意 ：Kubernetes 1.11 不建议使用 Heapster，就 SIG Instrumentation 而言，这是为了转向新的 Kubernetes 监控模型的持续努力的一部分。仍使用 Heapster 进行自动扩展的集群应迁移到 metrics-server 和自定义指标 API。&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2021-11-15-kubernetes-jian-kong-ti-xi-zong-jie/008-cd80d35d.jpg"&gt;&lt;/p&gt;
&lt;h3 id="metrics-server"&gt;&lt;a href="#metrics-server" class="header-anchor"&gt;&lt;/a&gt;Metrics Server
&lt;/h3&gt;&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2021-11-15-kubernetes-jian-kong-ti-xi-zong-jie/009-f72ed8af.jpg"&gt;&lt;/p&gt;
&lt;p&gt;kubernetes 集群资源监控之前可以通过 &lt;code&gt;heapster&lt;/code&gt; 来获取数据，在 &lt;code&gt;1.11&lt;/code&gt; 开始开始逐渐废弃 &lt;code&gt;heapster&lt;/code&gt; 了，采用 &lt;code&gt;metrics-server&lt;/code&gt; 来代替，&lt;code&gt;metrics-server&lt;/code&gt; 是集群的核心监控数据的聚合器，它从 kubelet 公开的 Summary API 中采集指标信息，&lt;code&gt;metrics-server&lt;/code&gt; 是扩展的 APIServer，依赖于 kube-aggregator，因为我们需要在 APIServer 中开启相关参数。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Metrics Server&lt;/code&gt; 并不是 kube-apiserver 的一部分，而是通过 Aggregator 这种插件机制，在独立部署的情况下同 kube-apiserver 一起统一对外服务的。&lt;/p&gt;
&lt;p&gt;Aggregator&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;“&lt;/p&gt;
&lt;p&gt;通过聚合层扩展 Kubernetes API使用聚合层（Aggregation Layer），用户可以通过额外的 API 扩展 Kubernetes， 而不局限于 Kubernetes 核心 API 提供的功能。这里的附加 API 可以是现成的解决方案比如 metrics server, 或者你自己开发的 API。聚合层不同于 定制资源（Custom Resources）。后者的目的是让 kube-apiserver 能够认识新的对象类别（Kind）。&lt;/p&gt;
&lt;p&gt;”&lt;/p&gt;

 &lt;/blockquote&gt;

 &lt;blockquote&gt;
 &lt;p&gt;“&lt;/p&gt;
&lt;p&gt;聚合层聚合层在 kube-apiserver 进程内运行。在扩展资源注册之前，聚合层不做任何事情。要注册 API，用户必须添加一个 APIService 对象，用它来“申领” Kubernetes API 中的 URL 路径。自此以后，聚合层将会把发给该 API 路径的所有内容（例如 /apis/myextension.mycompany.io/v1/…） 转发到已注册的 APIService。&lt;/p&gt;
&lt;p&gt;”&lt;/p&gt;

 &lt;/blockquote&gt;

 &lt;blockquote&gt;
 &lt;p&gt;“&lt;/p&gt;
&lt;p&gt;APIService 的最常见实现方式是在集群中某 Pod 内运行 扩展 API 服务器。如果你在使用扩展 API 服务器来管理集群中的资源，该扩展 API 服务器（也被写成“extension-apiserver”） 一般需要和一个或多个控制器一起使用。apiserver-builder 库同时提供构造扩展 API 服务器和控制器框架代码。&lt;/p&gt;
&lt;p&gt;”&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;这里，Aggregator APIServer 的工作原理，可以用如下所示的一幅示意图来表示清楚 ：&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2021-11-15-kubernetes-jian-kong-ti-xi-zong-jie/010-8e03d754.jpg"&gt;&lt;/p&gt;
&lt;p&gt;因为 k8s 的 api-server 将所有的数据持久化到了 etcd 中，显然 k8s 本身不能处理这种频率的采集，而且这种监控数据变化快且都是临时数据，因此需要有一个组件单独处理他们，于是 metric-server 的概念诞生了。&lt;/p&gt;
&lt;p&gt;Metrics server 出现后，新的 Kubernetes 监控架构将变成下图的样子&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;核心流程（黑色部分）：这是 Kubernetes 正常工作所需要的核心度量，从 Kubelet、cAdvisor 等获取度量数据，再由 metrics-server 提供给 Dashboard、HPA 控制器等使用。&lt;/li&gt;
&lt;li&gt;监控流程（蓝色部分）：基于核心度量构建的监控流程，比如 Prometheus 可以从 metrics-server 获取核心度量，从其他数据源（如 Node Exporter 等）获取非核心度量，再基于它们构建监控告警系统。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2021-11-15-kubernetes-jian-kong-ti-xi-zong-jie/011-c53d6287.jpg"&gt;&lt;/p&gt;
&lt;p&gt;注意：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;metrics-sevrer 的数据存在内存中。&lt;/li&gt;
&lt;li&gt;metrics-server 主要针对 node、pod 等的 cpu、网络、内存等系统指标的监控&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="kube-state-metrics"&gt;&lt;a href="#kube-state-metrics" class="header-anchor"&gt;&lt;/a&gt;kube-state-metrics
&lt;/h3&gt;&lt;p&gt;已经有了 cadvisor、heapster、metric-server，几乎容器运行的所有指标都能拿到，但是下面这种情况却无能为力：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;我调度了多少个 replicas？现在可用的有几个？&lt;/li&gt;
&lt;li&gt;多少个 Pod 是 running/stopped/terminated 状态？&lt;/li&gt;
&lt;li&gt;Pod 重启了多少次？&lt;/li&gt;
&lt;li&gt;我有多少 job 在运行中&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;而这些则是 kube-state-metrics 提供的内容，它基于 client-go 开发，轮询 Kubernetes API，并将 Kubernetes 的结构化信息转换为 metrics。&lt;/p&gt;
&lt;p&gt;kube-state-metrics 与 metrics-server 对比&lt;/p&gt;
&lt;p&gt;我们服务在运行过程中，我们想了解服务运行状态，pod 有没有重启，伸缩有没有成功，pod 的状态是怎么样的等，这时就需要 kube-state-metrics，它主要关注 deployment,、node 、 pod 等内部对象的状态。而 metrics-server 主要用于监测 node，pod 等的 CPU，内存，网络等系统指标。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;metric-server（或 heapster）是从 api-server 中获取 cpu、内存使用率这种监控指标，并把他们发送给存储后端，如 influxdb 或云厂商，他当前的核心作用是：为 HPA 等组件提供决策指标支持。&lt;/li&gt;
&lt;li&gt;kube-state-metrics 关注于获取 k8s 各种资源的最新状态，如 deployment 或者 daemonset，之所以没有把 kube-state-metrics 纳入到 metric-server 的能力中，是因为他们的关注点本质上是不一样的。metric-server 仅仅是获取、格式化现有数据，写入特定的存储，实质上是一个监控系统。而 kube-state-metrics 是将 k8s 的运行状况在内存中做了个快照，并且获取新的指标，但他没有能力导出这些指标&lt;/li&gt;
&lt;li&gt;换个角度讲，kube-state-metrics 本身是 metric-server 的一种数据来源，虽然现在没有这么做。&lt;/li&gt;
&lt;li&gt;另外，像 Prometheus 这种监控系统，并不会去用 metric-server 中的数据，他都是自己做指标收集、集成的（Prometheus 包含了 metric-server 的能力），但 Prometheus 可以监控 metric-server 本身组件的监控状态并适时报警，这里的监控就可以通过 kube-state-metrics 来实现，如 metric-serverpod 的运行状态。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2021-11-15-kubernetes-jian-kong-ti-xi-zong-jie/012-0394707a.jpg"&gt;&lt;/p&gt;
&lt;h3 id="custom-metrics-apiserver"&gt;&lt;a href="#custom-metrics-apiserver" class="header-anchor"&gt;&lt;/a&gt;custom-metrics-apiserver
&lt;/h3&gt;&lt;p&gt;kubernetes 的监控指标分为两种&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Core metrics（核心指标）：从 Kubelet、cAdvisor 等获取度量数据，再由 metrics-server 提供给 Dashboard、HPA 控制器等使用。&lt;/li&gt;
&lt;li&gt;Custom Metrics（自定义指标）：由 Prometheus Adapter 提供 API custom.metrics.k8s.io，由此可支持任意 Prometheus 采集到的指标。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;以下是官方 metrics 的项目介绍：&lt;/p&gt;
&lt;p&gt;Resource Metrics API（核心 api）&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Heapster&lt;/li&gt;
&lt;li&gt;Metrics Server&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Custom Metrics API：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Prometheus Adapter&lt;/li&gt;
&lt;li&gt;Microsoft Azure Adapter&lt;/li&gt;
&lt;li&gt;Google Stackdriver&lt;/li&gt;
&lt;li&gt;Datadog Cluster Agent&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;核心指标只包含 node 和 pod 的 cpu、内存等，一般来说，核心指标作 HPA 已经足够，但如果想根据自定义指标：如请求 qps/5xx 错误数来实现 HPA，就需要使用自定义指标了，目前 Kubernetes 中自定义指标一般由 Prometheus 来提供，再利用 &lt;code&gt;k8s-prometheus-adpater&lt;/code&gt; 聚合到 apiserver，实现和核心指标（metric-server) 同样的效果。&lt;/p&gt;
&lt;p&gt;HPA 请求 metrics 时，kube-aggregator(apiservice 的 controller) 会将请求转发到 adapter，adapter 作为 kubernentes 集群的 pod，实现了 Kubernetes resource metrics API 和 custom metrics API，它会根据配置的 rules 从 Prometheus 抓取并处理 metrics，在处理（如重命名 metrics 等）完后将 metric 通过 custom metrics API 返回给 HPA。最后 HPA 通过获取的 metrics 的 value 对 Deployment/ReplicaSet 进行扩缩容。&lt;/p&gt;
&lt;p&gt;adapter 作为 extension-apiserver（即自己实现的 pod)，充当了代理 kube-apiserver 请求 Prometheus 的功能。&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2021-11-15-kubernetes-jian-kong-ti-xi-zong-jie/013-bee9d39d.jpg"&gt;&lt;/p&gt;
&lt;p&gt;其实 k8s-prometheus-adapter 既包含自定义指标，又包含核心指标，即如果安装了 prometheus，且指标都采集完整，k8s-prometheus-adapter 可以替代 metrics server。&lt;/p&gt;
&lt;h2 id="prometheus-部署方案"&gt;&lt;a href="#prometheus-%e9%83%a8%e7%bd%b2%e6%96%b9%e6%a1%88" class="header-anchor"&gt;&lt;/a&gt;Prometheus 部署方案
&lt;/h2&gt;&lt;p&gt;prometheus operator&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/prometheus-operator/prometheus-operator" target="_blank" rel="noopener"
 &gt;https://github.com/prometheus-operator/prometheus-operator&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;kube-prometheus&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/prometheus-operator/kube-prometheus" target="_blank" rel="noopener"
 &gt;https://github.com/prometheus-operator/kube-prometheus&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在集群外部署&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://www.qikqiak.com/post/monitor-external-k8s-on-prometheus/" target="_blank" rel="noopener"
 &gt;https://www.qikqiak.com/post/monitor-external-k8s-on-prometheus/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;kube-prometheus 既包含了 Operator，又包含了 Prometheus 相关组件的部署及常用的 Prometheus 自定义监控，具体包含下面的组件&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The Prometheus Operator：创建 CRD 自定义的资源对象&lt;/li&gt;
&lt;li&gt;Highly available Prometheus：创建高可用的 Prometheus&lt;/li&gt;
&lt;li&gt;Highly available Alertmanager：创建高可用的告警组件&lt;/li&gt;
&lt;li&gt;Prometheus node-exporter：创建主机的监控组件&lt;/li&gt;
&lt;li&gt;Prometheus Adapter for Kubernetes Metrics APIs：创建自定义监控的指标工具（例如可以通过 nginx 的 request 来进行应用的自动伸缩）&lt;/li&gt;
&lt;li&gt;kube-state-metrics：监控 k8s 相关资源对象的状态指标&lt;/li&gt;
&lt;li&gt;Grafana：进行图像展示&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="我们的做法"&gt;&lt;a href="#%e6%88%91%e4%bb%ac%e7%9a%84%e5%81%9a%e6%b3%95" class="header-anchor"&gt;&lt;/a&gt;我们的做法
&lt;/h2&gt;&lt;p&gt;我们的做法，其实跟 kube-prometheus 的思路差不多，只不过我们没有用 Operator ，是自己将以下这些组件的 yaml 文件用 helm 组织了起来而已：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;kube-state-metrics&lt;/li&gt;
&lt;li&gt;prometheus&lt;/li&gt;
&lt;li&gt;alertmanager&lt;/li&gt;
&lt;li&gt;grafana&lt;/li&gt;
&lt;li&gt;k8s-prometheus-adapter&lt;/li&gt;
&lt;li&gt;node-exporter&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;当然 kube-prometheus 也有 helm charts 由 prometheus 社区提供：https://github.com/prometheus-community/helm-charts/tree/main/charts/kube-prometheus-stack&lt;/p&gt;
&lt;p&gt;这么干的原因是：这样的灵活度是最高的，虽然在第一次初始化创建这些脚本的时候麻烦了些。不过还有一个原因是我们当时部署整个基于 prometheus 的监控体系时，kube-prometheus 这个项目还在早期，没有引起我们的关注。如果在 2021 年年初或 2020 年年底的时候创建的话，可能就会直接上了。&lt;/p&gt;
&lt;h2 id="参考"&gt;&lt;a href="#%e5%8f%82%e8%80%83" class="header-anchor"&gt;&lt;/a&gt;参考
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://blog.opskumu.com/cadvisor.html" target="_blank" rel="noopener"
 &gt;https://blog.opskumu.com/cadvisor.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://prometheus.io/" target="_blank" rel="noopener"
 &gt;https://prometheus.io/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://kubernetes.io/zh/docs/tasks/run-application/horizontal-pod-autoscale/" target="_blank" rel="noopener"
 &gt;https://kubernetes.io/zh/docs/tasks/run-application/horizontal-pod-autoscale/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.cnblogs.com/chenqionghe/p/10494868.html" target="_blank" rel="noopener"
 &gt;https://www.cnblogs.com/chenqionghe/p/10494868.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.qikqiak.com/post/k8s-operator-101/" target="_blank" rel="noopener"
 &gt;https://www.qikqiak.com/post/k8s-operator-101/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://kubernetes.io/zh/docs/concepts/extend-kubernetes/api-extension/apiserver-aggregation/" target="_blank" rel="noopener"
 &gt;https://kubernetes.io/zh/docs/concepts/extend-kubernetes/api-extension/apiserver-aggregation/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://segmentfault.com/a/1190000017875641" target="_blank" rel="noopener"
 &gt;https://segmentfault.com/a/1190000017875641&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://segmentfault.com/a/1190000038888544" target="_blank" rel="noopener"
 &gt;https://segmentfault.com/a/1190000038888544&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://yasongxu.gitbook.io/" target="_blank" rel="noopener"
 &gt;https://yasongxu.gitbook.io/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://mp.weixin.qq.com/s/p4FAFKHi8we4mrD7OIk7IQ" target="_blank" rel="noopener"
 &gt;https://mp.weixin.qq.com/s/p4FAFKHi8we4mrD7OIk7IQ&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://kubernetes.feisky.xyz/apps/index/operator" target="_blank" rel="noopener"
 &gt;https://kubernetes.feisky.xyz/apps/index/operator&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://yunlzheng.gitbook.io/prometheus-book/part-iii-prometheus-shi-zhan/operator/what-is-prometheus-operator" target="_blank" rel="noopener"
 &gt;https://yunlzheng.gitbook.io/prometheus-book/part-iii-prometheus-shi-zhan/operator/what-is-prometheus-operator&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item></channel></rss>