Yes, I'd say it was against the rules of designing clean databases, as the data you have is repeated, therefore you have to actively maintain two sets of data at all times for fear they are out of sync. If you don't do this, you can't trust the data. :)
As a side note: You say you're doing this for performance reasons? What benefits have you seen of doing this? As regardless of which table you access the data from, you should find (with proper indexes) that there is very little difference? It might be you are fixing the wrong issue here and maybe there are other things to look at first (like indexes, improving your queries etc).
Edit: Selecting free characters should be easy enough as you would get rid of the character_id
from the users table and only have user_id
in the characters one:
Users: user_id, name, age
Characters: character_id, shape, user_id
Then you would be able to do some queries like these:
//something like this to query for free characters.
SELECT * FROM characters WHERE user_id IS NULL;
// To access a list of a users characters, you could do something like this:
SELECT * FROM characters WHERE user_id = 42;
// More information about the user, while still grabbing character info.
SELECT * FROM characters AS c INNER JOIN users AS u ON u.user_id = c.user_id WHERE u.user_id = 42;
Or is there more about your model that I'm maybe missing with this? (As I don't pretend to know your database layout ;) ). I'm assuming a one-to-many relationship, as it does appear to be that way (one user can have many characters).