标签

MariaDB

MariaDB 是一个开源数据库服务器,为 MySQL 提供直接替换功能。

MariaDB
查看更多相关内容
服务端2026年5月27日 18:26
MariaDB 如何进行索引优化?有哪些索引类型和优化策略?## MariaDB 有哪些索引类型?各自的适用场景是什么? MariaDB 支持多种索引类型,理解它们的区别是做优化决策的基础。 **B-Tree 索引**是 MariaDB 的默认索引结构,绝大多数场景下使用的都是它。B-Tree 采用平衡多路搜索树结构,叶子节点通过双向链表连接,天然支持等值查询、范围查询和排序操作。当你执行 `WHERE id BETWEEN 100 AND 200` 或 `ORDER BY created_at DESC` 时,B-Tree 可以高效地利用索引的有序性完成扫描,而不需要额外的文件排序(filesort)。 **哈希索引**仅支持等值匹配查询,不支持范围查询和排序。它的查询时间复杂度接近 O(1),在精确查找场景下比 B-Tree 更快。MariaDB 中哈希索引主要用于 MEMORY 存储引擎的表,InnoDB 的自适应哈希索引(Adaptive Hash Index)是引擎内部自动维护的,不需要手动创建。面试中如果被问到"哈希索引为什么不能用于范围查询",核心原因是哈希值之间没有大小关系,无法利用有序性做区间扫描。 **全文索引(FULLTEXT)**专门用于文本内容的模糊搜索,底层基于倒排索引实现。相比于 `LIKE '%关键词%'` 会导致全表扫描,全文索引可以快速定位包含目标词的记录。MariaDB 支持 `MATCH ... AGAINST` 语法,提供自然语言模式和布尔模式两种查询方式。需要注意的是,全文索引对中文分词的支持有限,通常需要借助 ngram 解析器或 Mroonga 引擎来处理中文场景。 **空间索引(SPATIAL)**用于地理空间数据类型的索引,底层基于 R-Tree 结构。适合处理点、线、多边形等 GIS 数据的空间关系查询,比如"查找某坐标 5 公里范围内的门店"。空间索引仅支持 InnoDB 和 MyISAM 引擎,且索引列必须声明为 `NOT NULL`。 **聚簇索引**不是一个独立的索引类型,而是 InnoDB 的数据组织方式。InnoDB 的主键索引就是聚簇索引——叶子节点直接存储完整的行数据,而非主键索引(二级索引)的叶子节点存储的是主键值。这意味着通过二级索引查找数据时,需要先查到主键值,再回表查询完整行数据,这个过程叫做"回表"。理解聚簇索引和回表机制,是掌握覆盖索引优化前提。 ## 创建索引时应该遵循哪些设计原则? 索引不是越多越好。每多一个索引,INSERT/UPDATE/DELETE 就多一份维护成本,同时占用额外的磁盘空间。设计索引时需要把握几个关键原则。 **优先对高选择性列建索引。** 选择性指的是列中不同值的数量与总行数的比值。选择性越高,索引过滤效果越好。例如用户表的 email 列选择性接近 1,几乎每条记录值都不同,索引过滤效率极高;而性别列只有两三个值,选择性极低,索引对查询的帮助微乎其微,优化器大概率会选择全表扫描。一个经验阈值是:当某值占比超过全表的 20% 时,优化器通常放弃使用索引。 **复合索引要遵循最左前缀原则。** 对于索引 `idx_abc(a, b, c)`,查询条件用到了 a、(a,b)、(a,b,c) 都能命中索引,但只用 b 或 c 则无法使用。实际设计中,应该把等值查询的列放在前面,范围查询的列放在后面。例如 `WHERE status = 1 AND created_at > '2024-01-01'`,应建索引 `(status, created_at)` 而非 `(created_at, status)`,因为等值过滤在前可以大幅缩小范围查询的扫描区间。 **利用覆盖索引减少回表。** 如果查询需要的所有列都包含在索引中,InnoDB 直接从索引返回数据,无需回表读取行记录。例如 `SELECT id, name FROM users WHERE name = 'John'`,如果 name 列上有索引且索引包含了 id(InnoDB 二级索引自动包含主键),这就是一次覆盖索引扫描。在 EXPLAIN 输出中,覆盖索引的 Extra 列会显示 `Using index`。 **避免在索引列上使用函数或表达式。** `WHERE YEAR(created_at) = 2024` 会导致索引失效,因为 MariaDB 需要对每一行计算函数值后才能比较。正确的写法是 `WHERE created_at >= '2024-01-01' AND created_at < '2025-01-01'`,这样优化器可以利用 B-Tree 的有序性做范围扫描。 ## 如何使用 EXPLAIN 分析查询的索引使用情况? EXPLAIN 是索引优化的核心工具,它展示优化器为查询选择的执行计划。 ```sql EXPLAIN SELECT u.name, o.total FROM users u JOIN orders o ON u.id = o.user_id WHERE u.status = 1 AND o.created_at > '2024-01-01'; ``` 重点关注以下几个字段: - **type**:访问类型,从好到差依次为 `system > const > eq_ref > ref > range > index > ALL`。出现 `ALL` 意味着全表扫描,需要重点优化。`ref` 表示使用索引等值匹配,`range` 表示索引范围扫描。 - **key**:实际使用的索引名称。如果显示 NULL,说明没有可用索引。 - **rows**:预估扫描行数。这个值越小越好,但它是基于统计信息的估算值,不一定精确。 - **Extra**:额外信息。`Using index` 表示覆盖索引,`Using filesort` 表示需要额外排序,`Using temporary` 表示使用了临时表,后两者通常意味着性能瓶颈。 一个实用的工作流是:先跑 EXPLAIN 看执行计划,发现 type 为 ALL 或 rows 过大时,针对性地添加或调整索引,再反复验证。 ## 什么是索引失效?哪些常见写法会导致索引失效? 索引失效指的是查询条件虽然涉及了索引列,但优化器最终选择不使用索引而做全表扫描。以下几种写法是常见的索引失效陷阱。 **对索引列使用函数或运算:** `WHERE name LIKE '%John'`(左模糊)、`WHERE YEAR(date_col) = 2024`、`WHERE id + 1 = 100`,这些写法破坏了 B-Tree 的有序性,优化器无法利用索引定位。 **隐式类型转换:** 当列是 VARCHAR 类型,查询条件写成 `WHERE phone = 13800001111`(数字类型),MariaDB 会将列值转为数字再做比较,这相当于对列施加了隐式函数,导致索引失效。正确写法是 `WHERE phone = '13800001111'`。 **OR 条件连接不同索引列:** `WHERE name = 'John' OR age = 25`,如果 name 和 age 各有独立索引,MariaDB 在某些情况下可以使用 Index Merge 优化,但效果往往不如预期,不如改写为 UNION ALL 两个子查询。 **NOT IN、NOT EXISTS、!=、<>:** 这些否定条件可能导致索引失效,尤其是结果集占比较大时。但并非绝对——如果否定条件过滤性很强(排除的值很少),优化器仍可能选择索引。 **索引列 IS NULL:** 在 MariaDB 中,B-Tree 索引是包含 NULL 值的,`WHERE col IS NULL` 可以使用索引。这一点与 Oracle 等数据库不同,面试中注意区分。 ## 如何通过覆盖索引和索引下推提升查询性能? **覆盖索引**在前文已经提到,核心思路是让查询所需的所有列都在索引中,从而避免回表。实际应用中,可以通过 SELECT 指定列或将常用查询列加入复合索引来实现。 ```sql -- 订单列表查询,只需要 id、status、created_at SELECT id, status, created_at FROM orders WHERE user_id = 100; -- 建立覆盖索引 (user_id, status, created_at) -- InnoDB 二级索引自动包含主键 id,因此这三列 + id 都在索引中 ALTER TABLE orders ADD INDEX idx_user_status_created(user_id, status, created_at); ``` **索引下推(Index Condition Pushdown,ICP)** 是 MariaDB 5.6+ 引入的优化。传统流程中,二级索引查到主键后必须回表才能判断 WHERE 中的其他条件;启用 ICP 后,存储引擎在索引扫描阶段就根据 WHERE 条件过滤,减少回表次数。 ```sql -- 假设有索引 (last_name, first_name) SELECT * FROM users WHERE last_name = 'Smith' AND first_name LIKE '%ohn'; -- 没有 ICP:先通过 last_name 索引查到所有 Smith 的主键,逐个回表再过滤 first_name -- 有 ICP:在索引扫描时直接对 first_name 做 LIKE 判断,不满足的跳过,减少回表 ``` 在 EXPLAIN 的 Extra 列中,ICP 会显示 `Using index condition`。ICP 的适用条件是:查询使用了复合索引,且 WHERE 中有索引前列的等值条件加上后续列的条件过滤。 ## 如何监控和维护索引的健康状态? 索引不是建完就一劳永逸的,随着数据增删改,索引可能出现碎片化、统计信息过期等问题,需要定期维护。 ```sql -- 查看表的索引信息 SHOW INDEX FROM users; -- 查看索引统计信息 SELECT INDEX_NAME, SEQ_IN_INDEX, COLUMN_NAME, CARDINALITY FROM information_schema.STATISTICS WHERE TABLE_SCHEMA = 'your_db' AND TABLE_NAME = 'users'; -- 更新表的统计信息(不锁表) ANALYZE TABLE users; -- 重建表以消除碎片(会锁表) OPTIMIZE TABLE users; ``` **识别无用索引:** 可以通过 `sys.schema_unused_indexes` 视图(MariaDB 10.6+)或开启 `userstat` 插件来追踪索引使用情况。长期未使用的索引应该清理,减少写入开销。 **监控索引碎片:** 频繁的增删改会导致索引页出现空洞,降低索引扫描效率。`OPTIMIZE TABLE` 会重建表和索引,消除碎片,但操作期间会锁表,建议在低峰期执行。对于大表,可以考虑使用 `ALTER TABLE ... ENGINE=InnoDB` 的方式在线重建。 **统计信息维护:** 优化器依赖统计信息(cardinality、rows 等)来选择执行计划。如果统计信息严重失真,可能导致优化器选错索引。定期执行 `ANALYZE TABLE` 可以刷新统计信息,且在 MariaDB 10.4+ 中该操作是在线进行的,不会阻塞读写。 ## MariaDB 索引优化有哪些常见的实战案例? **案例一:分页查询优化** 深分页是典型的性能杀手。`SELECT * FROM orders ORDER BY id LIMIT 100000, 10` 需要先扫描 100010 行再丢弃前 100000 行。 ```sql -- 方案一:游标分页(推荐) -- 前端记录上一页最后一条的 id,下一页查询时带上 SELECT * FROM orders WHERE id > 100000 ORDER BY id LIMIT 10; -- 方案二:延迟关联 -- 先通过子查询在索引上定位 id,再回表取数据 SELECT o.* FROM orders o JOIN (SELECT id FROM orders ORDER BY id LIMIT 100000, 10) t ON o.id = t.id; ``` **案例二:多条件组合查询** `SELECT * FROM products WHERE category_id = 5 AND status = 1 AND price BETWEEN 100 AND 500 ORDER BY sales_count DESC LIMIT 20;` 索引设计:`(category_id, status, sales_count)`。前两列做等值过滤缩小范围,第三列利用索引有序性避免 filesort。price 列的范围查询放在最后处理。 **案例三:大表 JOIN 优化** ```sql SELECT o.id, u.name FROM orders o JOIN users u ON o.user_id = u.id WHERE o.status = 2 AND u.region = 'CN'; ``` 确保 JOIN 条件列(user_id、id)有索引,同时 orders 表在 status 上建索引、users 表在 region 上建索引,使驱动表的过滤结果尽可能小,减少循环 JOIN 的次数。 掌握以上索引类型、设计原则、分析工具和实战技巧,基本能应对 MariaDB 索引优化的大部分面试问题和线上场景。核心思路始终是:用 EXPLAIN 验证,让索引覆盖查询,减少回表和全表扫描。
服务端2026年5月27日 18:24
MariaDB 的 JSON 函数怎么用?有哪些常见坑?MariaDB 从 10.2 开始提供了一套 JSON 函数,能直接在 SQL 里创建、查询、修改和校验 JSON 数据。JSON 列本质是 LONGTEXT 加 CHECK 约束,不是 MySQL 那种二进制格式,这一点在迁移时容易踩坑。 ## 追问 ### JSON_EXTRACT、JSON_VALUE、JSON_QUERY 有什么区别? 三个都是取值,但返回类型不同: | 函数 | 返回值 | 示例 | |------|--------|------| | JSON_EXTRACT | 原始 JSON 片段(带引号) | `"John"` | | JSON_VALUE | 标量值(去引号) | `John` | | JSON_QUERY | 对象或数组 | `{"city":"NY"}` | 日常取字符串值用 `->` 操作符(JSON_EXTRACT 的语法糖),取标量用 JSON_VALUE,取嵌套对象用 JSON_QUERY。 ### JSON_SET、JSON_INSERT、JSON_REPLACE 有什么区别? - **JSON_SET**:存在则更新,不存在则插入——万能选手 - **JSON_INSERT**:只在路径不存在时插入,已有值不动 - **JSON_REPLACE**:只在路径已存在时替换,没找到就跳过 记住一句话:不确定用 SET,只想加新字段用 INSERT,只想改旧字段用 REPLACE。 ### JSON 列怎么加索引? JSON 列不能直接建普通索引。两种方式: 1. **生成列 + 索引**(推荐): ```sql ALTER TABLE products ADD COLUMN brand VARCHAR(50) GENERATED ALWAYS AS (JSON_UNQUOTE(JSON_EXTRACT(attributes, '$.brand'))) STORED, ADD INDEX idx_brand(brand); ``` 2. **函数索引**(MariaDB 10.3+): ```sql CREATE INDEX idx_brand ON products((CAST(attributes->'$.brand' AS CHAR(50)))); ``` ### MariaDB 的 JSON 和 MySQL 的 JSON 有什么区别? 这是迁移时最容易翻车的地方: | 对比项 | MariaDB | MySQL | |--------|---------|-------| | 存储格式 | 原文 LONGTEXT | 二进制 JSON | | JSON 类型 | LONGTEXT 的别名 | 独立数据类型 | | 自动校验 | 需要 CHECK 约束 | 内置校验 | | 部分更新 | 不支持 | 支持二进制增量更新 | MariaDB 存原文的好处是可以直接用文本函数处理,坏处是每次修改整个字段重写,大 JSON 字段更新性能差。 ### 用 JSON 列存数据有什么坑? 1. **没有 schema 约束**:同列不同行结构可以完全不同,查出来才知道长什么样,排查问题靠蒙 2. **查询性能**:每次取值都要解析 JSON,高频查询字段务必抽成普通列加索引 3. **更新代价**:改一个字段整个 JSON 重写,大文档更新慢 4. **CHECK 约束别忘了加**: ```sql CREATE TABLE products ( id INT PRIMARY KEY, attrs JSON, CONSTRAINT chk_json CHECK (JSON_VALID(attrs)) ); ``` ## 写段代码 ```sql -- 建表 + 插入 + 查询一条龙 CREATE TABLE products ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(100), attrs JSON CHECK (JSON_VALID(attrs)) ); INSERT INTO products (name, attrs) VALUES ('Laptop', '{"brand":"Dell","ram":"16GB"}'); -- 查品牌、改内存 SELECT name, attrs->'$.brand' AS brand FROM products; UPDATE products SET attrs = JSON_SET(attrs, '$.ram', '32GB') WHERE id = 1; ```
服务端2026年5月27日 17:38
如何优化MariaDB查询性能?查询性能是数据库系统的生命线。一条低效的SQL可能拖垮整个应用,而一次精准的优化能让响应时间从秒级降到毫秒级。这篇文章从诊断、索引、写法、配置四个层面,给出经过生产验证的优化方法。 ## 用 EXPLAIN 定位性能瓶颈 优化之前,先要找到问题。EXPLAIN 是最直接的诊断工具: ```sql EXPLAIN SELECT * FROM orders WHERE user_id = 100 AND status = 'paid'; ``` 输出中有四个字段值得重点关注: - **type** — 访问类型,从差到优依次为 ALL → index → range → ref → eq_ref → const。出现 ALL 意味着全表扫描,必须优化 - **key** — 实际使用的索引。如果为 NULL,说明索引未被命中 - **rows** — 预估扫描行数。数字越大,查询越慢 - **Extra** — Using filesort 表示额外排序,Using temporary 表示使用了临时表,两者都应尽量避免 一个简单的判断标准:type 不是 ALL 且 Extra 没有 Using filesort/Using temporary,查询基本合格。 ## 索引:最有效的加速手段 ### 建立合适的复合索引 单列索引在多条件查询时往往不够用。复合索引遵循最左前缀原则,把区分度高的列放前面: ```sql -- 假设查询条件为 WHERE user_id = ? AND status = ? -- user_id 区分度远高于 status,放前面 CREATE INDEX idx_user_status ON orders(user_id, status); ``` ### 用覆盖索引避免回表 当查询的列全部包含在索引中时,引擎无需回表读取数据行,性能提升显著: ```sql -- 索引 idx_user_status(user_id, status) 无法覆盖此查询(需要 amount 列) SELECT user_id, status, amount FROM orders WHERE user_id = 100; -- 建立覆盖索引后,直接从索引读取所有数据 CREATE INDEX idx_user_status_amount ON orders(user_id, status, amount); ``` ### 避免索引失效的常见写法 以下写法会导致索引无法命中: - 对索引列使用函数:`WHERE YEAR(created_at) = 2025` 改为 `WHERE created_at >= '2025-01-01' AND created_at < '2026-01-01'` - 隐式类型转换:`WHERE varchar_col = 123` 改为 `WHERE varchar_col = '123'` - 前缀模糊查询:`WHERE name LIKE '%John'` 改为 `WHERE name LIKE 'John%'` - 使用 OR 连接不同索引列:改用 UNION ALL 拆分 ## 查询写法的优化技巧 ### 只查需要的列 SELECT * 是性能杀手。它强制读取所有列的数据,增加 I/O 和内存开销,还可能破坏覆盖索引: ```sql -- 不推荐 SELECT * FROM users WHERE id = 1; -- 推荐:只查业务需要的列 SELECT id, name, email FROM users WHERE id = 1; ``` ### 用 JOIN 替代子查询 MariaDB 优化器对子查询的处理不如 JOIN 高效,特别是 IN 子查询: ```sql -- 不推荐 SELECT * FROM users WHERE id IN (SELECT user_id FROM orders WHERE amount > 1000); -- 推荐 SELECT u.id, u.name, u.email FROM users u INNER JOIN orders o ON u.id = o.user_id WHERE o.amount > 1000; ``` ### UNION ALL 替代 UNION UNION 会对结果去重,需要额外的排序操作。如果确定结果集无重复,用 UNION ALL 省掉去重开销: ```sql -- 不需要去重时 SELECT name FROM customers WHERE region = 'east' UNION ALL SELECT name FROM suppliers WHERE region = 'east'; ``` ### 深分页的两种优化方案 OFFSET 值很大时,数据库需要扫描并跳过前面的所有行: ```sql -- 传统写法:跳过 10 万行,极其缓慢 SELECT * FROM orders ORDER BY id LIMIT 100000, 10; -- 方案一:游标分页(要求排序字段连续且有索引) SELECT * FROM orders WHERE id > 100000 ORDER BY id LIMIT 10; -- 方案二:延迟关联(先查主键再回表,减少扫描列数) SELECT o.* FROM orders o INNER JOIN (SELECT id FROM orders ORDER BY id LIMIT 100000, 10) tmp ON o.id = tmp.id; ``` ### JOIN 优化 - 被驱动表的连接列必须有索引 - 小结果集驱动大表,减少循环次数 - 当优化器选错连接顺序时,用 STRAIGHT_JOIN 强制指定: ```sql SELECT * FROM small_table s STRAIGHT_JOIN large_table l ON s.id = l.small_id; ``` ## 配置层面的调优 ### InnoDB 缓冲池 这是影响 InnoDB 性能最重要的参数,建议设为物理内存的 50%-70%: ```ini innodb_buffer_pool_size = 4G innodb_buffer_pool_instances = 4 ``` ### 排序和连接缓冲 ```ini sort_buffer_size = 4M -- 每个连接的排序缓冲 join_buffer_size = 4M -- 每个无索引连接的缓冲 read_rnd_buffer_size = 4M -- MRR 读取缓冲 ``` ### 临时表大小 ```ini tmp_table_size = 256M max_heap_table_size = 256M ``` 超过此大小的临时表会写到磁盘,导致性能骤降。 ### 关于查询缓存 注意:MariaDB 10.6 起默认禁用查询缓存,后续版本已移除该功能。如果使用 10.6+,不要配置 query_cache_size,而是关注应用层缓存(如 Redis)。 ## 监控慢查询 开启慢查询日志,定期分析并优化: ```sql SET GLOBAL slow_query_log = ON; SET GLOBAL long_query_time = 1; -- 超过 1 秒记录 SET GLOBAL log_queries_not_using_indexes = ON; -- 记录未使用索引的查询 ``` 结合 pt-query-digest 工具分析慢查询日志,找出最需要优化的 SQL: ```bash pt-query-digest /var/lib/mysql/slow.log ``` ## 优化决策路径 面对一个慢查询,按以下顺序排查: 1. 先用 EXPLAIN 查看执行计划,确认是否走了索引 2. 如果走了索引仍然慢,考虑建立覆盖索引或调整索引列顺序 3. 如果索引没有问题,检查查询写法是否有优化空间(避免 SELECT *、子查询改 JOIN、深分页优化) 4. 如果单条 SQL 已最优,考虑配置调优(缓冲池、排序缓冲、临时表大小) 5. 配置也调不动了,考虑架构层面优化(读写分离、分库分表、引入缓存) 每个阶段都有明确的检查点和动作,避免盲目调参。
服务端2026年5月27日 15:06
MariaDB 的事务隔离级别如何工作?怎样根据业务场景选择合适的隔离级别?## 事务隔离级别要解决什么问题 多个事务并发执行时,如果不加任何隔离措施,会出现三类数据不一致的问题: - **脏读(Dirty Read)**:事务 A 读到了事务 B 尚未提交的数据。如果事务 B 回滚,事务 A 拿到的就是根本不存在的"脏数据"。 - **不可重复读(Non-Repeatable Read)**:事务 A 两次读取同一行数据,中间事务 B 修改并提交了这行,导致两次读到的值不同。 - **幻读(Phantom Read)**:事务 A 两次执行相同的范围查询,中间事务 B 插入了新行并提交,导致第二次查询多出了"幻影行"。 这三类问题逐层递进:脏读是读到了未提交的修改,不可重复读是已提交的修改导致同一行前后不一致,幻读是已提交的新增导致行数变化。SQL 标准据此定义了四种隔离级别,每种级别禁止一部分问题。 ## 四种隔离级别 ### READ UNCOMMITTED(读未提交) 最低隔离级别,允许事务读取其他事务未提交的修改。在这个级别下,脏读、不可重复读、幻读都可能发生。实际业务中几乎不会使用——读到未提交的数据意味着可能基于错误数据做出决策,风险极高。 ### READ COMMITTED(读已提交) 只允许读取已经提交的数据,杜绝了脏读。但同一事务内两次读取同一行,可能因为其他事务的提交而得到不同结果,所以不可重复读和幻读仍然存在。 Oracle 和 PostgreSQL 默认使用这个级别。如果你的业务对同一事务内数据一致性要求不高(比如报表查询、大多数 Web 应用的读操作),READ COMMITTED 是一个性能和正确性的折中选择。 ### REPEATABLE READ(可重复读) 保证同一事务内多次读取同一行的结果一致,杜绝了脏读和不可重复读。按照 SQL 标准,幻读在这个级别仍然可能发生。但 MariaDB/MySQL 的 InnoDB 引擎通过 MVCC 和 Gap Lock 机制,在 REPEATABLE READ 下也避免了幻读——这比 SQL 标准更严格。 MariaDB 和 MySQL 的默认隔离级别就是 REPEATABLE READ。大多数 OLTP 场景不需要改动它。 ### SERIALIZABLE(串行化) 最高隔离级别,所有事务按顺序串行执行,完全杜绝脏读、不可重复读和幻读。实现方式是对所有读取的行加共享锁,其他事务无法修改这些行直到锁释放。 性能代价极大——并发度几乎归零。只在对数据一致性有极端要求的场景下使用,比如金融对账、审计等。 ## 隔离级别与并发问题的对应关系 | 隔离级别 | 脏读 | 不可重复读 | 幻读 | |---|---|---|---| | READ UNCOMMITTED | 可能 | 可能 | 可能 | | READ COMMITTED | 不会 | 可能 | 可能 | | REPEATABLE READ | 不会 | 不会 | 可能(SQL 标准)/ 不会(MariaDB InnoDB) | | SERIALIZABLE | 不会 | 不会 | 不会 | ## MVCC 是怎么工作的 MVCC(Multi-Version Concurrency Control,多版本并发控制)是 InnoDB 实现 REPEATABLE READ 和 READ COMMITTED 的核心机制。它的基本思路是:每行数据保留多个版本,读操作访问的是某个历史快照,写操作创建新版本,读写互不阻塞。 InnoDB 在每行记录后添加两个隐藏列: - **DB_TRX_ID**:最后修改该行的事务 ID。 - **DB_ROLL_PTR**:回滚指针,指向 undo log 中该行的前一个版本。 每个事务开始时会获得一个递增的事务 ID。读取数据时,InnoDB 根据当前事务 ID 和 undo log 链构建一个一致性视图(Read View),只返回对当前事务可见的版本。 MVCC 在两个隔离级别下的行为差异: - **REPEATABLE READ**:事务第一次读取时创建 Read View,整个事务期间复用同一个 View,所以同一行数据多次读取结果一致。 - **READ COMMITTED**:每次 SELECT 都创建新的 Read View,所以能看到其他事务已提交的最新数据。 这就是为什么 READ COMMITTED 下会出现不可重复读,而 REPEATABLE READ 不会——Read View 的创建时机不同。 ## Gap Lock 与 Next-Key Lock MVCC 解决了快照读(普通 SELECT)的幻读问题,但当前读(SELECT ... FOR UPDATE、UPDATE、DELETE 等加锁读)怎么办?InnoDB 的答案是 Gap Lock 和 Next-Key Lock。 - **Record Lock**:锁定索引上的单条记录。 - **Gap Lock**:锁定两条记录之间的间隙,阻止其他事务在该间隙中插入新行。 - **Next-Key Lock**:Record Lock + Gap Lock 的组合,锁定一条记录及其前面的间隙。这是 InnoDB 在 REPEATABLE READ 下的默认加锁方式。 举个例子:表中有 id = 1、5、10 三条记录。对 id = 5 加 Next-Key Lock 时,实际锁住的范围是 (1, 5],即 id 大于 1 且小于等于 5 的区间。其他事务无法在这个范围内插入新行(比如 id = 3),从而防止了幻读。 在 READ COMMITTED 下,Gap Lock 被禁用(外键约束检查和唯一键冲突检查除外),只使用 Record Lock。这意味着其他事务可以在已锁定记录的间隙中自由插入,并发度更高,但可能出现幻读。 ## InnoDB 与 MyISAM 的关键区别 讨论事务隔离级别的前提是存储引擎支持事务。MariaDB 同时支持 InnoDB 和 MyISAM,但两者在事务能力上有本质区别: - **InnoDB**:支持完整的 ACID 事务、行级锁、MVCC、外键约束和崩溃恢复。事务隔离级别的所有讨论都基于 InnoDB。 - **MyISAM**:不支持事务、不支持行级锁(只有表级锁)、没有 MVCC、没有崩溃恢复。在 MyISAM 表上执行 `START TRANSACTION` 不会有任何效果,`ROLLBACK` 也不会回滚任何修改。 如果你的表使用 MyISAM 引擎,事务隔离级别的设置毫无意义。检查方法: ```sql SELECT ENGINE FROM information_schema.TABLES WHERE TABLE_SCHEMA = 'your_db' AND TABLE_NAME = 'your_table'; ``` 如果是 MyISAM,需要先转为 InnoDB: ```sql ALTER TABLE your_table ENGINE = InnoDB; ``` MariaDB 5.5 起默认存储引擎已经是 InnoDB,新建的表无需额外指定。 ## 如何设置隔离级别 ### 查看当前隔离级别 ```sql -- 查看全局默认隔离级别 SELECT @@GLOBAL.transaction_isolation; -- 查看当前会话隔离级别 SELECT @@SESSION.transaction_isolation; -- 兼容写法(MariaDB 中仍可用) SELECT @@tx_isolation; ``` ### 设置隔离级别 ```sql -- 仅影响下一个事务 SET TRANSACTION ISOLATION LEVEL READ COMMITTED; -- 影响当前会话的所有后续事务 SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; -- 影响所有新会话的默认隔离级别(需要 SUPER 权限) SET GLOBAL TRANSACTION ISOLATION LEVEL READ COMMITTED; ``` 注意:事务已经开始后不能修改隔离级别,否则会报错 `ERROR 1568 (25001): Transaction characteristics can't be changed while a transaction is in progress`。 ### 在配置文件中设置 在 `my.cnf` 中设置全局默认: ```ini [mysqld] transaction-isolation = READ-COMMITTED ``` 重启后生效。 ## MariaDB 与 MySQL 的差异 MariaDB 是 MySQL 的分支,事务隔离机制基本一致,但有几个值得注意的差异: - **tx_isolation vs transaction_isolation**:MySQL 8.0.3 移除了 `tx_isolation` 别名,只使用 `transaction_isolation`;MariaDB 两者都支持。 - **WITH CONSISTENT SNAPSHOT**:MariaDB 的 `START TRANSACTION WITH CONSISTENT SNAPSHOT` 兼容所有隔离级别,MySQL 8.0 前只支持 REPEATABLE READ。 - **Gap Lock 行为**:两者在 REPEATABLE READ 下的 Gap Lock 策略相同,但具体死锁场景可能因版本不同而有差异。 - **默认二进制日志**:MySQL 8.0 默认开启 binlog,MariaDB 默认关闭。binlog 的开启与否会影响事务的提交流程和性能。 - **Aria 引擎**:MariaDB 用 Aria 替代 MyISAM 作为非事务型引擎的选择,Aria 支持崩溃安全特性。 ## 怎么选择隔离级别 选择隔离级别本质上是正确性和并发性能之间的权衡: - **大多数 Web 应用**:保持默认的 REPEATABLE READ 即可。InnoDB 的 MVCC 让读操作不加锁,性能开销可控。 - **高并发短事务场景**(如秒杀、库存扣减):考虑降级到 READ COMMITTED。Gap Lock 在高并发下容易导致死锁,去掉 Gap Lock 可以减少锁冲突。代价是需要业务层处理不可重复读。 - **报表和数据分析**:READ COMMITTED 通常够用。报表查询对同一事务内的一致性要求不高,但需要看到最新提交的数据。 - **金融对账和审计**:SERIALIZABLE 或者在应用层加分布式锁。数据一致性优先,性能可以妥协。 - **READ UNCOMMITTED**:几乎没有任何合理的使用场景。即使你不在乎一致性,它也不会比 READ COMMITTED 快多少——InnoDB 在 RC 级别下读操作同样不加锁。 一个常见的调优方向:把 REPEATABLE READ 降为 READ COMMITTED,减少 Gap Lock 带来的死锁问题。Drupal 官方就推荐使用 READ COMMITTED 替代默认的 REPEATABLE READ 来避免死锁。如果你的业务逻辑中大量使用范围查询和插入操作混合的场景,值得做这个调整。 从性能角度看,隔离级别从低到高,锁持有时间递增、锁范围递增、并发度递减。REPEATABLE READ 的 Read View 在事务期间一直持有,长事务会占用大量 undo log 空间;READ COMMITTED 每次 SELECT 创建新 Read View,undo log 压力更小。所以控制事务长度比选择隔离级别本身更重要——无论用哪个级别,都应该让事务尽可能短。
服务端2026年5月27日 10:48
MariaDB 窗口函数怎么用?排名、累计和同比计算详解窗口函数是 SQL 里做数据分析最好用的工具——不用窗口函数,计算排名、累计、同比这些需求得写各种子查询和自连接,代码又长又慢。MariaDB 从 10.2 开始支持窗口函数,基本覆盖了 SQL 标准的核心功能。 ## 语法结构 每个窗口函数都遵循同一套语法: ```sql 函数名(表达式) OVER ( PARTITION BY 分组字段 ORDER BY 排序字段 ROWS/RANGE 窗口范围 ) ``` OVER 子句定义了"窗口"——函数在这个范围内计算。PARTITION BY 把数据分组,每组独立计算;ORDER BY 决定组内排序;ROWS/RANGE 进一步约束参与计算的行范围。 ## 排名函数:ROW_NUMBER、RANK、DENSE_RANK 三个排名函数的区别在处理并列值时的行为: - **ROW_NUMBER**:严格递增,1-2-3-4,不管值是否相同 - **RANK**:并列同名,跳号,1-1-3-4 - **DENSE_RANK**:并列同名,不跳号,1-1-2-3 典型场景——每个部门薪资前三名: ```sql SELECT * FROM ( SELECT name, department, salary, DENSE_RANK() OVER (PARTITION BY department ORDER BY salary DESC) AS rnk FROM employees ) t WHERE rnk <= 3; ``` 用 DENSE_RANK 而不是 RANK,因为如果前三名有并列,RANK 会跳号,导致实际返回的记录少于 3 条。 ## 聚合函数做累计和移动平均 窗口函数让 SUM/AVG/COUNT 不再只是"一组一个数",而是逐行累计: ```sql -- 累计销售额 SELECT order_date, amount, SUM(amount) OVER (ORDER BY order_date) AS running_total FROM orders; -- 7 天移动平均 SELECT order_date, amount, AVG(amount) OVER (ORDER BY order_date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS avg_7d FROM orders; ``` ROWS BETWEEN ... AND ... 定义了参与计算的行范围。6 PRECEDING AND CURRENT ROW 表示当前行和前 6 行,总共 7 行做平均。 ## LAG 和 LEAD:访问前后行 LAG 取前 N 行的值,LEAD 取后 N 行的值。算环比增长率靠它们: ```sql SELECT month, revenue, LAG(revenue, 1) OVER (ORDER BY month) AS prev_month, ROUND((revenue - LAG(revenue, 1) OVER (ORDER BY month)) / LAG(revenue, 1) OVER (ORDER BY month) * 100, 2) AS growth_pct FROM monthly_sales; ``` LAG 的第二个参数是偏移量,第三个参数是默认值(缺省返回 NULL)。算同比就改成 `LAG(revenue, 12)`,往前取 12 个月。 ## FIRST_VALUE 和 LAST_VALUE 的坑 FIRST_VALUE 取分组内第一个值,LAST_VALUE 取最后一个值。但 LAST_VALUE 有个常见陷阱——默认窗口范围是 ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW,不是整个分区。所以如果你想取部门最低薪资,必须显式指定窗口范围: ```sql -- 正确写法:指定完整窗口范围 SELECT name, department, salary, LAST_VALUE(salary) OVER ( PARTITION BY department ORDER BY salary DESC ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING ) AS lowest_in_dept FROM employees; ``` 不加 ROWS BETWEEN ... UNBOUNDED FOLLOWING,LAST_VALUE 每行返回的值都不一样——因为窗口只到当前行为止。 ## ROWS 和 RANGE 的区别 - **ROWS**:按物理行号计算,窗口大小固定 - **RANGE**:按逻辑值范围计算,同一排序值的行作为一个整体 ```sql -- ROWS:固定 3 行窗口 SUM(amount) OVER (ORDER BY date ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) -- RANGE:同一天的行一起算 SUM(amount) OVER (ORDER BY date RANGE BETWEEN INTERVAL 7 DAY PRECEDING AND CURRENT ROW) ``` RANGE 适合按时间窗口聚合,同一时间点的所有行会被包含在同一个窗口内。ROWS 更精确,适合固定行数的滑动窗口。
服务端2月21日 15:51
MariaDB 性能调优有哪些关键参数和优化策略?MariaDB 的性能调优需要从多个维度进行优化,以下是主要的调优策略: ## 1. 配置参数优化 ```ini # my.cnf 配置文件 # 连接配置 max_connections = 500 max_connect_errors = 100000 wait_timeout = 28800 interactive_timeout = 28800 # InnoDB 配置 innodb_buffer_pool_size = 4G innodb_buffer_pool_instances = 4 innodb_log_file_size = 512M innodb_log_buffer_size = 16M innodb_flush_log_at_trx_commit = 2 innodb_flush_method = O_DIRECT innodb_file_per_table = 1 innodb_io_capacity = 2000 innodb_io_capacity_max = 4000 innodb_read_io_threads = 8 innodb_write_io_threads = 8 # MyISAM 配置 key_buffer_size = 256M myisam_sort_buffer_size = 64M # 查询缓存(MariaDB 10.3+ 已移除) # query_cache_size = 64M # query_cache_type = 1 # 临时表配置 tmp_table_size = 256M max_heap_table_size = 256M # 排序和连接配置 sort_buffer_size = 2M read_buffer_size = 1M read_rnd_buffer_size = 2M join_buffer_size = 2M # 线程配置 thread_cache_size = 16 thread_stack = 256K # 日志配置 slow_query_log = 1 long_query_time = 2 log_queries_not_using_indexes = 1 ``` ## 2. 内存优化 ```sql -- 查看 InnoDB 缓冲池使用情况 SHOW STATUS LIKE 'Innodb_buffer_pool%'; -- 查看连接数 SHOW STATUS LIKE 'Threads_connected'; SHOW VARIABLES LIKE 'max_connections'; -- 查看内存使用 SHOW STATUS LIKE 'Memory%'; ``` ## 3. 硬件优化 1. **CPU**:多核处理器,建议 8 核以上 2. **内存**:建议 16GB 以上,InnoDB 缓冲池占用 70-80% 3. **磁盘**:使用 SSD,配置 RAID 10 4. **网络**:千兆以上网络带宽 ## 4. 表结构优化 ```sql -- 使用合适的数据类型 -- 不推荐:VARCHAR(255) 用于状态字段 -- 推荐:TINYINT 或 ENUM -- 规范化与反规范化权衡 -- 读取频繁:适当反规范化 -- 写入频繁:保持规范化 -- 分区表 ALTER TABLE orders PARTITION BY RANGE (YEAR(created_at)) ( PARTITION p2023 VALUES LESS THAN (2024), PARTITION p2024 VALUES LESS THAN (2025), PARTITION p2025 VALUES LESS THAN (2026) ); ``` ## 5. 查询优化 ```sql -- 使用 EXPLAIN 分析查询 EXPLAIN SELECT * FROM orders WHERE user_id = 1; -- 创建合适的索引 CREATE INDEX idx_user_id_created ON orders(user_id, created_at); -- 避免全表扫描 SELECT * FROM large_table WHERE indexed_column = 'value'; -- 使用批量操作 INSERT INTO users (name, email) VALUES ('John', 'john@example.com'), ('Jane', 'jane@example.com'); ``` ## 6. 监控和诊断 ```sql -- 查看慢查询 SELECT * FROM mysql.slow_log ORDER BY query_time DESC LIMIT 10; -- 查看表状态 SHOW TABLE STATUS FROM database_name; -- 查看索引使用情况 SELECT * FROM information_schema.statistics WHERE table_schema = 'database_name'; -- 查看进程列表 SHOW PROCESSLIST; SHOW FULL PROCESSLIST; ``` ## 7. 性能测试工具 ```bash # 使用 sysbench 进行性能测试 sysbench oltp_read_write \ --mysql-host=localhost \ --mysql-port=3306 \ --mysql-user=root \ --mysql-password=password \ --mysql-db=test \ --tables=10 \ --table-size=100000 \ --threads=16 \ --time=300 \ --report-interval=10 \ run ``` ## 8. 定期维护 ```sql -- 分析表 ANALYZE TABLE table_name; -- 优化表 OPTIMIZE TABLE table_name; -- 检查表 CHECK TABLE table_name; -- 修复表 REPAIR TABLE table_name; ``` 通过系统性的性能调优,可以显著提升 MariaDB 的整体性能和稳定性。
服务端2月21日 15:51
MariaDB 如何进行安全配置?有哪些安全最佳实践?MariaDB 的安全配置是保护数据库安全的重要环节,以下是主要的安全配置措施: ## 1. 用户权限管理 ```sql -- 创建用户并设置密码 CREATE USER 'app_user'@'localhost' IDENTIFIED BY 'strong_password'; -- 授予最小必要权限 GRANT SELECT, INSERT, UPDATE ON database_name.* TO 'app_user'@'localhost'; -- 撤销权限 REVOKE DELETE ON database_name.* FROM 'app_user'@'localhost'; -- 删除用户 DROP USER 'app_user'@'localhost'; -- 查看用户权限 SHOW GRANTS FOR 'app_user'@'localhost'; -- 刷新权限 FLUSH PRIVILEGES; ``` ## 2. 配置文件安全 ```ini # my.cnf 安全配置 # 禁止远程 root 登录 skip-networking # 或 bind-address = 127.0.0.1 # 禁用本地文件加载 local-infile = 0 # 限制最大连接数 max_connections = 100 # 启用 SSL require-secure-transport = ON ssl-ca = /path/to/ca-cert.pem ssl-cert = /path/to/server-cert.pem ssl-key = /path/to/server-key.pem # 设置默认认证插件 default-authentication-plugin = mysql_native_password ``` ## 3. 密码策略 ```sql -- 安装密码验证插件 INSTALL PLUGIN simple_password_check SONAME 'simple_password_check.so'; -- 配置密码策略 SET GLOBAL simple_password_check_minimal_length = 12; SET GLOBAL simple_password_check_minimal_digit_count = 2; SET GLOBAL simple_password_check_minimal_special_char_count = 1; SET GLOBAL simple_password_check_minimal_uppercase_char_count = 1; -- 强制密码过期 ALTER USER 'app_user'@'localhost' PASSWORD EXPIRE; ALTER USER 'app_user'@'localhost' PASSWORD EXPIRE INTERVAL 90 DAY; ``` ## 4. 网络安全 ```bash # 配置防火墙 # 只允许特定 IP 访问 iptables -A INPUT -p tcp -s 192.168.1.0/24 --dport 3306 -j ACCEPT iptables -A INPUT -p tcp --dport 3306 -j DROP # 使用 SSH 隧道 ssh -L 3306:localhost:3306 user@remote_server ``` ## 5. 数据加密 ```sql -- 启用 InnoDB 表加密 -- my.cnf 配置 innodb_encrypt_tables = ON innodb_encrypt_log = ON innodb_encryption_threads = 4 innodb_encryption_rotate_key_age = 1 -- 创建加密表 CREATE TABLE sensitive_data ( id INT PRIMARY KEY, data VARCHAR(255), ENCRYPTION='Y' ) ENGINE=InnoDB ENCRYPTED=YES; -- 使用加密函数 SELECT AES_ENCRYPT('sensitive_data', 'encryption_key'); SELECT AES_DECRYPT(encrypted_data, 'encryption_key'); ``` ## 6. 审计日志 ```sql -- 启用审计日志 -- my.cnf 配置 plugin_load_add = server_audit server_audit_events = CONNECT,QUERY,TABLE server_audit_logging = ON server_audit_file_path = /var/log/mariadb/audit.log server_audit_file_rotate_size = 100M server_audit_file_rotations = 9 -- 查看审计日志 SELECT * FROM information_schema.server_audit; ``` ## 7. 定期安全检查 ```sql -- 查看所有用户 SELECT user, host FROM mysql.user; -- 查看空密码用户 SELECT user, host FROM mysql.user WHERE authentication_string = ''; -- 查看具有所有权限的用户 SELECT user, host FROM mysql.user WHERE Grant_priv = 'Y'; -- 查看匿名用户 SELECT user, host FROM mysql.user WHERE user = ''; ``` ## 8. 备份安全 ```bash # 加密备份文件 mysqldump -u root -p database_name | gzip | openssl enc -aes-256-cbc -salt -out backup.sql.gz.enc # 解密备份文件 openssl enc -d -aes-256-cbc -in backup.sql.gz.enc | gunzip | mysql -u root -p database_name ``` ## 9. 安全最佳实践 1. **最小权限原则**:只授予必要的权限 2. **定期更新**:及时安装安全补丁 3. **强密码策略**:使用复杂密码并定期更换 4. **网络隔离**:限制数据库的网络访问 5. **加密传输**:使用 SSL/TLS 加密连接 6. **审计监控**:启用审计日志并定期审查 7. **备份保护**:加密备份文件并安全存储 8. **定期检查**:定期进行安全审计和漏洞扫描 通过以上安全配置措施,可以显著提升 MariaDB 的安全性,保护数据免受未授权访问和攻击。
服务端2月21日 15:51
MariaDB 如何进行备份和恢复?有哪些备份策略和工具?MariaDB 的备份与恢复是保障数据安全的重要环节,以下是主要的备份和恢复方法: ## 1. 逻辑备份(mysqldump) **全量备份**: ```bash # 备份所有数据库 mysqldump -u root -p --all-databases > all_databases.sql # 备份指定数据库 mysqldump -u root -p database_name > database_name.sql # 备份指定表 mysqldump -u root -p database_name table_name > table_name.sql # 备份并压缩 mysqldump -u root -p database_name | gzip > database_name.sql.gz ``` **增量备份**: ```bash # 启用二进制日志 # my.cnf 配置 log-bin = mysql-bin binlog-format = ROW # 备份二进制日志 mysqlbinlog mysql-bin.000001 > binlog_backup.sql ``` **恢复数据**: ```bash # 恢复完整备份 mysql -u root -p < all_databases.sql # 恢复指定数据库 mysql -u root -p database_name < database_name.sql # 恢复压缩备份 gunzip < database_name.sql.gz | mysql -u root -p database_name # 应用二进制日志 mysqlbinlog mysql-bin.000001 | mysql -u root -p ``` ## 2. 物理备份(Mariabackup) **全量备份**: ```bash # 创建备份 mariabackup --backup --target-dir=/backup/full \ --user=root --password=password # 准备备份 mariabackup --prepare --target-dir=/backup/full # 恢复备份 mariabackup --copy-back --target-dir=/backup/full ``` **增量备份**: ```bash # 创建全量备份 mariabackup --backup --target-dir=/backup/full \ --user=root --password=password # 创建增量备份 mariabackup --backup --target-dir=/backup/inc1 \ --incremental-basedir=/backup/full --user=root --password=password # 准备备份 mariabackup --prepare --target-dir=/backup/full mariabackup --prepare --target-dir=/backup/full \ --incremental-dir=/backup/inc1 ``` ## 3. 快照备份 ```bash # 使用 LVM 快照 lvcreate -L 10G -s -n mysql_snapshot /dev/vg0/mysql mount /dev/vg0/mysql_snapshot /mnt/backup rsync -av /mnt/backup/ /backup/mysql/ umount /mnt/backup lvremove /dev/vg0/mysql_snapshot ``` ## 4. 自动化备份脚本 ```bash #!/bin/bash # backup.sh DATE=$(date +%Y%m%d_%H%M%S) BACKUP_DIR="/backup/mariadb" MYSQL_USER="root" MYSQL_PASSWORD="password" # 创建备份目录 mkdir -p $BACKUP_DIR # 全量备份 mysqldump -u $MYSQL_USER -p$MYSQL_PASSWORD --all-databases \ --single-transaction --quick --lock-tables=false \ | gzip > $BACKUP_DIR/all_$DATE.sql.gz # 保留最近7天的备份 find $BACKUP_DIR -name "all_*.sql.gz" -mtime +7 -delete echo "Backup completed: all_$DATE.sql.gz" ``` ## 5. 备份策略建议 1. **全量备份**:每天凌晨执行 2. **增量备份**:每小时执行 3. **二进制日志**:实时保留 4. **异地备份**:定期同步到远程服务器 5. **备份验证**:定期测试恢复流程 ## 6. 恢复注意事项 1. 恢复前先停止 MariaDB 服务 2. 确保有足够的磁盘空间 3. 恢复后验证数据完整性 4. 记录恢复过程和时间点 5. 在测试环境先验证恢复流程 通过合理的备份策略和恢复流程,可以最大程度保障 MariaDB 数据的安全性和可靠性。
服务端2月21日 15:51
MariaDB 的分区表有哪些类型?如何创建和管理分区表?MariaDB 的分区表(Partitioning)是将大表分割成更小、更易管理的部分的技术,可以显著提升查询性能和管理效率。 ## 1. 分区类型 ### RANGE 分区 ```sql -- 按日期范围分区 CREATE TABLE orders ( id INT PRIMARY KEY, order_date DATE, customer_id INT, amount DECIMAL(10,2) ) PARTITION BY RANGE (YEAR(order_date)) ( PARTITION p2022 VALUES LESS THAN (2023), PARTITION p2023 VALUES LESS THAN (2024), PARTITION p2024 VALUES LESS THAN (2025), PARTITION pmax VALUES LESS THAN MAXVALUE ); -- 按数值范围分区 CREATE TABLE users ( id INT PRIMARY KEY, username VARCHAR(50), age INT ) PARTITION BY RANGE (age) ( PARTITION p0 VALUES LESS THAN (18), PARTITION p1 VALUES LESS THAN (30), PARTITION p2 VALUES LESS THAN (50), PARTITION p3 VALUES LESS THAN MAXVALUE ); ``` ### LIST 分区 ```sql -- 按离散值列表分区 CREATE TABLE orders ( id INT PRIMARY KEY, order_date DATE, region VARCHAR(20), amount DECIMAL(10,2) ) PARTITION BY LIST COLUMNS(region) ( PARTITION p_east VALUES IN ('New York', 'Boston', 'Philadelphia'), PARTITION p_west VALUES IN ('Los Angeles', 'San Francisco', 'Seattle'), PARTITION p_midwest VALUES IN ('Chicago', 'Detroit', 'Cleveland'), PARTITION p_south VALUES IN ('Atlanta', 'Miami', 'Dallas') ); ``` ### HASH 分区 ```sql -- 哈希分区(均匀分布数据) CREATE TABLE products ( id INT PRIMARY KEY, name VARCHAR(100), category_id INT, price DECIMAL(10,2) ) PARTITION BY HASH(id) PARTITIONS 4; -- 线性哈希分区(更快但分布可能不均匀) CREATE TABLE orders ( id INT PRIMARY KEY, customer_id INT, order_date DATE, amount DECIMAL(10,2) ) PARTITION BY LINEAR HASH(customer_id) PARTITIONS 8; ``` ### KEY 分区 ```sql -- KEY 分区(类似 HASH,使用主键或唯一键) CREATE TABLE users ( id INT PRIMARY KEY, username VARCHAR(50), email VARCHAR(100) ) PARTITION BY KEY(id) PARTITIONS 4; ``` ## 2. 分区管理 ### 添加分区 ```sql -- RANGE 分区添加新分区 ALTER TABLE orders ADD PARTITION ( PARTITION p2025 VALUES LESS THAN (2026) ); -- LIST 分区添加新分区 ALTER TABLE orders ADD PARTITION ( PARTITION p_other VALUES IN ('Other', 'Unknown') ); -- HASH/KEY 分区增加分区数量 ALTER TABLE products REORGANIZE PARTITION ( PARTITION p0, PARTITION p1, PARTITION p2, PARTITION p3 ) INTO ( PARTITION p0, PARTITION p1, PARTITION p2, PARTITION p3, PARTITION p4, PARTITION p5 ); ``` ### 删除分区 ```sql -- 删除分区(同时删除数据) ALTER TABLE orders DROP PARTITION p2022; -- 删除所有分区(转换为普通表) ALTER TABLE orders REMOVE PARTITIONING; ``` ### 合并分区 ```sql -- 合并 RANGE 分区 ALTER TABLE orders REORGANIZE PARTITION p2022, p2023 INTO ( PARTITION p2022_2023 VALUES LESS THAN (2024) ); ``` ## 3. 分区查询 ```sql -- 查看分区信息 SELECT PARTITION_NAME, PARTITION_METHOD, PARTITION_EXPRESSION, PARTITION_DESCRIPTION, TABLE_ROWS FROM information_schema.PARTITIONS WHERE TABLE_NAME = 'orders'; -- 查询特定分区 SELECT * FROM orders PARTITION (p2023); -- 使用分区裁剪优化查询 SELECT * FROM orders WHERE order_date >= '2023-01-01' AND order_date < '2024-01-01'; ``` ## 4. 分区维护 ```sql -- 检查分区 ALTER TABLE orders CHECK PARTITION p2023; -- 优化分区 ALTER TABLE orders OPTIMIZE PARTITION p2023; -- 分析分区 ALTER TABLE orders ANALYZE PARTITION p2023; -- 修复分区 ALTER TABLE orders REPAIR PARTITION p2023; ``` ## 5. 分区索引 ```sql -- 创建本地索引(每个分区独立索引) CREATE INDEX idx_customer_id ON orders(customer_id); -- 创建全局索引(MariaDB 10.3+) CREATE UNIQUE INDEX idx_global ON orders(id); ``` ## 6. 分区使用场景 ### 时间序列数据 ```sql CREATE TABLE logs ( id BIGINT PRIMARY KEY, log_time TIMESTAMP, message TEXT, level VARCHAR(10) ) PARTITION BY RANGE (UNIX_TIMESTAMP(log_time)) ( PARTITION p2024_q1 VALUES LESS THAN (UNIX_TIMESTAMP('2024-04-01')), PARTITION p2024_q2 VALUES LESS THAN (UNIX_TIMESTAMP('2024-07-01')), PARTITION p2024_q3 VALUES LESS THAN (UNIX_TIMESTAMP('2024-10-01')), PARTITION p2024_q4 VALUES LESS THAN (UNIX_TIMESTAMP('2025-01-01')) ); ``` ### 大表归档 ```sql -- 定期归档旧数据 ALTER TABLE orders DROP PARTITION p2022; ``` ## 7. 分区注意事项 1. **分区键选择**:选择查询中常用的列作为分区键 2. **分区数量**:不宜过多,建议 10-100 个分区 3. **主键约束**:主键必须包含分区键 4. **唯一索引**:唯一索引必须包含分区键 5. **数据分布**:确保数据在各分区间均匀分布 6. **维护成本**:分区表需要额外的维护操作 分区表是处理大数据量的有效手段,合理使用可以显著提升查询性能和管理效率。