@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 = { ... }
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 = { ... }
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)
private String email;
特点:
BETWEEN
, <
, >
, ORDER BY
, 等)。与其他分类的交叉:
在 InnoDB 引擎下,所有主键、唯一索引、普通索引默认就是 B+Tree 实现。只要我们在实体类里用以下方式指定索引,即可默认得到 B+Tree:
主键索引(B+Tree + 聚簇)
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
// ...
}
唯一索引(B+Tree 二级索引)
@Column(unique = true)
private String email;
email
字段创建一个唯一索引,同样是 B+Tree。普通索引(B+Tree 二级索引)
@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)
@Table(
indexes = {
@Index(name = "idx_user_email_status", columnList = "email, status")
}
)
小结:在 Spring Data JPA 里,正常使用 @Id
/@Column(unique=true)
/@Table(indexes=...)
就已经默认是 B+Tree,无需额外指定。
MyBatis-Plus 并没有像 JPA 那样的注解去自动创建索引。常见做法有两种:
在数据库脚本或 Flyway/Liquibase 中显式创建
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);
在 Mapper 接口中执行原生 SQL
@Mapper
public interface UserMapper extends BaseMapper {
@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();
}
public interface UserRepository extends JpaRepository {
@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();
}
同样,需要先把表引擎改成 MEMORY,然后再执行建索引语句:
@Mapper
public interface UserMapper extends BaseMapper {
@Select("ALTER TABLE user ENGINE=MEMORY")
void convertToMemoryEngine();
@Select("ALTER TABLE user ADD KEY USING HASH (username)")
void createHashIndexOnUsername();
}
MATCH ... AGAINST
语法来进行全文搜索。public interface ArticleRepository extends JpaRepository {
@Query(value = "ALTER TABLE article ADD FULLTEXT INDEX idx_content(content)", nativeQuery = true)
@Modifying
void createFullTextIndexOnContent();
}
@Query(value = "SELECT * FROM article WHERE MATCH(content) AGAINST(?1 IN NATURAL LANGUAGE MODE)", nativeQuery = true)
List searchByContent(String keywords);
同理,在 MyBatis-Plus 中也需执行原生 SQL:
@Mapper
public interface ArticleMapper extends BaseMapper {
@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 searchByContent(String keywords);
}
InnoDB 中的主键索引即为聚簇索引
数据文件按主键顺序组织存储,主键索引本身就是一棵 B+Tree,叶子节点存的就是整行数据。
与其他分类维度的交叉
PRIMARY KEY
),那么这就是表的聚簇索引。@Id
声明主键,并使用 InnoDB 引擎,就会自动成为聚簇索引(无需额外设置): @Entity
@Table(name = "user")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id; // InnoDB主键 => 聚簇索引
private String username;
// ...
}
@IdClass
或 @EmbeddedId
。CREATE TABLE user (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(100),
...
) ENGINE=InnoDB;
id
就是聚簇索引。@TableId
标识主键,但它并不自动建索引,而是依赖数据库表本身: @TableName("user")
public class User {
@TableId(type = IdType.AUTO)
private Long id;
private String username;
// ...
}
辅助索引
与其他分类维度的交叉
CREATE UNIQUE INDEX
、CREATE INDEX
都属于创建二级索引。@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
就是一个二级索引。CREATE INDEX idx_user_username ON user(username);
@Select("CREATE INDEX idx_user_username ON user(username)")
void createIndexUsername();
@Id
即是主键索引: @Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Entity
@Table(name = "user")
public class User {
@Id
private Long id;
// ...
}
CREATE TABLE user (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
...
) ENGINE=InnoDB;
@TableId(type = IdType.AUTO)
private Long id;
单列唯一:
@Column(unique = true)
private String email;
联合唯一:
@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)
上创建一个唯一索引。CREATE UNIQUE INDEX uk_user_email ON user(email);
CREATE UNIQUE INDEX uk_user_email_username ON user(email, username);
@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();
@Table(indexes = {...})
: @Table(
name = "user",
indexes = {
@Index(name = "idx_user_username", columnList = "username")
}
)
@Entity
public class User {
@Id
private Long id;
private String username;
// ...
}
CREATE INDEX idx_user_username ON user(username);
@Select("CREATE INDEX idx_user_username ON user(username)")
void createIndexUsername();
public interface UserRepository extends JpaRepository {
@Query(value = "CREATE INDEX idx_prefix_username ON user (username(10))", nativeQuery = true)
@Modifying
void createPrefixIndex();
}
CREATE INDEX idx_prefix_username ON user (username(10));
@Select("CREATE INDEX idx_prefix_username ON user(username(10))")
void createPrefixIndex();
针对单个字段创建
@Table(
indexes = @Index(name="idx_user_username", columnList="username")
)
@Entity
public class User {
@Id
private Long id;
// username 单列索引
private String username;
}
@Select("CREATE INDEX idx_user_username ON user(username)")
void createIndexUsername();
针对多个字段的组合创建
@Index
的 columnList
中写多个字段: @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"})
中声明联合唯一。最左前缀原则:从左到右依次匹配索引列,中间不能跳过;一旦遇到范围查询或被跳过的列,后续列就无法继续利用联合索引进行快速定位。因此,在设计联合索引时,要结合最常用的查询方式,把过滤频率最高或最常作为等值过滤的列放到最左,才能最大化利用索引加速查询。
CREATE 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
继续使用索引精准匹配。因为范围扫描一旦开始,后续的精准匹配就无法发挥作用。- 这也是最左前缀原则的延伸:联合索引后续字段的利用,往往需要前一列是等值匹配(
=
)才能继续深度使用。
CREATE INDEX idx_user_email_status ON user(email, status);
@Select("CREATE INDEX idx_user_email_status ON user(email, status)")
void createUnionIndexOnEmailStatus();
实际开发中最常用的索引:
@Id
、@Column(unique=true)
、@Table(indexes=..., uniqueConstraints=...)
已能满足绝大部分需求。Hash 索引
Full-Text 索引
MATCH ... AGAINST
的使用和各种搜索模式。聚簇索引 = InnoDB 主键索引,只有一个;二级索引 = 非主键索引(可唯一可普通可联合)。
主键、唯一、普通、前缀 这四类索引可以任意组合到单列或多列中,但前缀索引只对字符串列有效。
Spring Data JPA 提供了一些方便的注解:
@Id
/ @Column(unique=true)
/ @Table(indexes=..., uniqueConstraints=...)
MyBatis-Plus 不会自动创建索引,需要: