Spring Data JPA建立索引所使用的语法
java
@Entity
@Table(
name = "user",
indexes = {
@Index(name = "idx_user_username", columnList = "username"),
@Index(name = "idx_user_email_status", columnList = "email, status")
},
uniqueConstraints = {
@UniqueConstraint(name = "uk_user_email_username", columnNames = {"email", "username"})
}
)
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
private String email;
private String status;
// ... 其他字段
}
@Table(name = "user")
- 指定实体类对应的数据库表名为
user
。 - 如果不写
name
,默认会根据实体类名推断(比如类名叫User
就会对应user
表或User
表,具体与 JPA 实现有关)。
indexes = { ... }
- 这里可以声明一个或多个普通索引(或非唯一索引)。
java
indexes = {
@Index(name = "idx_user_username", columnList = "username"),
@Index(name = "idx_user_email_status", columnList = "email, status")
}
@Index(name = "idx_user_username", columnList = "username")
- 代表在
username
字段上创建一个 普通索引 ,索引名为idx_user_username
。 - 这样在数据库层面会自动执行类似
CREATE INDEX idx_user_username ON user(username);
的操作。
- 代表在
@Index(name = "idx_user_email_status", columnList = "email, status")
- 代表在
email
和status
这两个字段上创建一个联合索引 (复合索引),索引名为idx_user_email_status
。 - 在数据库层面大致对应
CREATE INDEX idx_user_email_status ON user(email, status);
。
- 代表在
注意 :这些索引默认不是唯一索引 。如果你没有在注解里指定
unique = true
,JPA 会把它当作普通索引创建。
uniqueConstraints = { ... }
- 用来指定唯一性约束,可以是针对一个或多个列。
java
uniqueConstraints = {
@UniqueConstraint(name = "uk_user_email_username", columnNames = {"email", "username"})
}
- 表示在
email
+username
这两个字段上创建一个联合唯一约束 ,约束名为uk_user_email_username
。 - 数据库层面上会生成类似
ALTER TABLE user ADD CONSTRAINT uk_user_email_username UNIQUE(email, username);
的语句(具体实现可能因数据库和 JPA 实现不同而略有差异)。 - 该约束可以确保同一张表中,不会出现
email
和username
的组合相同的两行数据。
区别于
indexes
:
uniqueConstraints
侧重的是唯一性约束。它不仅会在数据库中创建唯一索引,还会在表层面建立"约束"关系,不允许重复。indexes
只是声明一个非唯一索引(除非加unique=true
),主要用于加速查询,不会强制数据唯一。
@Column(unique = true)
如果你只想创建唯一索引 (不一定是多字段),也可以直接在字段上用 @Column(unique = true)
:
java
@Column(unique = true)
private String email;
- 这样会自动生成一个单字段唯一索引(约束)
一、按「数据结构」分类
1.1 B+Tree 索引
1.1.1 特点与应用场景
-
特点:
- B+Tree 是 MySQL(InnoDB/MyISAM 等多数引擎) 中最常用的索引结构。
- 支持 等值 查询和 范围 查询 (
BETWEEN
,<
,>
,ORDER BY
, 等)。 - 查询性能稳定,适合多数增删改查需求。
-
与其他分类的交叉:
- 物理存储:InnoDB 中的主键索引(聚簇索引)和二级索引都是 B+Tree。
- 字段特性:主键、唯一、普通、前缀索引都可由 B+Tree 来实现。
- 字段个数:可做单列或联合索引,底层都是 B+Tree。
1.1.2 Spring Data JPA 中如何使用 B+Tree 索引
在 InnoDB 引擎下,所有主键、唯一索引、普通索引默认就是 B+Tree 实现。只要我们在实体类里用以下方式指定索引,即可默认得到 B+Tree:
主键索引(B+Tree + 聚簇)
java
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
// ...
}
- 当表是 InnoDB 时,主键索引就是聚簇索引,底层是 B+Tree。
唯一索引(B+Tree 二级索引)
java
@Column(unique = true)
private String email;
- 这样会自动为
email
字段创建一个唯一索引,同样是 B+Tree。
普通索引(B+Tree 二级索引)
java
@Table(
indexes = {
@Index(name = "idx_user_username", columnList = "username")
}
)
@Entity
public class User {
@Id
private Long id;
private String username;
// ...
}
- 使用
@Index
注解在@Table
中声明普通索引,底层也是 B+Tree。
联合索引(B+Tree)
java
@Table(
indexes = {
@Index(name = "idx_user_email_status", columnList = "email, status")
}
)
- 声明复合(联合)索引时,依然是 B+Tree。
小结 :在 Spring Data JPA 里,正常使用 @Id
/@Column(unique=true)
/@Table(indexes=...)
就已经默认是 B+Tree,无需额外指定。
1.1.3 MyBatis-Plus 中如何使用 B+Tree 索引
MyBatis-Plus 并没有像 JPA 那样的注解去自动创建索引。常见做法有两种:
在数据库脚本或 Flyway/Liquibase 中显式创建
sql
CREATE TABLE user (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(100),
email VARCHAR(100),
status VARCHAR(20)
-- ...
) ENGINE=InnoDB;
-- 普通索引
CREATE INDEX idx_user_username ON user(username);
-- 唯一索引
CREATE UNIQUE INDEX uk_user_email ON user(email);
-- 联合索引
CREATE INDEX idx_user_email_status ON user(email, status);
- 只要引擎是 InnoDB/MyISAM,默认索引结构即 B+Tree。
在 Mapper 接口中执行原生 SQL
java
@Mapper
public interface UserMapper extends BaseMapper<User> {
@Select("CREATE INDEX idx_user_username ON user(username)")
void createIndexOnUsername();
@Select("CREATE UNIQUE INDEX uk_user_email ON user(email)")
void createUniqueIndexOnEmail();
@Select("CREATE INDEX idx_user_email_status ON user(email, status)")
void createIndexOnEmailStatus();
}
- 这会在运行时执行建索引的 SQL,底层同样是 B+Tree(只要数据引擎是 InnoDB/MyISAM)。
1.2 Hash 索引
1.2.1 特点与应用场景
- 特点 :
- 基于哈希表,只适合等值查询,不支持范围查询、排序等。
- 只在 MEMORY 存储引擎中可用(InnoDB 并不支持手动创建 Hash 索引)。
- 在生产环境中不常见,除非有极少量、频繁等值查询的数据。
1.2.2 Spring Data JPA 中的操作
- Spring Data JPA 没有专门的注解来指定 "Hash 索引"。
- 如果一定要在一个 MEMORY 表上创建 Hash 索引,需要先将表改成 MEMORY 引擎,再用原生 SQL 创建:
java
public interface UserRepository extends JpaRepository<User, Long> {
@Query(value = "ALTER TABLE user ENGINE=MEMORY", nativeQuery = true)
@Modifying
void convertToMemory();
@Query(value = "ALTER TABLE user ADD KEY USING HASH (username)", nativeQuery = true)
@Modifying
void createHashIndex();
}
- 这种情况十分少见,一般不做推荐。
1.2.3 MyBatis-Plus 中的操作
同样,需要先把表引擎改成 MEMORY,然后再执行建索引语句:
java
@Mapper
public interface UserMapper extends BaseMapper<User> {
@Select("ALTER TABLE user ENGINE=MEMORY")
void convertToMemoryEngine();
@Select("ALTER TABLE user ADD KEY USING HASH (username)")
void createHashIndexOnUsername();
}
- 由于场景极少,建议仅在极端情况下使用。
1.3 Full-Text 索引
1.3.1 特点与应用场景
- 特点 :
- 用于对文本字段(CHAR、VARCHAR、TEXT)进行全文检索。
- MyISAM 引擎最早支持,InnoDB 在 5.6+ 版本也支持。
- 需要配合
MATCH ... AGAINST
语法来进行全文搜索。
1.3.2 Spring Data JPA 中的操作
-
Spring Data JPA 没有"自动"创建全文索引的注解。
-
需要使用原生 SQL 来创建:
javapublic interface ArticleRepository extends JpaRepository<Article, Long> { @Query(value = "ALTER TABLE article ADD FULLTEXT INDEX idx_content(content)", nativeQuery = true) @Modifying void createFullTextIndexOnContent(); }
-
然后查询时:
java@Query(value = "SELECT * FROM article WHERE MATCH(content) AGAINST(?1 IN NATURAL LANGUAGE MODE)", nativeQuery = true) List<Article> searchByContent(String keywords);
1.3.3 MyBatis-Plus 中的操作
同理,在 MyBatis-Plus 中也需执行原生 SQL:
java
@Mapper
public interface ArticleMapper extends BaseMapper<Article> {
@Select("ALTER TABLE article ADD FULLTEXT INDEX idx_content(content)")
void createFullTextIndex();
@Select("SELECT * FROM article WHERE MATCH(content) AGAINST(#{keywords} IN NATURAL LANGUAGE MODE)")
List<Article> searchByContent(String keywords);
}
二、按「物理存储」分类
2.1 聚簇索引(Clustered Index / 主键索引)
2.1.1 特点与应用场景
-
InnoDB 中的主键索引即为聚簇索引
数据文件按主键顺序组织存储,主键索引本身就是一棵 B+Tree,叶子节点存的就是整行数据。
-
与其他分类维度的交叉
- 若显式声明了主键 (
PRIMARY KEY
),那么这就是表的聚簇索引。 - 如果主键是单列,则它既是单列索引;如果主键由多列组成,则是联合索引。
- 除了主键索引外,其他都是「二级索引」。
- 若显式声明了主键 (
2.1.2 Spring Data JPA 中的操作
-
只要 你在实体类中通过
@Id
声明主键,并使用 InnoDB 引擎,就会自动成为聚簇索引(无需额外设置):java@Entity @Table(name = "user") public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; // InnoDB主键 => 聚簇索引 private String username; // ... }
-
如果是联合主键 ,也依旧是聚簇索引,比如使用
@IdClass
或@EmbeddedId
。
2.1.3 MyBatis-Plus 中的操作
-
MyBatis-Plus 并不"帮"你创建表或索引,只要你在数据库里指定:
sqlCREATE TABLE user ( id BIGINT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(100), ... ) ENGINE=InnoDB;
- 这时
id
就是聚簇索引。
- 这时
-
在实体类中也会有对应的
@TableId
标识主键,但它并不自动建索引,而是依赖数据库表本身:java@TableName("user") public class User { @TableId(type = IdType.AUTO) private Long id; private String username; // ... }
2.2 二级索引(Secondary Index / 辅助索引)
2.2.1 特点与应用场景
-
辅助索引
- 在 InnoDB 中,非主键的索引(唯一索引、普通索引等)都属于二级索引。
- 二级索引是一棵独立的 B+Tree,叶子节点存的是对应行的「主键值」,需要再回表到聚簇索引查数据。
-
与其他分类维度的交叉
- 可以是唯一索引或普通索引,也可以是单列或联合,底层都是 B+Tree 结构。
- 比如
CREATE UNIQUE INDEX
、CREATE INDEX
都属于创建二级索引。
-
二级索引的叶子节点存储的是主键值 ,然后通过主键值再去聚簇索引定位行数据(回表)。
2.2.2 Spring Data JPA 中的操作
-
唯一索引 、普通索引 都是二级索引(只要不是主键)。
-
示例(普通索引):
java@Table( name = "user", indexes = { @Index(name = "idx_user_username", columnList = "username") } ) @Entity public class User { @Id private Long id; // 主键(聚簇) private String username; // 二级索引 // ... }
- 这里
idx_user_username
就是一个二级索引。
- 这里
2.2.3 MyBatis-Plus 中的操作
-
同样需要手动 写 SQL 或通过数据库脚本:
sqlCREATE INDEX idx_user_username ON user(username);
-
在 MyBatis-Plus 的实体中并没有注解去标明"这是二级索引",只能说非主键 的索引默认就是二级索引(InnoDB):
java@Select("CREATE INDEX idx_user_username ON user(username)") void createIndexUsername();
三、按「字段特性」分类
3.1 主键索引
3.1.1 特点与应用场景
- 唯一且不能为空
- 一张表只能有一个主键索引,它在 InnoDB 中就是聚簇索引。
- 与其他分类维度的交叉
- 底层数据结构:B+Tree;
- 也是聚簇索引;
- 可以是单列或多列(联合主键)。
- 主键不能为空,通常自增或 UUID 等。
3.1.2 Spring Data JPA 中的操作
-
直接
@Id
即是主键索引:java@Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id;
-
或者在表层面指定:
java@Entity @Table(name = "user") public class User { @Id private Long id; // ... }
3.1.3 MyBatis-Plus 中的操作
-
数据库里定义 PRIMARY KEY:
javaCREATE TABLE user ( id BIGINT PRIMARY KEY AUTO_INCREMENT, ... ) ENGINE=InnoDB;
-
实体类:
java@TableId(type = IdType.AUTO) private Long id;
-
就完成了主键索引的声明。
3.2 唯一索引
3.2.1 特点与应用场景
- 保证字段值不重复
- 允许多列一起唯一,也允许为 NULL(有些场景多个 NULL 可视为不冲突)。
- 与其他分类维度的交叉
- 底层通常是 B+Tree;
- 若非主键,则它就是一个「二级索引」。
- 可以是单列或联合索引。
3.2.2 Spring Data JPA 中的操作
单列唯一:
java
@Column(unique = true)
private String email;
- 这会自动生成一个唯一索引。
联合唯一:
java
@Table(
uniqueConstraints = {
@UniqueConstraint(name = "uk_user_email_username", columnNames = {"email", "username"})
}
)
@Entity
public class User {
@Id
private Long id;
private String email;
private String username;
}
- 会在
(email, username)
上创建一个唯一索引。
3.2.3 MyBatis-Plus 中的操作
-
同样需要手工 或脚本:
sqlCREATE UNIQUE INDEX uk_user_email ON user(email); CREATE UNIQUE INDEX uk_user_email_username ON user(email, username);
-
或在 Mapper 接口中:
java@Select("CREATE UNIQUE INDEX uk_user_email ON user(email)") void createUniqueIndexEmail(); @Select("CREATE UNIQUE INDEX uk_user_email_username ON user(email, username)") void createUniqueIndexEmailUsername();
3.3 普通索引
3.3.1 特点与应用场景
- 最常见,提升查询速度,没有唯一性约束。
- 可用于经常在 WHERE 条件或 JOIN 条件中的列。
3.3.2 Spring Data JPA 中的操作
-
通过
@Table(indexes = {...})
:java@Table( name = "user", indexes = { @Index(name = "idx_user_username", columnList = "username") } ) @Entity public class User { @Id private Long id; private String username; // ... }
3.3.3 MyBatis-Plus 中的操作
-
还是手动 SQL:
sqlCREATE INDEX idx_user_username ON user(username);
-
或者在 Mapper 接口:
sql@Select("CREATE INDEX idx_user_username ON user(username)") void createIndexUsername();
3.4 前缀索引
3.4.1 特点与应用场景
- 对字符串类型的前 N 个字符建立索引,减少索引体积,适合超长字段。
- 会降低搜索精度(可能会有更多回表操作),仅在部分情况下使用。
3.4.2 Spring Data JPA 中的操作
-
没有 注解可指定 "前缀长度",需要原生 SQL 或 DDL 脚本:
sqlpublic interface UserRepository extends JpaRepository<User, Long> { @Query(value = "CREATE INDEX idx_prefix_username ON user (username(10))", nativeQuery = true) @Modifying void createPrefixIndex(); }
-
或者在建表脚本里提前写:
sqlCREATE INDEX idx_prefix_username ON user (username(10));
3.4.3 MyBatis-Plus 中的操作
-
同理,原生 SQL :
java@Select("CREATE INDEX idx_prefix_username ON user(username(10))") void createPrefixIndex();
-
或者在数据库层面定义。
四、按「字段个数」分类
4.1 单列索引
4.1.1 特点与应用场景
针对单个字段创建
- 可以是主键索引、唯一索引或普通索引。
- 一张表可以有多个不同字段的单列索引。
4.1.2 Spring Data JPA 中的操作
-
常见写法:
java@Table( indexes = @Index(name="idx_user_username", columnList="username") ) @Entity public class User { @Id private Long id; // username 单列索引 private String username; }
4.1.3 MyBatis-Plus 中的操作
-
依旧SQL 或 Mapper :
java@Select("CREATE INDEX idx_user_username ON user(username)") void createIndexUsername();
4.2 联合索引(复合索引)
4.2.1 特点与应用场景
针对多个字段的组合创建
- 可以是联合主键索引、联合唯一索引或普通联合索引。
- 在联合索引中,需要注意"最左前缀原则",即只有在按"索引最左字段"进行查询时,索引才会被有效利用。
4.2.2 Spring Data JPA 中的操作
-
在
@Index
的columnList
中写多个字段:java@Table( indexes = { @Index(name = "idx_user_email_status", columnList = "email, status") } ) @Entity public class User { @Id private Long id; private String email; private String status; // ... }
-
或在
@UniqueConstraint(columnNames = {"col1", "col2"})
中声明联合唯一。
最左前缀原则: 从左到右依次匹配索引列,中间不能跳过;一旦遇到范围查询或被跳过的列,后续列就无法继续利用联合索引进行快速定位 。因此,在设计联合索引时,要结合最常用的查询方式,把过滤频率最高 或最常作为等值过滤 的列放到最左,才能最大化利用索引加速查询。
javaCREATE INDEX idx_col1_col2_col3 ON table_name(col1, col2, col3);
必须从索引的最左列开始匹配
- 以上例中,最左列是
col1
。如果在查询时 没有 使用col1
进行过滤或连接条件,那么即便条件中包含了col2
、col3
,也无法有效利用这个联合索引。- 例如,
WHERE col1 = ...
可以用索引;WHERE col1 = ... AND col2 = ...
可以继续利用索引更深层次的匹配;但如果只是WHERE col2 = ...
,MySQL 无法使用idx_col1_col2_col3
的第一列col1
进行定位,就不能走该索引的高效检索。索引可部分匹配,但只能持续到出现"断裂"
- 对联合索引
(col1, col2, col3)
来说,如果查询只使用了col1
,MySQL 仍能使用索引里(col1)
这部分来加速查询。- 如果查询中使用了
col1
和col2
,但忽略了col3
,那么 MySQL 可以利用索引的前两列(col1, col2)
进行匹配。- 如果跳过 了中间一列,比如只用
col1
和col3
(而col2
不出现在查询条件中),那么对于col3
的过滤很难直接走这条索引,因为在索引排序里col2
位于col1
和col3
中间,造成"断裂"。对范围条件的影响
- 若在
col1
上使用了范围查询(如col1 BETWEEN 10 AND 20
),则只能对col1
进行索引检索,并且无法 再对后续col2
、col3
继续使用索引精准匹配。因为范围扫描一旦开始,后续的精准匹配就无法发挥作用。- 这也是最左前缀原则的延伸:联合索引后续字段的利用,往往需要前一列是等值匹配(
=
)才能继续深度使用。
4.2.3 MyBatis-Plus 中的操作
-
还是原生 SQL:
sqlCREATE INDEX idx_user_email_status ON user(email, status);
-
或者:
java@Select("CREATE INDEX idx_user_email_status ON user(email, status)") void createUnionIndexOnEmailStatus();
五、小结
-
实际开发中最常用的索引:
- B+Tree 实现的主键索引、唯一索引、普通索引和联合索引。这基本覆盖了 90%+ 的应用场景。
- 在 Spring Data JPA 中,利用
@Id
、@Column(unique=true)
、@Table(indexes=..., uniqueConstraints=...)
已能满足绝大部分需求。 - 在 MyBatis-Plus 中,多使用数据库脚本或 Mapper 接口原生 SQL 来管理索引。
-
Hash 索引
- 因为只能在 MEMORY 引擎下使用,而且限制很多(不支持范围查询、无法排序等),生产中极少使用。除非需要一些极端高并发且数据量很小、仅仅等值查询的场景。
-
Full-Text 索引
- 如果需要"搜索功能",可以考虑 MySQL 自带全文索引。但通常更推荐使用专业搜索引擎(如 Elasticsearch、Solr 等),在索引和查询性能上更强大。
- 如果只是简单的搜索,可以用 MySQL Full-Text 索引,但要掌握
MATCH ... AGAINST
的使用和各种搜索模式。
-
聚簇索引 = InnoDB 主键索引,只有一个;二级索引 = 非主键索引(可唯一可普通可联合)。
-
主键、唯一、普通、前缀 这四类索引可以任意组合到单列或多列中,但前缀索引只对字符串列有效。
-
Spring Data JPA 提供了一些方便的注解:
@Id
/@Column(unique=true)
/@Table(indexes=..., uniqueConstraints=...)
- 特殊索引(Hash、Full-Text、前缀)要用原生 SQL。
-
MyBatis-Plus 不会自动创建索引,需要:
- SQL脚本 / Mapper接口中原生SQL / DB初始化工具(Flyway, Liquibase)
- 只要数据库层面创建了对应索引,即可在 MyBatis-Plus 中正常使用。