deno.com
在本頁面上

次要索引

像是 Deno KV 的鍵值儲存將資料組織為鍵值對的集合,其中每個唯一鍵都與單一值相關聯。這種結構可以根據鍵輕鬆檢索值,但不允許根據值本身進行查詢。為了克服這個限制,您可以建立次要索引,將相同的值儲存在包含(部分)該值的額外鍵下。

當使用次要索引時,維護主要鍵和次要鍵之間的一致性至關重要。如果在更新次要鍵的情況下更新了主要鍵中的值,則從針對次要鍵的查詢返回的資料將不正確。為了確保主要鍵和次要鍵始終代表相同的資料,請在插入、更新或刪除資料時使用原子操作。這種方法確保突變操作組作為單個單元執行,並且要么全部成功要么全部失敗,從而防止不一致。

唯一索引 (一對一) 跳到標題

唯一索引中的每個鍵都與恰好一個主要鍵相關聯。例如,當儲存使用者資料並通過其唯一 ID 和電子郵件地址查找使用者時,請在兩個不同的鍵下儲存使用者資料:一個用於主要鍵(使用者 ID),另一個用於次要索引(電子郵件)。這種設定允許根據使用者的 ID 或電子郵件查詢使用者。次要索引還可以對商店中的值強制執行唯一性約束。在使用者資料的情況下,使用索引來確保每個電子郵件地址僅與一個使用者關聯 - 換句話說,電子郵件是唯一的。

要為此範例實作唯一次要索引,請按照下列步驟操作

  1. 建立一個代表資料的 User 介面

    interface User {
      id: string;
      name: string;
      email: string;
    }
    
  2. 定義一個 insertUser 函數,該函數將使用者資料儲存在主要鍵和次要鍵中

    async function insertUser(user: User) {
      const primaryKey = ["users", user.id];
      const byEmailKey = ["users_by_email", user.email];
      const res = await kv.atomic()
        .check({ key: primaryKey, versionstamp: null })
        .check({ key: byEmailKey, versionstamp: null })
        .set(primaryKey, user)
        .set(byEmailKey, user)
        .commit();
      if (!res.ok) {
        throw new TypeError("User with ID or email already exists");
      }
    }
    

    此函數使用原子操作執行插入,以檢查是否已存在具有相同 ID 或電子郵件的使用者。如果違反了這些約束中的任何一個,則插入將失敗,並且不會修改任何資料。

  3. 定義一個 getUser 函數,以通過其 ID 檢索使用者

    async function getUser(id: string): Promise<User | null> {
      const res = await kv.get<User>(["users", id]);
      return res.value;
    }
    
  4. 定義一個 getUserByEmail 函數,以通過其電子郵件地址檢索使用者

    async function getUserByEmail(email: string): Promise<User | null> {
      const res = await kv.get<User>(["users_by_email", email]);
      return res.value;
    }
    

    此函數使用次要鍵 (`["users_by_email", email]`) 查詢商店。

  5. 定義一個 deleteUser 函數,以通過其 ID 刪除使用者

    async function deleteUser(id: string) {
      let res = { ok: false };
      while (!res.ok) {
        const getRes = await kv.get<User>(["users", id]);
        if (getRes.value === null) return;
        res = await kv.atomic()
          .check(getRes)
          .delete(["users", id])
          .delete(["users_by_email", getRes.value.email])
          .commit();
      }
    }
    

    此函數首先通過其 ID 檢索使用者以獲取使用者的電子郵件地址。這是檢索電子郵件所必需的,該電子郵件是用於構建此使用者地址的次要索引的鍵所必需的。然後,它執行原子操作,檢查資料庫中的使用者是否未更改,然後刪除指向使用者值的主要鍵和次要鍵。如果此操作失敗(使用者在查詢和刪除之間已被修改),則原子操作中止。將重試整個過程,直到刪除成功。需要進行檢查以防止在檢索和刪除之間值可能已被修改的競爭條件。如果更新更改了使用者的電子郵件,則可能會發生這種競爭,因為在這種情況下次要索引會移動。然後,次要索引的刪除將失敗,因為刪除的目標是舊的次要索引鍵。

非唯一索引 (一對多) 跳到標題

非唯一索引是次要索引,其中單個鍵可以與多個主要鍵關聯,從而允許您根據共享屬性查詢多個項目。例如,當按使用者最喜歡的顏色查詢使用者時,請使用非唯一次要索引實作此功能。最喜歡的顏色是非唯一屬性,因為多個使用者可以具有相同最喜歡的顏色。

要為此範例實作非唯一次要索引,請按照下列步驟操作

  1. 定義 User 介面

    interface User {
      id: string;
      name: string;
      favoriteColor: string;
    }
    
  2. 定義 insertUser 函數

    async function insertUser(user: User) {
      const primaryKey = ["users", user.id];
      const byColorKey = [
        "users_by_favorite_color",
        user.favoriteColor,
        user.id,
      ];
      await kv.atomic()
        .check({ key: primaryKey, versionstamp: null })
        .set(primaryKey, user)
        .set(byColorKey, user)
        .commit();
    }
    
  3. 定義一個函數,以通過其最喜歡的顏色檢索使用者

    async function getUsersByFavoriteColor(color: string): Promise<User[]> {
      const iter = kv.list<User>({ prefix: ["users_by_favorite_color", color] });
      const users = [];
      for await (const { value } of iter) {
        users.push(value);
      }
      return users;
    }
    

此範例示範了非唯一次要索引 `users_by_favorite_color` 的使用,該索引允許根據使用者最喜歡的顏色查詢使用者。主要鍵仍然是使用者 ID。

唯一索引和非唯一索引實作之間的主要區別在於次要鍵的結構和組織。在唯一索引中,每個次要鍵都與恰好一個主要鍵相關聯,從而確保索引屬性在所有記錄中都是唯一的。在非唯一索引的情況下,單個次要鍵可以與多個主要鍵相關聯,因為索引屬性可能在多個記錄之間共享。為了實現這一點,非唯一次要鍵通常以附加的唯一標識符(例如,主要鍵)作為鍵的一部分來構建,從而允許多個具有相同屬性的記錄共存而不會發生衝突。

你找到需要的資訊了嗎?

隱私權政策