优化数据库表
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)
inventory
和 user_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_id
或 item_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.
所以,我想制作一款多人在线闲置游戏。
到目前为止,我已经设计了 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)
inventory
和 user_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_id
或 item_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.