杰瑞科技汇

java mongodb 索引

为什么需要索引?

在深入代码之前,我们必须理解索引的重要性,索引是 MongoDB 中用于查询优化的一种数据结构,可以极大地提高数据检索速度。

  • 无索引的情况:当执行一个查询(如 db.users.find({"name": "张三"}))时,MongoDB 必须扫描整个 users 集合中的所有文档,这个过程称为全表扫描,如果集合中有数百万个文档,查询会非常慢。
  • 有索引的情况:如果在 name 字段上创建了索引,MongoDB 会维护一个按照 name 字段值排序的 B-Tree 索引结构,当查询 name 时,MongoDB 可以快速定位到目标文档,而无需扫描整个集合,就像在书的目录中快速找到页码一样。

索引的代价

  • 写入开销:每次插入、更新或删除文档时,MongoDB 都需要同时更新索引,这会降低写入性能。
  • 存储空间:索引需要占用额外的磁盘空间。

索引是一把双刃剑,需要根据查询模式来合理创建。


Java 驱动中的核心 API

在 Java 中,我们使用 MongoCollection 对象来操作索引,主要的 API 都在这个对象上。

1 创建索引

最常用的方法是 createIndex()

// 创建一个在 "name" 字段上的升序索引
collection.createIndex(new Document("name", 1));
  • Document: 用于定义索引的字段和排序方向。
  • 1: 表示升序。
  • -1: 表示降序。

2 创建复合索引

复合索引是建立在多个字段上的索引,对于多字段查询非常有效。

// 创建一个复合索引,先按 "category" 升序,再按 "price" 降序
collection.createIndex(new Document("category", 1).append("price", -1));

3 创建唯一索引

唯一索引确保集合中在索引字段上的值是唯一的,类似于关系型数据库中的 UNIQUE 约束。

// 确保 "email" 字段的值是唯一的
collection.createIndex(new Document("email", 1), new IndexOptions().unique(true));

4 创建稀疏索引

稀疏索引只会为包含索引字段的文档创建索引条目,这对于那些大部分文档都缺少某个字段(如 optional_field)的场景非常有用,可以节省大量空间。

// 只为包含 "optional_field" 的文档创建索引
collection.createIndex(new Document("optional_field", 1), new IndexOptions().sparse(true));

5 创建 TTL (Time-To-Live) 索引

TTL 索引用于在指定时间后自动删除文档,常用于实现缓存、会话日志等场景。

  • 注意:TTL 索引只能用于日期类型(Date)的字段。
// 在 "createdAt" 字段上创建 TTL 索引,300秒(5分钟)后自动删除文档
collection.createIndex(
    new Document("createdAt", 1),
    new IndexOptions().expireAfter(300L, TimeUnit.SECONDS)
);

6 获取索引信息

你可以查看集合上所有已创建的索引信息。

// 获取集合的所有索引
ListIndexesIterable<Document> indexes = collection.listIndexes();
// 遍历并打印索引信息
for (Document index : indexes) {
    System.out.println(index.toJson());
}

7 删除索引

可以根据索引名称或索引键来删除索引。

// 方法1: 通过索引名称删除
// 注意:默认创建的 _id 索引名称是 "_id_"
collection.dropIndex("name_1");
// 方法2: 通过索引键删除
collection.dropIndex(new Document("name", 1));

8 重命名索引

MongoDB 5.0+ 支持重命名索引。

// 将 "name_1" 重命名为 "user_name_index"
collection.renameIndex("name_1", "user_name_index");

完整的 Java 示例

下面是一个完整的 Java 程序,演示了如何连接到 MongoDB 并执行上述各种索引操作。

1 准备工作

  1. 启动 MongoDB:确保你的 MongoDB 服务正在运行。
  2. 添加 Java 驱动依赖:如果你使用 Maven,在 pom.xml 中添加:
    <dependency>
        <groupId>org.mongodb</groupId>
        <artifactId>mongodb-driver-sync</artifactId>
        <version>4.11.1</version> <!-- 使用最新稳定版本 -->
    </dependency>

2 示例代码

import com.mongodb.client.*;
import com.mongodb.client.model.Indexes;
import com.mongodb.client.model.IndexOptions;
import org.bson.Document;
import java.util.Arrays;
public class MongoDBIndexExample {
    public static void main(String[] args) {
        // 1. 创建 MongoDB 客户端并连接到服务器
        String uri = "mongodb://localhost:27017";
        try (MongoClient mongoClient = MongoClients.create(uri)) {
            // 2. 获取数据库和集合
            MongoDatabase database = mongoClient.getDatabase("testdb");
            MongoCollection<Document> collection = database.getCollection("users");
            // 为了演示,先清空集合
            collection.drop();
            // 3. 插入一些示例数据
            collection.insertMany(Arrays.asList(
                new Document("name", "Alice").append("age", 30).append("city", "New York").append("email", "alice@example.com"),
                new Document("name", "Bob").append("age", 25).append("city", "London"),
                new Document("name", "Charlie").append("age", 35).append("city", "New York").append("email", "charlie@example.com"),
                new Document("name", "David").append("age", 40).append("city", "Paris")
            ));
            System.out.println("插入示例数据完成。");
            // --- 索引操作 ---
            // 4. 创建单字段索引
            System.out.println("\n--- 创建单字段索引 ---");
            collection.createIndex(Indexes.ascending("name"));
            System.out.println("在 'name' 字段上创建升序索引成功。");
            // 5. 创建复合索引
            System.out.println("\n--- 创建复合索引 ---");
            collection.createIndex(Indexes.compoundIndex(Indexes.ascending("city"), Indexes.descending("age")));
            System.out.println("在 'city' (升序) 和 'age' (降序) 上创建复合索引成功。");
            // 6. 创建唯一索引
            System.out.println("\n--- 创建唯一索引 ---");
            collection.createIndex(Indexes.ascending("email"), new IndexOptions().unique(true));
            System.out.println("在 'email' 字段上创建唯一索引成功。");
            // 尝试插入重复 email 的文档会报错
            try {
                collection.insertOne(new Document("name", "Eve").append("email", "alice@example.com"));
            } catch (Exception e) {
                System.out.println("插入重复 email 失败,这是预期的行为: " + e.getMessage());
            }
            // 7. 获取并打印所有索引信息
            System.out.println("\n--- 集合中的所有索引 ---");
            collection.listIndexes().forEach(doc -> System.out.println(doc.toJson()));
            // 8. 删除索引
            System.out.println("\n--- 删除索引 ---");
            // 默认创建的 _id 索引名称是 "_id_"
            collection.dropIndex("_id_"); // 通常不建议删除 _id 索引
            collection.dropIndex("name_1"); // 通过名称删除
            System.out.println("删除 'name_1' 索引成功。");
            // 9. 再次查看索引,确认删除
            System.out.println("\n--- 删除后的索引列表 ---");
            collection.listIndexes().forEach(doc -> System.out.println(doc.toJson()));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

最佳实践

  1. 为常用查询字段创建索引:分析你的应用程序,找出 find(), sort(), skip(), limit() 操作中频繁使用的字段,并为它们创建索引。
  2. 复合索引的顺序很重要:在 {"fieldA": 1, "fieldB": 1} 这样的复合索引中,它能够高效支持:
    • {"fieldA": ...}
    • {"fieldA": ..., "fieldB": ...}
    • {"fieldB": ...} (效率较低,因为索引不是为这个查询优化的)
    • 最佳实践:将高选择性(区分度高)的字段放在索引的前面。
  3. 避免过多索引:不要为所有字段都创建索引,索引会增加写入开销和存储空间,只创建真正需要的索引。
  4. 使用 explain() 分析查询:在开发或调试阶段,使用 explain() 方法来检查你的查询是否有效地使用了索引。
    // 在 Java 中使用 explain
    Document query = new Document("city", "New York");
    Document sort = new Document("age", -1);
    collection.find(query).sort(sort).explain().forEach(doc -> System.out.println(doc.toJson()));

    查看 explain() 输出中的 winningPlan.executionStats,特别是 executionTimeMillistotalDocsExamined,一个好的查询应该有很低的 totalDocsExamined 值。

  5. 考虑后台创建索引:对于大型集合,创建索引可能会阻塞数据库的读写操作,可以使用 background: true 选项在后台创建索引,但这会减慢索引创建的速度。
    collection.createIndex(Indexes.ascending("large_field"), new IndexOptions().background(true));

索引是 MongoDB 性能优化的核心,通过 Java 驱动,你可以灵活地创建、管理和删除索引来满足你的应用需求,关键在于分析查询模式,并有策略地创建索引,以在查询性能和写入性能之间取得平衡。

分享:
扫描分享到社交APP
上一篇
下一篇