roblox-datastores

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Roblox DataStores

Roblox DataStores

Persist data across sessions in Roblox with
DataStoreService
: loading on join, saving on leave and shutdown, safe updates, retries, and ordered stores for leaderboards. Server-side only.
在Roblox中使用
DataStoreService
跨会话持久化数据:实现加入时加载、离开和服务器关闭时保存、安全更新、重试,以及用于排行榜的有序存储。仅支持服务器端。

When to use

适用场景

  • Use to save/load player progress (coins, inventory, levels), build persistent leaderboards, or fix data loss, overwrites, and throttling.
  • Use when server code calls
    DataStoreService
    ,
    GetDataStore
    ,
    GetAsync
    ,
    SetAsync
    ,
    UpdateAsync
    , or
    GetOrderedDataStore
    .
When not to use: general scripting, services, remotes, the client/server split →
roblox-luau
. High-frequency temporary state (matchmaking, per-round) → memory stores (a different service). Engine-agnostic persistence theory →
save-systems
.
  • 用于保存/加载玩家进度(金币、背包、等级)、构建持久化排行榜,或解决数据丢失、覆盖和限流问题。
  • 当服务器代码调用
    DataStoreService
    GetDataStore
    GetAsync
    SetAsync
    UpdateAsync
    GetOrderedDataStore
    时使用。
不适用场景: 通用脚本编写、服务、远程调用、客户端/服务器分离 → 请使用
roblox-luau
。高频临时状态(匹配、单回合数据)→ 使用内存存储(另一项服务)。与引擎无关的持久化理论 → 使用
save-systems

Core workflow

核心工作流程

  1. Enable Studio access once. File → Game Settings → Security → Enable Studio Access to API Services (use a test place; Studio hits live data). DataStores work only from server
    Script
    s, never
    LocalScript
    s.
  2. Get a store, then read/write by key.
    DataStoreService:GetDataStore("Name")
    ; key per player is usually
    "Player_" .. player.UserId
    .
  3. Wrap every call in
    pcall
    .
    GetAsync
    /
    SetAsync
    /
    UpdateAsync
    are network calls that can fail; an unguarded failure errors the thread and risks data loss.
  4. Load on
    PlayerAdded
    , save on
    PlayerRemoving
    , and also
    BindToClose
    .
    A leaving player and a shutting-down server both need a final save.
  5. Prefer
    UpdateAsync
    for read-modify-write
    (multi-server safe) over
    SetAsync
    (blind overwrite). On a failed load, do not overwrite with defaults — abort the save so you don't wipe good data.
  6. Use
    OrderedDataStore
    for ranked data
    (leaderboards) via
    GetSortedAsync
    . Test by joining, changing data, rejoining, and confirming it persisted.
  1. 一次性启用Studio访问权限。文件 → 游戏设置 → 安全性 → 启用Studio对API服务的访问权限(使用测试场景;Studio会访问实时数据)。DataStores仅能在服务器
    Script
    中使用,绝不能在
    LocalScript
    中使用。
  2. 获取存储实例,然后按键读写数据
    DataStoreService:GetDataStore("Name")
    ;每个玩家的键通常为
    "Player_" .. player.UserId
  3. 将所有调用包装在
    pcall
    GetAsync
    /
    SetAsync
    /
    UpdateAsync
    是可能失败的网络调用;未加防护的失败会导致线程报错,并有数据丢失风险。
  4. PlayerAdded
    时加载数据,
    PlayerRemoving
    时保存数据,同时使用
    BindToClose
    。玩家离开和服务器关闭时都需要最终保存。
  5. 优先使用
    UpdateAsync
    进行读取-修改-写入操作
    (多服务器安全),而非
    SetAsync
    (盲目覆盖)。加载失败时,不要用默认值覆盖——中止保存,避免擦除有效数据。
  6. 使用
    OrderedDataStore
    处理排名数据(排行榜)
    ,通过
    GetSortedAsync
    实现。测试方法:加入游戏、修改数据、重新加入,确认数据已持久化。

Patterns

模式示例

1. Load on join (pcall-guarded)

1. 加入时加载(含pcall防护)

lua
local DataStoreService = game:GetService("DataStoreService")
local Players = game:GetService("Players")
local store = DataStoreService:GetDataStore("PlayerData")

local DEFAULT = { Coins = 0, Level = 1 }

Players.PlayerAdded:Connect(function(player)
    local key = "Player_" .. player.UserId
    local ok, data = pcall(function()
        return store:GetAsync(key)
    end)

    if not ok then
        -- Load FAILED (network). Do not treat as a new player; flag so we never save
        -- over their real data with defaults.
        warn("Load failed for", player.Name, data)
        player:SetAttribute("DataLoaded", false)
        return
    end

    player:SetAttribute("DataLoaded", true)
    local profile = data or DEFAULT          -- nil == genuinely new player
    applyToLeaderstats(player, profile)
end)
lua
local DataStoreService = game:GetService("DataStoreService")
local Players = game:GetService("Players")
local store = DataStoreService:GetDataStore("PlayerData")

local DEFAULT = { Coins = 0, Level = 1 }

Players.PlayerAdded:Connect(function(player)
    local key = "Player_" .. player.UserId
    local ok, data = pcall(function()
        return store:GetAsync(key)
    end)

    if not ok then
        -- 加载失败(网络问题)。不要视为新玩家;标记状态以避免用默认值覆盖真实数据。
        warn("Load failed for", player.Name, data)
        player:SetAttribute("DataLoaded", false)
        return
    end

    player:SetAttribute("DataLoaded", true)
    local profile = data or DEFAULT          -- nil 代表真正的新玩家
    applyToLeaderstats(player, profile)
end)

2. Save with UpdateAsync (multi-server safe)

2. 使用UpdateAsync保存(多服务器安全)

lua
-- UpdateAsync reads the latest value, then writes what the callback returns.
-- The callback MUST NOT yield (no task.wait, no further Async calls inside it).
local function savePlayer(player)
    if player:GetAttribute("DataLoaded") == false then return end  -- never overwrite on a bad load
    local key = "Player_" .. player.UserId
    local newData = gatherDataFor(player)    -- a plain table of serializable values

    local ok, err = pcall(function()
        store:UpdateAsync(key, function(old)
            -- merge/decide here; return nil to cancel the write
            return newData
        end)
    end)
    if not ok then warn("Save failed for", player.Name, err) end
end
lua
-- UpdateAsync会读取最新值,然后写入回调函数返回的结果。
-- 回调函数**禁止**挂起(不能调用task.wait,也不能在内部进行其他Async调用)。
local function savePlayer(player)
    if player:GetAttribute("DataLoaded") == false then return end  -- 加载失败时绝不覆盖数据
    local key = "Player_" .. player.UserId
    local newData = gatherDataFor(player)    -- 可序列化值的普通表

    local ok, err = pcall(function()
        store:UpdateAsync(key, function(old)
            -- 在此处合并/决策;返回nil可取消写入
            return newData
        end)
    end)
    if not ok then warn("Save failed for", player.Name, err) end
end

3. Save on leave AND on shutdown

3. 离开时保存 + 服务器关闭时保存

lua
Players.PlayerRemoving:Connect(savePlayer)

-- BindToClose runs when the server shuts down; save everyone still in.
-- It has a limited time budget, so save in parallel and yield until done.
game:BindToClose(function()
    local players = Players:GetPlayers()
    local remaining = #players
    if remaining == 0 then return end
    for _, player in players do
        task.spawn(function()
            savePlayer(player)
            remaining -= 1
        end)
    end
    while remaining > 0 do task.wait() end
end)
lua
Players.PlayerRemoving:Connect(savePlayer)

-- BindToClose在服务器关闭时运行;保存所有仍在服务器中的玩家数据。
-- 它有时间限制,因此需并行保存并等待完成。
game:BindToClose(function()
    local players = Players:GetPlayers()
    local remaining = #players
    if remaining == 0 then return end
    for _, player in players do
        task.spawn(function()
            savePlayer(player)
            remaining -= 1
        end)
    end
    while remaining > 0 do task.wait() end
end)

4. Retry with backoff (transient failures)

4. 带退避策略的重试(临时故障)

lua
local function withRetry(fn, attempts)
    attempts = attempts or 3
    for i = 1, attempts do
        local ok, result = pcall(fn)
        if ok then return true, result end
        if i < attempts then task.wait(2 ^ i) end   -- 2s, 4s, ... backoff
    end
    return false
end

local ok, data = withRetry(function() return store:GetAsync(key) end)
lua
local function withRetry(fn, attempts)
    attempts = attempts or 3
    for i = 1, attempts do
        local ok, result = pcall(fn)
        if ok then return true, result end
        if i < attempts then task.wait(2 ^ i) end   -- 退避间隔:2秒、4秒……
    end
    return false
end

local ok, data = withRetry(function() return store:GetAsync(key) end)

5. Increment a counter

5. 递增计数器

lua
-- IncrementAsync is a convenience for integer read-modify-write (still wrap it).
local ok, newTotal = pcall(function()
    return store:IncrementAsync("Visits_" .. player.UserId, 1)
end)
lua
-- IncrementAsync是整数读取-修改-写入操作的便捷方法(仍需包装)。
local ok, newTotal = pcall(function()
    return store:IncrementAsync("Visits_" .. player.UserId, 1)
end)

6. Leaderboard with OrderedDataStore

6. 使用OrderedDataStore实现排行榜

lua
local boards = DataStoreService:GetOrderedDataStore("Coins")

-- Write a player's score (call when it changes, not every frame).
pcall(function() boards:SetAsync("Player_" .. player.UserId, coins) end)

-- Read the top 10, descending.
local ok, pages = pcall(function()
    return boards:GetSortedAsync(false, 10)   -- ascending=false → highest first
end)
if ok then
    for rank, entry in ipairs(pages:GetCurrentPage()) do
        print(rank, entry.key, entry.value)   -- entry.value is the number
    end
end
lua
local boards = DataStoreService:GetOrderedDataStore("Coins")

-- 写入玩家分数(分数变化时调用,不要每帧调用)。
pcall(function() boards:SetAsync("Player_" .. player.UserId, coins) end)

-- 读取前10名,降序排列。
local ok, pages = pcall(function()
    return boards:GetSortedAsync(false, 10)   -- ascending=false → 从高到低排序
end)
if ok then
    for rank, entry in ipairs(pages:GetCurrentPage()) do
        print(rank, entry.key, entry.value)   -- entry.value 为数值
    end
end

Pitfalls

常见陷阱

  • Unhandled failure wipes progress → always
    pcall
    Async calls; on a failed load, mark the session and refuse to save so defaults never overwrite real data.
  • SetAsync
    race between servers
    → two servers writing the same key can clobber each other. Use
    UpdateAsync
    for read-modify-write so each write sees the latest.
  • Yielding inside the
    UpdateAsync
    callback
    → the callback can't call
    task.wait
    or other Async functions; compute the new value beforehand and return it.
  • No
    BindToClose
    save
    → players in the server at shutdown lose unsaved progress; add
    game:BindToClose
    and wait for saves to finish within its budget.
  • Throttling / "too many requests" → respect per-key and per-minute limits; don't save on every value change. Batch and save on a timer / on leave.
    GetAsync
    is cached briefly, so immediate re-reads may be stale.
  • Storing non-serializable values → only JSON-serializable data persists: numbers, strings, booleans, and tables with string/number keys.
    Instance
    s,
    Vector3
    ,
    CFrame
    , and functions do not — serialize them to plain tables first.
  • Testing without API access → DataStores silently can't be used in Studio until Enable Studio Access to API Services is on (and they don't work from a
    LocalScript
    ).
  • DataStoreKeyInfo
    is nil for ordered stores
    OrderedDataStore
    doesn't support versioning/metadata; use a regular
    DataStore
    when you need those.
  • 未处理的故障会清除进度 → 始终用
    pcall
    包裹Async调用;加载失败时,标记会话并拒绝保存,避免默认值覆盖真实数据。
  • SetAsync
    在多服务器间的竞争问题
    → 两台服务器写入同一个键可能会互相覆盖。读取-修改-写入操作请使用
    UpdateAsync
    ,确保每次写入都能获取最新值。
  • UpdateAsync
    回调中挂起
    → 回调函数不能调用
    task.wait
    或其他Async函数;需提前计算好新值并返回。
  • 未使用
    BindToClose
    保存
    → 服务器关闭时仍在服务器中的玩家会丢失未保存的进度;添加
    game:BindToClose
    并在其时间限制内等待保存完成。
  • 限流 / “请求过多” → 遵守单键和每分钟的请求限制;不要在每次值变化时都保存。批量处理并定时保存/离开时保存。
    GetAsync
    会短暂缓存,因此立即重新读取可能会获取到过时数据。
  • 存储不可序列化的值 → 只有可JSON序列化的数据才能持久化:数字、字符串、布尔值,以及键为字符串/数字的表。
    Instance
    Vector3
    CFrame
    和函数无法持久化——需先将它们序列化为普通表。
  • 未开启API访问权限进行测试 → 在Studio中,直到启用Studio对API服务的访问权限后,DataStores才能正常使用(且无法在
    LocalScript
    中工作)。
  • 有序存储的
    DataStoreKeyInfo
    为nil
    OrderedDataStore
    不支持版本控制/元数据;需要这些功能时请使用常规
    DataStore

References

参考资料

  • For session locking (preventing duplicate data across servers), versioning/ metadata with
    DataStoreSetOptions
    , ordered-store pagination (
    AdvanceToNextPageAsync
    ), the key error codes and request limits, and Right-to-be-Forgotten compliance, read
    references/sessions-and-limits.md
    .
  • 如需了解会话锁定(防止跨服务器数据重复)、使用
    DataStoreSetOptions
    进行版本控制/元数据管理、有序存储分页(
    AdvanceToNextPageAsync
    )、关键错误代码和请求限制,以及“被遗忘权”合规性,请阅读
    references/sessions-and-limits.md

Related skills

相关技能

  • roblox-luau
    — services, instances, events, and the server/client model.
  • save-systems
    — engine-agnostic serialization, slots, and migration.
  • roblox-luau
    — 服务、实例、事件,以及客户端/服务器模型。
  • save-systems
    — 与引擎无关的序列化、存档槽和数据迁移。