目录

第八部分:数据库版本升级

8.1 版本升级概述

IndexedDB 通过版本号管理数据库结构。当需要添加对象存储、创建索引或修改结构时,必须增加版本号触发升级流程。

8.1.1 为什么需要版本升级

应用发布后,需求会不断变化:

8.1.2 升级流程

1. 调用 indexedDB.open(dbName, newVersion)
2. 如果 newVersion > currentVersion,触发 onupgradeneeded
3. 在 onupgradeneeded 中执行结构变更
4. 升级完成后触发 onsuccess
5. 如果其他页面持有旧版本连接,触发 onblocked

8.2 升级策略

8.2.1 基本升级

const request = indexedDB.open('MyApp', 2);
 
request.onupgradeneeded = function(event) {
  const db = event.target.result;
  const oldVersion = event.oldVersion;
 
  console.log(`升级: 版本 ${oldVersion} -> 2`);
 
  if (oldVersion < 1) {
    // 首次创建
    const store = db.createObjectStore('users', { keyPath: 'id' });
    store.createIndex('emailIndex', 'email', { unique: true });
  }
 
  if (oldVersion < 2) {
    // 版本 1 -> 2 的升级
    const store = request.transaction.objectStore('users');
    store.createIndex('createdAtIndex', 'createdAt', { unique: false });
  }
};

8.2.2 多版本递进升级

request.onupgradeneeded = function(event) {
  const db = event.target.result;
  const oldVersion = event.oldVersion;
  const transaction = event.target.transaction;
 
  // 从旧版本逐步升级到新版本
  if (oldVersion < 1) {
    // v0 -> v1
    db.createObjectStore('users', { keyPath: 'id' });
  }
 
  if (oldVersion < 2) {
    // v1 -> v2
    const store = transaction.objectStore('users');
    store.createIndex('nameIndex', 'name');
  }
 
  if (oldVersion < 3) {
    // v2 -> v3
    const store = transaction.objectStore('users');
    store.createIndex('ageIndex', 'age');
  }
 
  if (oldVersion < 4) {
    // v3 -> v4
    // 新增对象存储
    db.createObjectStore('orders', { keyPath: 'orderId' });
  }
 
  if (oldVersion < 5) {
    // v4 -> v5
    // 删除旧索引
    const store = transaction.objectStore('users');
    if (store.indexNames.contains('tempIndex')) {
      store.deleteIndex('tempIndex');
    }
  }
};

8.3 数据迁移

8.3.1 字段重命名

request.onupgradeneeded = function(event) {
  const db = event.target.result;
  const transaction = event.target.transaction;
 
  if (event.oldVersion < 3) {
    const store = transaction.objectStore('users');
 
    // 迁移:将 'name' 重命名为 'fullName'
    const cursor = store.openCursor();
 
    cursor.onsuccess = function(e) {
      const result = e.target.result;
      if (result) {
        const user = result.value;
        if (user.name && !user.fullName) {
          user.fullName = user.name;
          delete user.name;
          result.update(user);
        }
        result.continue();
      }
    };
  }
};

8.3.2 数据转换

request.onupgradeneeded = function(event) {
  const db = event.target.result;
  const transaction = event.target.transaction;
 
  if (event.oldVersion < 4) {
    const store = transaction.objectStore('users');
 
    // 迁移:将年龄从字符串转为数字
    store.openCursor().onsuccess = function(e) {
      const cursor = e.target.result;
      if (cursor) {
        const user = cursor.value;
        if (typeof user.age === 'string') {
          user.age = parseInt(user.age, 10);
          cursor.update(user);
        }
        cursor.continue();
      }
    };
  }
};

8.3.3 对象存储迁移

request.onupgradeneeded = function(event) {
  const db = event.target.result;
  const transaction = event.target.transaction;
 
  if (event.oldVersion < 5) {
    // 创建新的对象存储
    const newStore = db.createObjectStore('users_v2', { keyPath: 'id' });
    newStore.createIndex('emailIndex', 'email', { unique: true });
 
    // 从旧存储迁移数据
    const oldStore = transaction.objectStore('users');
 
    oldStore.openCursor().onsuccess = function(e) {
      const cursor = e.target.result;
      if (cursor) {
        const oldUser = cursor.value;
 
        // 数据转换
        const newUser = {
          id: oldUser.id,
          email: oldUser.email,
          profile: {
            firstName: oldUser.firstName,
            lastName: oldUser.lastName,
            age: oldUser.age
          },
          migratedAt: new Date().toISOString()
        };
 
        newStore.add(newUser);
        cursor.continue();
      } else {
        // 迁移完成,删除旧存储
        db.deleteObjectStore('users');
      }
    };
  }
};

8.4 处理阻塞

8.4.1 onblocked 事件

const request = indexedDB.open('MyApp', 3);
 
request.onblocked = function(event) {
  console.warn('数据库升级被阻塞');
  console.warn('请关闭其他使用此数据库的标签页');
 
  // 可以提示用户
  showNotification('请刷新页面以完成更新');
};
 
request.onupgradeneeded = function(event) {
  // 升级逻辑
};
 
request.onsuccess = function(event) {
  console.log('数据库升级成功');
};

8.4.2 优雅处理多个标签页

// 方案:在页面卸载时关闭连接
window.addEventListener('beforeunload', () => {
  if (db) {
    db.close();
  }
});
 
// 方案:使用 BroadcastChannel 通知其他标签页
const channel = new BroadcastChannel('db_upgrade');
 
function upgradeDatabase(newVersion) {
  channel.postMessage({ type: 'upgrading', version: newVersion });
 
  const request = indexedDB.open('MyApp', newVersion);
 
  request.onblocked = () => {
    channel.postMessage({ type: 'blocked' });
  };
 
  request.onsuccess = () => {
    channel.postMessage({ type: 'completed', version: newVersion });
  };
}
 
channel.onmessage = (event) => {
  if (event.data.type === 'upgrading') {
    // 其他标签页正在升级,主动关闭连接
    if (db) {
      db.close();
      db = null;
    }
  }
};

8.5 版本管理最佳实践

8.5.1 版本号策略

// 策略一:线性递增
const DB_VERSION = 5;
 
// 策略二:使用日期作为版本号
const DB_VERSION = 20240423; // YYYYMMDD
 
// 策略三:语义化版本映射
// 应用版本 2.1.3 -> 数据库版本 213
function appVersionToDbVersion(major, minor, patch) {
  return major * 100 + minor * 10 + patch;
}

8.5.2 升级代码组织

const migrations = {
  1: function(db, transaction) {
    db.createObjectStore('users', { keyPath: 'id' });
  },
  2: function(db, transaction) {
    const store = transaction.objectStore('users');
    store.createIndex('emailIndex', 'email', { unique: true });
  },
  3: function(db, transaction) {
    const store = transaction.objectStore('users');
    store.createIndex('createdAtIndex', 'createdAt');
  },
  4: function(db, transaction) {
    db.createObjectStore('settings', { keyPath: 'key' });
  }
};
 
request.onupgradeneeded = function(event) {
  const db = event.target.result;
  const transaction = event.target.transaction;
  const oldVersion = event.oldVersion;
 
  for (let version = oldVersion + 1; version <= event.newVersion; version++) {
    if (migrations[version]) {
      console.log(`执行迁移: 版本 ${version}`);
      migrations[version](db, transaction);
    }
  }
};

8.6 本章小结

本章介绍了数据库版本升级:

继续阅读 第九部分:高级特性