Best Practice? – Saving multiple Values as Serialized Data / Saving each Value per Row / Dedicated Table

This is written on the assumption that this data will only be retrieved when the user ID is known, and always in full for that user. If any kind of filtering is needed, if you need to filter users themselves by this data, then this answer will no longer apply.

One Row per User

This works but only if you fetch the entire data and process it in PHP. Serialized data in the database is a bad idea.

Multiple Rows per User

This will be just as fast as the one row per user, with the advantage of it being a nicer query, and the data is easier to handle. Always use this option over the previous option. Serialised data is not a good idea.

Separate Table

This will be just as fast as the previous 3 options

Large Row Count Anxiety

I imagine you’ve used sites that rely on post meta queries, and noticed that as they get bigger, they get slower. This isn’t because having lots of rows in a table is slower. Databases are designed in a way that can handle millions of rows, the performance slow down of extra rows is not enough to explain your problems.

Instead, the queries are the problem. Some queries are fast! E.g. finding a meta value when you already know the key and user ID is blazingly faster. Partially because WP fetches a user or posts meta values in bulk to save time and stores it in WP_Cache ( which is also why object caches make such a big difference ), but mainly because those tables were designed for those queries, and have indexes. The database doesn’t scan each row of a table when you do a query, that’s something a database does as a last resort in a worst case scenario.

By making bad data storage decisions, and running queries tables weren’t designed for, that’s when large tables get slower queries.

So on a site with 10 users, that query to find all users with a meta that has the value ABC will be quick, as will get_user_meta( 1, 'xyz' true )'. But on a site with 1 million users, get_user_meta will be a little slower, but that query to find all users with the meta of ABC will grind to a halt.

NOT/exclusions

User Dashboard, List(s) of Posts – CAN / CAN NOT access

Likewise when you ask not style queries. By doing this the database has to evaluate each row by row to test if it is or it is not, building a brand new temporary table in memory. Once it’s done this in memory copy, then it runs the query on the new table, before throwing it away. That’s why NOT IN OR __not_in parameters are so expensive. It’s also why the RAND sorting is so expensive. Not only that, but they dramatically hurt scaling. Your server only has so much memory to go round, if it’s full of temporary tables executing these exclusion queries then only so many people can access the site at the same time, PHP workers will have to wait their turn.