原创

MySQL 索引类型详解:主键索引、唯一索引、覆盖索引、全文索引

索引在 MySQL 中到底解决什么问题

索引的本质不是“让查询一定更快”,而是用额外的存储与维护成本,换取更低的检索代价。对于 InnoDB 来说,理解索引必须先区分两件事:一是数据在磁盘上的组织方式,二是优化器如何借助索引减少扫描范围。MySQL 官方文档明确指出,InnoDB 在定义了 PRIMARY KEY 时,会把主键作为聚簇索引;如果表没有合适的主键,InnoDB 会选择第一个非空唯一索引作为聚簇索引,若仍不存在,则自行生成隐藏行 ID。也就是说,在 InnoDB 中,主键索引不仅是“约束”,还是“数据存放顺序”的核心。(MySQL开发者专区)

很多开发者把“主键索引、唯一索引、覆盖索引、全文索引”看成并列关系,但这其实并不严谨。主键索引和唯一索引是索引定义类型;全文索引是特定检索场景下的特殊索引结构;覆盖索引则不是单独的索引类型,而是一种查询命中了索引且无需回表的执行效果。如果把覆盖索引理解成 CREATE INDEX ... COVERING 之类的语法,那就是错误的,因为 MySQL 并不存在这种语法;覆盖索引是优化器执行计划中的结果,通常可在 EXPLAINExtra 列中看到 Using index。(MySQL开发者专区)

从底层结构看,MySQL 大多数常见索引包括 PRIMARY KEYUNIQUE、普通 INDEX,主要基于 B-Tree;但 FULLTEXT 在 InnoDB 中并不是普通 B-Tree 查找,而是倒排索引思路。也正因为如此,全文索引适合处理“包含哪些词”的问题,不适合替代精确匹配、范围查询或排序索引。(MySQL开发者专区)

一、主键索引:不仅唯一,而且决定了 InnoDB 的数据组织方式

1.1 主键索引的定义与本质

主键索引最直观的语义是:主键列值必须唯一,且不能为 NULL。但在 InnoDB 中,主键索引更关键的意义在于它是聚簇索引。所谓聚簇,不是说“索引和数据放在一起”这么简单,而是说表数据本身按照主键索引叶子节点的顺序组织。因此,按主键检索时,通常只需一次 B-Tree 路径定位,就能拿到整行数据。(MySQL开发者专区)

这带来两个直接结论:

  1. 主键查询通常是 InnoDB 最稳定、最便宜的点查方式。
  2. 所有二级索引的叶子节点里,都会保存对应记录的主键值,而不是直接保存整行地址。

第二点非常关键。很多性能问题并不是出在“有没有索引”,而是出在“二级索引查到主键后,还要不要再根据主键回表取数据”。理解这一点,后面才能真正理解覆盖索引。

1.2 主键设计为什么不能随意

主键索引的设计,决定了整张表的物理局部性。设计不当,会对插入、页分裂、二级索引大小、冷热数据分布产生连锁影响。

一个好的 InnoDB 主键,通常满足以下特征:

  • 稳定
  • 单调递增或大体递增
  • 业务语义尽量弱

为什么要短?因为二级索引叶子节点都要携带主键值,主键越长,所有二级索引越大。为什么要稳定?因为主键一旦更新,本质上不是简单修改列值,而是记录在聚簇索引中的位置变化,代价远高于普通列更新。为什么最好递增?因为递增主键更有利于减少频繁的页分裂与随机写。

因此,实践里最常见的方案是 BIGINT AUTO_INCREMENT,或者趋势递增的雪花 ID、分段有序 ID。至于 UUID,不能说绝对不能用,但无序 UUID 作为聚簇主键,会显著增加随机写和页分裂概率,在高写入场景下尤其不友好。

1.3 主键索引的建表 SQL

下面给出一个典型订单表的完整建表 SQL。这里主键选择 BIGINT,并为后续查询模式预留二级索引。

DROP TABLE IF EXISTS `t_order`;

CREATE TABLE `t_order` (
  `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '订单主键',
  `order_no` VARCHAR(64) NOT NULL COMMENT '订单号',
  `user_id` BIGINT NOT NULL COMMENT '用户ID',
  `status` TINYINT NOT NULL COMMENT '订单状态',
  `total_amount` DECIMAL(12,2) NOT NULL COMMENT '订单金额',
  `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_order_no` (`order_no`),
  KEY `idx_user_id_created_at` (`user_id`, `created_at`),
  KEY `idx_status_created_at` (`status`, `created_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='订单表';

这张表中:

  • PRIMARY KEY (id) 是聚簇索引;
  • uk_order_no 是唯一索引;
  • idx_user_id_created_atidx_status_created_at 是普通二级索引。

注意,虽然 order_no 也唯一,但它不应该轻易替代自增主键成为聚簇索引,除非业务模型明确要求,并且你能接受更长的主键带来的二级索引膨胀。

1.4 主键索引的典型查询优势

例如:

SELECT id, order_no, status, total_amount
FROM t_order
WHERE id = 1000001;

这类查询直接通过聚簇索引定位,代价最低。对于热点点查、按主键更新、按主键删除,主键索引几乎都是最优路径。

1.5 主键索引的常见误区

误区一:有唯一索引就不需要主键

错误。对 InnoDB 而言,主键不仅用于唯一性校验,更用于定义聚簇索引。官方也明确建议每张表都定义主键;如果没有逻辑上的合适列,应添加一个自增列作为主键。(MySQL开发者专区)

误区二:业务主键一定优于代理主键

不一定。业务主键如果过长、可变、无序,通常不适合作为 InnoDB 聚簇索引。业务唯一性完全可以通过唯一索引保证,而让一个更短、更稳定的代理键承担聚簇职责。

误区三:主键只影响主键查询

错误。主键长度和形态会影响所有二级索引的体积与访问成本,因为二级索引叶子节点保存的是主键值。

二、唯一索引:约束是表象,快速定位才是执行价值

2.1 唯一索引的定义

唯一索引要求索引列组合的值不能重复。和主键不同,唯一索引不要求必须是表的“身份标识”,也不要求只能有一个。一个表可以有多个唯一索引。MySQL 的 CREATE TABLECREATE INDEX 语法都支持定义唯一索引。(MySQL开发者专区)

例如用户表:

DROP TABLE IF EXISTS `t_user`;

CREATE TABLE `t_user` (
  `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '用户主键',
  `username` VARCHAR(64) NOT NULL COMMENT '用户名',
  `email` VARCHAR(128) NOT NULL COMMENT '邮箱',
  `phone` VARCHAR(32) DEFAULT NULL COMMENT '手机号',
  `nickname` VARCHAR(64) NOT NULL COMMENT '昵称',
  `status` TINYINT NOT NULL DEFAULT 1 COMMENT '状态',
  `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_username` (`username`),
  UNIQUE KEY `uk_email` (`email`),
  UNIQUE KEY `uk_phone` (`phone`),
  KEY `idx_status_created_at` (`status`, `created_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户表';

这里:

  • id 是主键索引;
  • usernameemailphone 都有唯一索引;
  • 一个表存在多个唯一索引是完全正常的。

2.2 唯一索引与主键索引的区别

两者都保证唯一性,但并不等价。

第一,主键只能有一个,唯一索引可以有多个。 第二,主键列不能为 NULL,唯一索引在 MySQL 中允许 NULL,而且对 NULL 的判定不是“多个 NULL 重复”,这与很多开发者的直觉不同。 第三,在 InnoDB 中,主键索引是聚簇索引;唯一索引通常是二级索引,叶子节点仍然保存主键值。 第四,主键更偏向数据组织核心,唯一索引更偏向业务约束与快速精确查找。

2.3 唯一索引的查询价值

唯一索引不仅用于防止脏数据,还能帮助优化器快速判定“最多只会返回一条记录”。这会影响执行计划选择。

例如:

SELECT id, username, nickname
FROM t_user
WHERE email = 'alice@example.com';

如果 email 上有唯一索引,优化器知道这是精确唯一匹配,预估代价很低,通常能快速走索引定位。

在业务系统中,以下字段非常适合唯一索引:

  • 用户名
  • 邮箱
  • 手机号
  • 订单号
  • 支付流水号
  • 三方平台的业务唯一编号

2.4 联合唯一索引的价值

唯一性并不总是由单列决定。很多业务规则是组合唯一,例如一个用户在某一天只能签到一次:

DROP TABLE IF EXISTS `t_sign_record`;

CREATE TABLE `t_sign_record` (
  `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '签到主键',
  `user_id` BIGINT NOT NULL COMMENT '用户ID',
  `sign_date` DATE NOT NULL COMMENT '签到日期',
  `reward_points` INT NOT NULL DEFAULT 0 COMMENT '奖励积分',
  `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_user_sign_date` (`user_id`, `sign_date`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户签到表';

uk_user_sign_date 可以从数据库层面保证“一个用户一天只能一条签到记录”,避免只靠应用层判断造成并发写入穿透。

2.5 唯一索引的常见误区

误区一:唯一索引只用于防重

不完整。唯一索引当然有约束作用,但它同样是查询优化的重要手段,尤其适合高频等值查询。

误区二:唯一索引一定比普通索引快很多

不应绝对化。对点查场景,唯一索引能让优化器更明确地收敛结果集;但对范围查询、排序、分组,是否更优还要看列顺序、过滤条件、回表成本和统计信息。

误区三:所有业务唯一字段都应该直接做主键

大多数情况下不建议。业务唯一字段适合唯一索引,是否做主键,要看它是否足够短、稳定、适合作为聚簇键。

三、覆盖索引:不是语法类型,而是“无需回表”的执行结果

3.1 覆盖索引到底是什么

覆盖索引经常被误写成一种独立索引类型,这是最常见的认知错误之一。实际上,覆盖索引不是通过某条专门语法创建出来的,而是指:查询所需的列全部可以从某个索引中直接获取,因此无需再访问聚簇索引中的完整数据行。MySQL 官方文档对 EXPLAIN 的说明中,Extra 列出现 Using index,通常就表示只通过索引即可返回所需列。(MySQL开发者专区)

这意味着覆盖索引是“查询 + 索引设计 + 访问列集合”共同作用的结果,而不是一个固定对象。

3.2 为什么覆盖索引能显著提速

在 InnoDB 中,二级索引叶子节点保存的是“索引列 + 主键值”。因此当查询条件命中二级索引时,如果 SELECT 还需要额外字段,而这些字段不在该二级索引中,MySQL 必须先通过二级索引找到主键,再回到聚簇索引取整行,这就是回表。

回表并不意味着一定很慢,但当结果集较大、随机 I/O 明显、缓存命中一般时,回表成本会非常高。覆盖索引减少了这一步,往往能带来非常可观的收益。

3.3 覆盖索引示例

继续使用订单表:

CREATE TABLE `t_order` (
  `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '订单主键',
  `order_no` VARCHAR(64) NOT NULL COMMENT '订单号',
  `user_id` BIGINT NOT NULL COMMENT '用户ID',
  `status` TINYINT NOT NULL COMMENT '订单状态',
  `total_amount` DECIMAL(12,2) NOT NULL COMMENT '订单金额',
  `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_order_no` (`order_no`),
  KEY `idx_user_id_created_at` (`user_id`, `created_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='订单表';

假设有如下查询:

SELECT user_id, created_at
FROM t_order
WHERE user_id = 2001
ORDER BY created_at DESC
LIMIT 20;

如果使用 idx_user_id_created_at (user_id, created_at),那么过滤条件需要的 user_id、返回列需要的 user_idcreated_at 都在索引里,排序也能利用索引顺序,这就是典型的覆盖索引场景。

但如果查询改成:

SELECT user_id, created_at, total_amount
FROM t_order
WHERE user_id = 2001
ORDER BY created_at DESC
LIMIT 20;

这时 total_amount 不在 idx_user_id_created_at 中,就可能需要回表。是否回表以及成本高低,还取决于优化器最终计划,但从原理上说,这已经不是纯覆盖访问了。

3.4 覆盖索引的设计原则

原则一:索引顺序先考虑过滤,再考虑排序,再考虑覆盖

很多人设计索引时只想着“把查询字段都塞进去”,这是不对的。索引首先要服务于高选择性过滤与排序路径,其次才是覆盖。否则你可能得到一个很宽、很重、维护成本很高、但过滤效果一般的索引。

原则二:不要为了覆盖而盲目把大字段放进索引

例如 TEXT、超长 VARCHAR、低价值字段,不应该为了少一次回表就塞进联合索引。索引页会变大,缓存利用率下降,写入成本上升,往往得不偿失。

原则三:高频列表页、详情摘要页最适合覆盖索引

例如订单列表、消息列表、用户列表等,只返回少量固定列,又存在明确过滤和排序条件,非常适合设计覆盖索引。

3.5 覆盖索引不是万能的

覆盖索引能减少回表,但不代表总是最优。原因主要有三点:

第一,覆盖索引会增加索引宽度,写入和维护成本更高。 第二,查询返回的列经常变化时,索引很难稳定覆盖。 第三,如果结果集很小,或者数据已高度缓存,回表成本可能并不高,专门为覆盖新增索引未必划算。

3.6 一个更实用的联合索引案例

假设后台管理系统经常执行如下查询:

SELECT id, order_no, status, created_at
FROM t_order
WHERE user_id = ?
  AND status = ?
ORDER BY created_at DESC
LIMIT 20;

可以考虑这样的索引:

KEY `idx_user_status_created` (`user_id`, `status`, `created_at`)

为什么不把 order_no 也放进去?要看实际收益。如果列表页确实高频,而且 order_no 必须展示,且这条 SQL 是核心热点,那么可以评估:

KEY `idx_user_status_created_order_no` (`user_id`, `status`, `created_at`, `order_no`)

但必须意识到,这不是“覆盖越多越好”,而是“核心热点查询是否值得用更大的索引换更少的回表”。

四、全文索引:解决模糊文本检索,但不替代普通索引

4.1 全文索引适合什么场景

LIKE '%关键词%' 是很多业务系统的常见写法,但它在大多数情况下无法有效利用普通 B-Tree 索引,尤其前缀是 % 时。全文索引正是为文本检索设计的。MySQL 官方文档说明,FULLTEXT 索引只能用于 CHARVARCHARTEXT 列,并且仅支持 InnoDBMyISAM 表;对于 CJK 场景,MySQL 提供内置的 ngram 全文解析器。(MySQL开发者专区)

换句话说,全文索引适合的问题是:

  • 文章内容包含某些词
  • 标题与正文是否匹配某些关键词
  • 评论、帖子、知识库文档的文本检索

它不适合的问题是:

  • 精确等值查找
  • 范围查询
  • 按数值排序
  • 联合过滤中承担普通高选择性条件的主要路径

4.2 InnoDB 全文索引的底层特征

InnoDB 的全文索引不是普通 B-Tree,而是面向词项检索的倒排结构。官方文档还提到,InnoDB 会使用文档标识 DOC_ID 映射词项与记录,并依赖辅助表维护全文索引数据。也正因为实现机制特殊,全文索引的维护、构建、查询行为,和普通索引不能混为一谈。(MySQL开发者专区)

4.3 全文索引建表 SQL

下面给出一个文章表示例:

DROP TABLE IF EXISTS `t_article`;

CREATE TABLE `t_article` (
  `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '文章主键',
  `title` VARCHAR(255) NOT NULL COMMENT '标题',
  `summary` VARCHAR(1000) DEFAULT NULL COMMENT '摘要',
  `content` TEXT NOT NULL COMMENT '正文',
  `author_id` BIGINT NOT NULL COMMENT '作者ID',
  `status` TINYINT NOT NULL DEFAULT 1 COMMENT '状态',
  `published_at` DATETIME DEFAULT NULL COMMENT '发布时间',
  `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`id`),
  KEY `idx_author_id_published_at` (`author_id`, `published_at`),
  FULLTEXT KEY `ft_title_summary_content` (`title`, `summary`, `content`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='文章表';

查询示例:

SELECT id, title, published_at
FROM t_article
WHERE MATCH(title, summary, content) AGAINST('MySQL 索引' IN NATURAL LANGUAGE MODE);

这是全文检索的标准写法之一。

4.4 中文全文检索必须关注解析器

MySQL 官方文档明确说明,内置全文解析器对没有天然单词边界的语言并不总能直接处理,因此提供了 ngram 解析器来支持中文、日文、韩文。对于中文业务,如果直接对中文文本使用默认解析逻辑,结果往往不理想;此时应考虑 WITH PARSER ngram。(MySQL开发者专区)

例如:

DROP TABLE IF EXISTS `t_article_cjk`;

CREATE TABLE `t_article_cjk` (
  `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '文章主键',
  `title` VARCHAR(255) NOT NULL COMMENT '标题',
  `content` TEXT NOT NULL COMMENT '正文',
  `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  PRIMARY KEY (`id`),
  FULLTEXT KEY `ft_title_content_ngram` (`title`, `content`) WITH PARSER ngram
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='中文全文索引文章表';

这在中文内容检索中通常比默认解析器更符合预期。

4.5 全文索引与 LIKE 的区别

二者都能做“搜文本”,但能力边界不同。

LIKE 'abc%' 有机会利用前缀索引。 LIKE '%abc%' 基本无法高效利用普通索引。 FULLTEXT 则基于词项检索,更适合自然语言搜索,但它不等价于任意子串匹配。

如果你的需求是“按手机号前缀查”“按订单号前缀查”,普通索引更合适。 如果你的需求是“搜索文章里提到 Redis 和 MySQL 的内容”,全文索引更合适。

4.6 全文索引的限制与注意点

根据 MySQL 官方文档,全文索引只能建在字符类型列上,并且同一个全文索引中的列必须使用相同字符集和排序规则。对于 CJK 文本,还要考虑解析器与分词粒度的影响。(MySQL开发者专区)

另外,全文索引虽然方便,但并不意味着它能替代专业搜索引擎。对于以下场景,应慎重:

  • 复杂相关性排序
  • 高亮、纠错、同义词扩展
  • 海量日志检索
  • 聚合统计与复杂搜索 DSL
  • 多字段、权重、布尔组合极其复杂的搜索

这类需求通常更适合 Elasticsearch、OpenSearch 一类专业搜索系统。MySQL 全文索引更适合中小规模、功能要求相对克制、希望减少系统复杂度的文本检索场景。

五、版本差异与准确理解

5.1 覆盖索引不是版本功能差异,而是执行行为

无论 MySQL 8.0 还是 8.4,覆盖索引都不是新的索引类型,而是查询只访问索引即可返回结果的行为表现。识别方式仍然应以执行计划为准,而不是寻找某种单独 DDL 语法。(MySQL开发者专区)

5.2 MySQL 8.4 仍然支持 InnoDB 与 MyISAM 的 FULLTEXT,但实际生产应优先 InnoDB

官方文档显示,FULLTEXT 可用于 InnoDBMyISAM。但在现代生产系统中,绝大多数事务型业务仍应优先选择 InnoDB,因为它具备事务、行级锁、崩溃恢复和更完整的并发控制能力。全文索引是否可用是一回事,主存储引擎的事务能力是另一回事。(MySQL开发者专区)

5.3 中文全文检索在 MySQL 8.4 中依旧要明确解析器策略

这不是历史遗留问题,而是当前版本仍然需要认真处理的设计点。默认解析器与 ngram 的适用范围不同,中文场景下通常不能省略这一步判断。(MySQL开发者专区)

六、生产实践中的选型建议

6.1 什么时候优先主键索引

  • 所有 InnoDB 表都应该显式定义主键
  • 主键优先选择短、稳定、尽量递增的列
  • 高并发 OLTP 场景,尽量避免把长字符串作为聚簇主键

6.2 什么时候使用唯一索引

  • 业务上必须保证不重复
  • 经常以该字段做等值精确查询
  • 需要数据库层兜底并发幂等约束

例如用户注册时的邮箱、手机号,支付回调时的三方交易号,订单系统中的业务订单号,都非常适合唯一索引。

6.3 什么时候重点设计覆盖索引

  • 列表查询频率高
  • 返回字段固定且不多
  • 回表代价明显
  • 查询模式相对稳定

很多系统中真正的核心热点 SQL 并不多,针对这少量高频 SQL 做覆盖设计,收益往往远高于盲目增加大量索引。

6.4 什么时候考虑全文索引

  • 文章、评论、知识库、公告等文本检索
  • 不想引入独立搜索引擎
  • 检索功能较轻量
  • 数据量与搜索复杂度尚在 MySQL 可承受范围内

一旦搜索需求开始出现复杂排序、过滤、召回、纠错、拼写建议、多维聚合,通常就应该评估专用搜索系统,而不是继续强压在 MySQL 上。

七、四类索引的核心区别总结

主键索引的核心价值是:定义 InnoDB 聚簇结构,并提供最高效的主键访问路径。 唯一索引的核心价值是:保证业务唯一性,并优化高频等值查找。 覆盖索引的核心价值是:减少或避免回表,是查询命中索引后的执行优化结果。 全文索引的核心价值是:支持基于文本词项的搜索,而不是传统 B-Tree 的精确或范围查找。(MySQL开发者专区)

真正做系统设计时,不能只问“该不该加索引”,而要问四个更具体的问题:

  1. 这是不是 InnoDB 表的聚簇键问题?
  2. 这是不是数据库层唯一性约束问题?
  3. 这条热点 SQL 是否值得通过覆盖减少回表?
  4. 这是不是文本检索,而不是普通精确查询?

把这四个问题分清,索引设计就会从“经验拍脑袋”变成“基于访问模式的结构化决策”。

八、结语式归纳

在 MySQL 索引设计中,最大的错误往往不是“不会建索引”,而是把不同索引概念混为一谈。主键索引决定 InnoDB 数据的组织方式;唯一索引侧重业务唯一性与精准定位;覆盖索引不是 DDL 类型,而是减少回表的查询形态;全文索引则服务于文本搜索,底层机制与普通 B-Tree 完全不同。只有把这几类能力的边界搞清楚,才能既写出正确 SQL,也设计出真正经得起流量和数据量增长的表结构。

正文到此结束
评论插件初始化中...
Loading...