数据访问运行时语义
当前实现状态:Phase 1 已支持 database.enabled / redis.enabled 控制 mock pool 初始化;未启用时 Lua API 返回 false, module_unavailable;启用时 CTest 覆盖 shield.db.query/execute 与 shield.redis.set/exists/del 的返回形态。 真实 MySQL/PostgreSQL/SQLite/Redis 驱动连接、连接错误传播和订阅生命周期仍未完成。
本文档包含 Shield 数据访问相关的运行时语义决策。
shield_data
shield_data 提供原始 DB/Redis 能力,不做 ORM。
local ok, rows = shield.db.query("SELECT * FROM users WHERE id = ?", { uid })
local ok, result = shield.redis.get("player:" .. uid)规则:
- API coroutine-friendly。
- query 不阻塞 runtime 线程。
- 返回
ok, result_or_error。 - 支持超时。
- 支持连接池。
- 不做分布式事务。
- 不跨 service 自动共享 transaction。
- 未启用 database/redis 时返回
false, module_unavailable。
当前最小契约只冻结以下 core 能力:
shield.db.queryshield.db.query_oneshield.db.executeshield.redis.get/set/del/existsshield.redis.publish/subscribe
以下内容属于 Phase 2+ 或驱动内部优化,可以在文档中先行收敛语义,但不作为 Phase 1 最小实现必达项,也不进入 lua-api-tests.md 的最小验收:
- 事务
- 显式预编译语句 handle
- Redis pipeline
- Redis eval
- Redis sentinel/cluster 配置
prepared statement 可以作为 driver 内部优化存在,但不能在 Phase 1 暴露为 Lua public API。
连接池配置
数据库连接池
database:
driver: mysql # mysql | postgresql | sqlite
host: localhost
port: 3306
database: game
username: root
password: ${DB_PASSWORD}
# 连接池配置
pool_size: 10 # 最小连接数
max_pool_size: 50 # 最大连接数
connect_timeout: 5000 # 建立连接超时(ms)
query_timeout: 30000 # 查询超时(ms)
idle_timeout: 300000 # 空闲连接超时(ms)
max_lifetime: 3600000 # 连接最大生命周期(ms)
# 重连策略
reconnect:
enabled: true
max_retries: 3 # 最大重试次数
initial_delay: 1000 # 初始延迟(ms)
max_delay: 30000 # 最大延迟(ms)
multiplier: 2 # 退避倍数
# 驱动专属配置
options:
charset: utf8mb4Redis 连接池
redis:
host: localhost
port: 6379
db: 0
password: ${REDIS_PASSWORD}
# 连接池配置
pool_size: 10 # 最小连接数
max_pool_size: 50 # 最大连接数
connect_timeout: 5000 # 建立连接超时(ms)
command_timeout: 5000 # 命令超时(ms)
idle_timeout: 300000 # 空闲连接超时(ms)
# 哨兵/集群配置(可选)
sentinel:
master_name: "mymaster"
addresses:
- "127.0.0.1:26379"
- "127.0.0.1:26380"连接池行为
┌─────────────────────────────────────────────────────────┐
│ 连接请求 │
│ - 优先使用空闲连接 │
│ - 无空闲且 < max_pool_size: 创建新连接 │
│ - 达到 max_pool_size: 等待(带超时) │
├─────────────────────────────────────────────────────────┤
│ 连接回收 │
│ - 空闲超过 idle_timeout: 关闭 │
│ - 存活超过 max_lifetime: 关闭(下次使用时重建) │
│ - 连接异常: 关闭并重建 │
└─────────────────────────────────────────────────────────┘数据库 API
查询
-- 查询多行
local ok, rows = shield.db.query("SELECT * FROM users WHERE level > ?", { 10 })
-- 查询单行
local ok, row = shield.db.query_one("SELECT * FROM users WHERE id = ?", { uid })
-- 执行(INSERT/UPDATE/DELETE)
local ok, result = shield.db.execute(
"INSERT INTO users (name, email) VALUES (?, ?)",
{ name, email }
)
-- result: { affected_rows = 1, insert_id = 123 }事务
事务属于 Phase 2+。Phase 1 不提供 shield.db.transaction Lua API;以下语义仅用于后续实现时保持边界一致。
local ok, err = shield.db.transaction(function(tx)
-- tx 对象提供 query/execute 方法
local ok, user = tx:query_one("SELECT * FROM users WHERE id = ? FOR UPDATE", { uid })
if not user then
return false, "user not found"
end
local ok, err = tx:execute("UPDATE users SET balance = balance - ? WHERE id = ?", { amount, uid })
if not ok then
return false, err
end
local ok, err = tx:execute("INSERT INTO logs (uid, action, amount) VALUES (?, ?, ?)", { uid, "deduct", amount })
if not ok then
return false, err
end
return true -- 提交事务
end)
if not ok then
shield.log.error("transaction failed: " .. err)
end事务规则:
- 事务内所有操作在同一连接上执行。
- 事务超时使用
query_timeout。 - 事务失败自动回滚。
- 不支持嵌套事务(savepoint 由驱动决定)。
- 不跨 service 共享事务。
预编译语句
显式预编译语句 handle 属于 Phase 2+。Phase 1 允许底层 driver 使用 prepared statement 优化 query/query_one/execute,但不向 Lua 暴露 shield.db.prepare。
-- 预编译语句(可选,驱动支持时)
local stmt = shield.db.prepare("SELECT * FROM users WHERE id = ?")
local ok, row = stmt:query_one({ uid })
stmt:close()Redis API
基础命令
-- String
local ok, value = shield.redis.get("key")
local ok, result = shield.redis.set("key", "value", 3600) -- TTL 可选
local ok, result = shield.redis.del("key")
local ok, result = shield.redis.exists("key")
-- Hash
local ok, value = shield.redis.hget("hash", "field")
local ok, result = shield.redis.hset("hash", "field", "value")
local ok, hash = shield.redis.hgetall("hash")
-- List
local ok, result = shield.redis.lpush("list", "value")
local ok, result = shield.redis.rpush("list", "value")
local ok, value = shield.redis.lpop("list")
local ok, values = shield.redis.lrange("list", 0, -1)
-- Set
local ok, result = shield.redis.sadd("set", "member")
local ok, result = shield.redis.srem("set", "member")
local ok, members = shield.redis.smembers("set")
-- Sorted Set
local ok, result = shield.redis.zadd("zset", 1.0, "member")
local ok, values = shield.redis.zrange("zset", 0, -1)
-- 通用
local ok, result = shield.redis.expire("key", 3600)
local ok, ttl = shield.redis.ttl("key")发布/订阅
-- 订阅
shield.redis.subscribe("channel", function(channel, message)
shield.log.info("received on " .. channel .. ": " .. message)
end)
-- 发布
local ok, receivers = shield.redis.publish("channel", "hello")Pipeline
Pipeline 属于 Phase 2+。Phase 1 不提供 shield.redis.pipeline。
local results = shield.redis.pipeline(function(pipe)
pipe:set("key1", "value1")
pipe:set("key2", "value2")
pipe:get("key1")
pipe:get("key2")
end)
-- results: { true, true, "value1", "value2" }Lua 脚本
Redis Lua 脚本属于 Phase 2+。Phase 1 不提供 shield.redis.eval。
local ok, result = shield.redis.eval(
"return redis.call('set', KEYS[1], ARGV[1])",
{ "mykey" }, -- KEYS
{ "myvalue" } -- ARGV
)错误处理
数据库和 Redis 错误码见 错误码参考。
local ok, result = shield.db.query("SELECT * FROM non_existent")
if not ok then
shield.log.error("db error: " .. result.code .. " - " .. result.message)
if result.code == "connection_lost" then
-- 连接丢失,下次查询会自动重连
elseif result.code == "query_timeout" then
-- 查询超时
end
endlocal ok, result = shield.redis.get("key")
if not ok then
shield.log.error("redis error: " .. result.code .. " - " .. result.message)
enddata ownership
DB/Redis 连接由 shield_data 管理,不由普通 Lua service 直接持有底层连接。
普通 service 只能通过 shield.db.* / shield.redis.* 调用。
ops 需要暴露:
- pool size。
- in-flight query。
- query latency。
- timeout count。
- reconnect count。