roblox-datastores

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

roblox-datastores

Roblox数据存储

Reference for Roblox
DataStoreService
— saving, loading, and managing player data on the server.
Roblox
DataStoreService
参考文档——在服务器端保存、加载和管理玩家数据。

Quick Reference

快速参考

MethodSignatureNotes
GetDataStore
DSS:GetDataStore(name, scope?)
Returns a
GlobalDataStore
GetOrderedDataStore
DSS:GetOrderedDataStore(name, scope?)
For leaderboards
GetAsync
store:GetAsync(key)
Returns value or nil
SetAsync
store:SetAsync(key, value)
No return value needed
UpdateAsync
store:UpdateAsync(key, fn)
Atomic read-modify-write
RemoveAsync
store:RemoveAsync(key)
Deletes key, returns old value
GetSortedAsync
orderedStore:GetSortedAsync(asc, pageSize)
Returns
DataStorePages

方法签名说明
GetDataStore
DSS:GetDataStore(name, scope?)
返回一个
GlobalDataStore
GetOrderedDataStore
DSS:GetOrderedDataStore(name, scope?)
用于排行榜
GetAsync
store:GetAsync(key)
返回值或nil
SetAsync
store:SetAsync(key, value)
无需返回值
UpdateAsync
store:UpdateAsync(key, fn)
原子化读-改-写操作
RemoveAsync
store:RemoveAsync(key)
删除键,返回旧值
GetSortedAsync
orderedStore:GetSortedAsync(asc, pageSize)
返回
DataStorePages

Basic Setup

基础设置

lua
-- Server Script (ServerScriptService)
local DataStoreService = game:GetService("DataStoreService")
local Players = game:GetService("Players")

local playerStore = DataStoreService:GetDataStore("PlayerData_v1")

local DEFAULT_DATA = {
    coins = 0,
    level = 1,
    xp = 0,
}

lua
-- Server Script (ServerScriptService)
local DataStoreService = game:GetService("DataStoreService")
local Players = game:GetService("Players")

local playerStore = DataStoreService:GetDataStore("PlayerData_v1")

local DEFAULT_DATA = {
    coins = 0,
    level = 1,
    xp = 0,
}

Loading Data (GetAsync + pcall)

加载数据(GetAsync + pcall)

Always wrap datastore calls in
pcall
. They can fail due to network issues or rate limits.
lua
local function loadData(player)
    local key = "player_" .. player.UserId
    local success, data = pcall(function()
        return playerStore:GetAsync(key)
    end)

    if success then
        local result = {}
        for k, v in pairs(DEFAULT_DATA) do result[k] = v end
        if data then
            for k, v in pairs(data) do result[k] = v end
        end
        return result
    else
        warn("Failed to load data for", player.Name, ":", data)
        return nil -- signal failure; do not give default data silently
    end
end

始终用
pcall
包裹数据存储调用,因为网络问题或速率限制可能导致调用失败。
lua
local function loadData(player)
    local key = "player_" .. player.UserId
    local success, data = pcall(function()
        return playerStore:GetAsync(key)
    end)

    if success then
        local result = {}
        for k, v in pairs(DEFAULT_DATA) do result[k] = v end
        if data then
            for k, v in pairs(data) do result[k] = v end
        end
        return result
    else
        warn("Failed to load data for", player.Name, ":", data)
        return nil -- 标记失败;不要静默提供默认数据
    end
end

Saving Data (SetAsync vs UpdateAsync)

保存数据(SetAsync vs UpdateAsync)

Use
SetAsync
for simple overwrites. Use
UpdateAsync
when the value must be based on the current stored value (e.g., incrementing a counter safely across servers).
lua
-- Simple save
local function saveData(player, data)
    local key = "player_" .. player.UserId
    local success, err = pcall(function()
        playerStore:SetAsync(key, data)
    end)
    if not success then
        warn("Failed to save data for", player.Name, ":", err)
    end
end

-- Atomic increment with UpdateAsync
local function addCoinsAtomic(userId, amount)
    local key = "player_" .. userId
    pcall(function()
        playerStore:UpdateAsync(key, function(current)
            current = current or { coins = 0 }
            current.coins = current.coins + amount
            return current
        end)
    end)
end

简单覆盖操作使用
SetAsync
。当值必须基于当前存储的值时(例如跨服务器安全地递增计数器),使用
UpdateAsync
lua
-- 简单保存
local function saveData(player, data)
    local key = "player_" .. player.UserId
    local success, err = pcall(function()
        playerStore:SetAsync(key, data)
    end)
    if not success then
        warn("Failed to save data for", player.Name, ":", err)
    end
end

-- 原子化递增(使用UpdateAsync)
local function addCoinsAtomic(userId, amount)
    local key = "player_" .. userId
    pcall(function()
        playerStore:UpdateAsync(key, function(current)
            current = current or { coins = 0 }
            current.coins = current.coins + amount
            return current
        end)
    end)
end

Retry Logic

重试逻辑

lua
local MAX_RETRIES = 3
local RETRY_DELAY = 2

local function safeGet(store, key)
    for attempt = 1, MAX_RETRIES do
        local success, result = pcall(function()
            return store:GetAsync(key)
        end)
        if success then return true, result end
        warn(string.format("GetAsync attempt %d/%d failed: %s", attempt, MAX_RETRIES, result))
        if attempt < MAX_RETRIES then task.wait(RETRY_DELAY) end
    end
    return false, nil
end

lua
local MAX_RETRIES = 3
local RETRY_DELAY = 2

local function safeGet(store, key)
    for attempt = 1, MAX_RETRIES do
        local success, result = pcall(function()
            return store:GetAsync(key)
        end)
        if success then return true, result end
        warn(string.format("GetAsync attempt %d/%d failed: %s", attempt, MAX_RETRIES, result))
        if attempt < MAX_RETRIES then task.wait(RETRY_DELAY) end
    end
    return false, nil
end

Auto-Save: PlayerRemoving + BindToClose

自动保存:PlayerRemoving + BindToClose

Server shutdown without
BindToClose
silently discards unsaved data.
lua
local sessionData = {} -- [userId] = data table

Players.PlayerAdded:Connect(function(player)
    local data = loadData(player)
    if data then
        sessionData[player.UserId] = data
    else
        player:Kick("Could not load your data. Please rejoin.")
    end
end)

Players.PlayerRemoving:Connect(function(player)
    local data = sessionData[player.UserId]
    if data then
        saveData(player, data)
        sessionData[player.UserId] = nil
    end
end)

-- Flush all sessions on server shutdown
game:BindToClose(function()
    for userId, data in pairs(sessionData) do
        local key = "player_" .. userId
        pcall(function()
            playerStore:SetAsync(key, data)
        end)
    end
end)

如果没有
BindToClose
,服务器关机时会静默丢弃未保存的数据。
lua
local sessionData = {} -- [userId] = 数据表

Players.PlayerAdded:Connect(function(player)
    local data = loadData(player)
    if data then
        sessionData[player.UserId] = data
    else
        player:Kick("无法加载你的数据,请重新加入。")
    end
end)

Players.PlayerRemoving:Connect(function(player)
    local data = sessionData[player.UserId]
    if data then
        saveData(player, data)
        sessionData[player.UserId] = nil
    end
end)

-- 服务器关机时刷新所有会话数据
game:BindToClose(function()
    for userId, data in pairs(sessionData) do
        local key = "player_" .. userId
        pcall(function()
            playerStore:SetAsync(key, data)
        end)
    end
end)

Ordered DataStores (Leaderboards)

有序数据存储(排行榜)

Values must be positive integers.
lua
local coinsLeaderboard = DataStoreService:GetOrderedDataStore("Coins_v1")

local function setLeaderboardScore(userId, coins)
    pcall(function()
        coinsLeaderboard:SetAsync("player_" .. userId, math.floor(coins))
    end)
end

local function getTopPlayers(count)
    local success, pages = pcall(function()
        return coinsLeaderboard:GetSortedAsync(false, count) -- false = descending
    end)
    if not success then return {} end

    local results = {}
    for rank, entry in ipairs(pages:GetCurrentPage()) do
        table.insert(results, { rank = rank, userId = entry.key, score = entry.value })
    end
    return results
end

值必须为正整数。
lua
local coinsLeaderboard = DataStoreService:GetOrderedDataStore("Coins_v1")

local function setLeaderboardScore(userId, coins)
    pcall(function()
        coinsLeaderboard:SetAsync("player_" .. userId, math.floor(coins))
    end)
end

local function getTopPlayers(count)
    local success, pages = pcall(function()
        return coinsLeaderboard:GetSortedAsync(false, count) -- false = 降序
    end)
    if not success then return {} end

    local results = {}
    for rank, entry in ipairs(pages:GetCurrentPage()) do
        table.insert(results, { rank = rank, userId = entry.key, score = entry.value })
    end
    return results
end

Data Versioning / Migration

数据版本控制 / 迁移

Include a
_version
field and migrate in the load path.
lua
local CURRENT_VERSION = 2

local function migrateData(data)
    local version = data._version or 1
    if version < 2 then
        data.coins = data.gold or 0  -- renamed field
        data.gold = nil
        data._version = 2
    end
    return data
end
Use a versioned datastore name (
PlayerData_v2
) for breaking schema changes.

在数据中加入
_version
字段,并在加载流程中进行迁移。
lua
local CURRENT_VERSION = 2

local function migrateData(data)
    local version = data._version or 1
    if version < 2 then
        data.coins = data.gold or 0  -- 字段重命名
        data.gold = nil
        data._version = 2
    end
    return data
end
当出现破坏性的 schema 变更时,使用带版本号的数据存储名称(如
PlayerData_v2
)。

Common Mistakes

常见错误

MistakeConsequenceFix
No
pcall
around datastore calls
Unhandled error crashes the scriptAlways wrap in
pcall
Saving on every
Changed
event
Hits rate limits (60 + numPlayers×10 writes/min)Throttle; save on remove + periodic interval
No
BindToClose
handler
Data lost on server shutdownAlways flush all sessions in
BindToClose
Giving default data on load failurePlayer silently loses progressReturn
nil
on failure; kick or retry
SetAsync
for atomic counters
Race condition across serversUse
UpdateAsync
for read-modify-write
Storing Instances or functionsData silently dropsStore only strings, numbers, booleans, plain tables
Reusing datastore name after schema changeOld shape clashes with new codeAppend
_v2
,
_v3
to name on breaking changes
错误后果修复方案
数据存储调用未用
pcall
包裹
未处理的错误会导致脚本崩溃始终用
pcall
包裹
每次
Changed
事件触发时都保存
触发速率限制(每分钟60 + 玩家数×10次写入)限制保存频率;在玩家离开时保存 + 定期保存
未设置
BindToClose
处理函数
服务器关机时数据丢失务必在
BindToClose
中刷新所有会话数据
加载失败时提供默认数据玩家进度被静默丢失失败时返回
nil
;踢出玩家或重试
使用
SetAsync
实现原子计数器
跨服务器出现竞态条件原子化读-改-写操作使用
UpdateAsync
存储实例或函数数据被静默丢弃仅存储字符串、数字、布尔值、普通表
schema变更后复用数据存储名称旧数据结构与新代码冲突出现破坏性变更时,在名称后追加
_v2
_v3
等版本号