优化数据库表

Optimise Database Tables

所以,我想制作一款多人在线闲置游戏。

到目前为止,我已经设计了 5 个 table,但我不确定它们 optimised/normalised 的效果如何。尤其是 user_equipment table 全是外键。 inventory table 也感觉很快就满了

库存 - 这不会很快被填充吗?

item_id INTEGER NOT NULL,
user_id TEXT NOT NULL,
count INTEGER NOT NULL DEFAULT 1,
FOREIGN KEY(user_id) REFERENCES users(user_id),
FOREIGN KEY(item_id) REFERENCES items(item_id),
PRIMARY KEY(item_id, user_id)

items - 存储游戏中所有物品的信息

item_id INTEGER NOT NULL,
name TEXT NOT NULL,
attack INTEGER,
defense INTEGER,
perception INTEGER,
special TEXT DEFAULT "x",
description TEXT NOT NULL,
PRIMARY KEY(item_id)

user_equipment - 这个 table 是一个主要问题。存储装备物品的 ID。

user_id TEXT NOT NULL,
head_id INTEGER,
body_id INTEGER,
hand_id INTEGER,
feet_id INTEGER,
main_hand_id INTEGER,
off_hand_id INTEGER,
FOREIGN KEY(user_id) REFERENCES users(user_id),
FOREIGN KEY(head_id) REFERENCES items(item_id),
FOREIGN KEY(body_id) REFERENCES items(item_id),
FOREIGN KEY(hand_id) REFERENCES items(item_id),
FOREIGN KEY(feet_id) REFERENCES items(item_id),
FOREIGN KEY(main_hand_id) REFERENCES items(item_id),
FOREIGN KEY(off_hand_id) REFERENCES items(item_id),
PRIMARY KEY(user_id)

user_stats - 存储每个用户的统计数据。

user_id TEXT NOT NULL,
attack INTEGER DEFAULT 100,
defense INTEGER DEFAULT 100,
perception INTEGER DEFAULT 5,
FOREIGN KEY(user_id) REFERENCES users(user_id),
PRIMARY KEY(user_id)

users - 存储所有用户。

user_id TEXT NOT NULL,
spirit_stones INTEGER DEFAULT 0,
cultivation_val INTEGER DEFAULT 0,
last_checked DATE NOT NULL,
PRIMARY KEY(user_id)

inventoryuser_equipment table 还好吗?有没有更好的方法来设计和解决这个问题? 我将不胜感激任何帮助和建议!

这对于小型角色扮演游戏来说似乎是一个不错的开始。这里有一些建议。


users.user_id 应该是一个整数。名称会更改,但 ID 不应更改。添加名称列。一般来说,data tables(不同于 join tables)的主键应该是简单的整数。


考虑 user_equipment。有一堆相同的列是一个危险信号。

虽然你有固定数量的库存槽,但考虑一下如果玩家装备了给定的物品,你将如何搜索。

select 1
from user_equipment
where user_id = :user_id
  and :item_id in (head_id, body_id, hand_id, feet_id, main_hand_id, off_hand_id)

当您需要添加或删除新插槽时,(越来越大的)table 必须更改,所有查询也必须更改。你需要六个索引。

相反,有一个 table 来存储槽的类型,以及您需要的任何其他信息。然后 table 为每个用户存储每个槽中的内容。这简化了索引和搜索。

create slots (
  -- You can also use an integer.
  -- I chose char(2) because it is easier to know what it is without a join.
  slot_id char(2) primary key,
  name text not null
);

insert into slots values
('H', 'Head'), ('B', 'Body'), ('MH', 'Main Hand'),
('OH', 'Off Hand'), ('F', 'Feet');

create user_slots (
  user_id integer not null references users(id),
  item_id integer not null references items(id),
  slot_id char(2) not null references slots(slot_id),

  -- Enforce only one item per user slot
  unique(user_id, slot_id)
)

现在查询用户是否装备了物品很容易。

select 1
from user_slots
where user_id = :user_id
  and item_id = :item_id

或者一个用户的所有设备,以及它所在的插槽。

select slots.name, item_id
from user_slots us
join slots on us.slot_id = slots.slot_id
where user_id = :user_id

或者您可以找到谁装备了物品以及装备在什么位置。

select user_id, item_id, slots.name
from user_slots us
join slots on us.slot_id = slots.slot_id
where item_id = :item_id

索引对于保持性能与 table 的增长相同很重要。例如,所有的外键都被索引了。如果您按 user_iditem_id 搜索,SQLite 将不必搜索整个 table。但是,如果您按没有索引的 items.name 进行搜索,则必须搜索整个项目 table.

索引是一个很大的话题。您必须考虑 tables 将如何增长以及如何搜索它们。例如,users 将无限增长。 items 可能只会变得这么大。 user_equipped 会随着用户的增长而增长,但单个用户只有这么多项目;由于 user_id 和 item_id 已编入索引,因此您可能没问题。

例如,检查用户是否装备了物品会执行得很好,因为所有外键都已编入索引。

select user_id, item_id, slots.name
from user_slots us
join slots on us.slot_id = slots.slot_id
where item_id = :item_id

但是要查找某个时间内没有被检查的用户将不得不搜索整个 table 除非 users.last_checked 被索引。

select user_id
from users
where last_checked < :time

同样,索引也用于排序。除非 users.last_checked 被索引,否则此查询将执行不佳。

select user_id
from users
order by last_checked desc

(并考虑 last_checked 是否应该是


除非有充分的理由将其分开,否则 user_stats 应该合并到用户中。

users

user_id TEXT NOT NULL,
attack INTEGER DEFAULT 100,
defense INTEGER DEFAULT 100,
perception INTEGER DEFAULT 5,
spirit_stones INTEGER DEFAULT 0,
cultivation_val INTEGER DEFAULT 0,
last_checked DATE NOT NULL,
PRIMARY KEY(user_id)

如果有充分的理由将其分开,请将所有用户统计信息放在同一个地方。

user_stats

user_id TEXT NOT NULL,
attack INTEGER DEFAULT 100,
defense INTEGER DEFAULT 100,
perception INTEGER DEFAULT 5,
spirit_stones INTEGER DEFAULT 0,
cultivation_val INTEGER DEFAULT 0,
FOREIGN KEY(user_id) REFERENCES users(user_id),
PRIMARY KEY(user_id)

users - Stores all users.

user_id TEXT NOT NULL,
last_checked DATE NOT NULL,
PRIMARY KEY(user_id)

分开 user_stats 的一个原因是您要经常更新统计数据。这可能涉及行锁定。将统计数据放在单独的 table 中可以避免锁定重要的 users table.