Zakładasz za dużo rzeczy

Użytkownikowi z reguły się "nie ufa" i wszelkie dane wprowadzane przez niego lub możliwe przez niego do edycji muszą być ciągle zabezpieczone. Liczby są także pewnym zabezpieczeniem

Ale wracając do wątku... Co z tego, że można zapisywać dane numerycznie w bazie/pliku skoro sięgając do kodu po kilku miesiącach, nie wiesz co 3/4 liczb oznacza i szukasz tego potem

Takie rozwiązanie z dodatkową tabelą jest bardziej naturalne i przez to przyswajalne nawet dla osoby, która zerka do kodu po raz pierwszy. Teraz wiesz co oznacza 1 i 0 w płeć. Ale czy za rok będziesz to rozróżniał?
Gdy mowa o jednej tabeli to zastanów się... Ile rekordów przetwarza baza gdy zadasz pytanie o miasto i województwo? Za każdym razem wszystkie. To ogromny narzut czasowy w porównaniu do osobnych tabel.
Widać także, że nie pracowałeś wiele z bazami bo byś wiedział, że operacje w nich są często wielokrotnie szybsze niż w PHP. Zwłaszcza przy "obróbce danych". Dlatego "zrzuca się" na nie tyle, na ile jest to sensowne.
Gdy mowa o LIKE to jego użycie jest konieczne w przypadku wyszukiwarki miast. Jeśli użytkownik wpisze Biała i taki parametr podasz jako WHERE miasto = 'Biała' to nie znajdzie Ci nic poza tym dokładnie. A użytkownik mógł mieć na myśli także Bielsko-Biała lub Biała Podlaska. Poza tym wcale nie interesuje nas tabela miast całego świata bądź Polski. Powstaje ona samoistnie w bazie. Jeśli mamy grupę kilkuset tysięcy userów z kilku tysięcy miejscowości to wyszukiwanie wszystkich rekordów używając LIKE jest zwykłym zajeżdżaniem bazy. Lepiej te LIKE zrobić na kilku tysiącach rekordów bazy miast i znalezione id miast wyszukać potem w profilach wszystkich userów. Poza tym zauważ, że może być kilka miast o tej samej nazwie. To zaś oznacza dodatkowe ograniczenie w warunku WHERE, który znowu przyspieszyć może zapytanie wykonując się na kilku tysiącach zamiast kilkuset tysięcach.
Liczba miast w odpowiedniej tabeli nigdy nie będzie większa niż ilość userów (no chyba że kasujemy userów a miast nie, wtedy to jest możliwe) bo przecież skoro nikogo nie ma, przykładowo, z Pcimia Dolnego, to w tabeli miast takiego wpisu nie ma

A id_miasta znamy bo używam LIKE w zapytaniach w każdej wersji. Tylko inaczej konstruuję je co widać w przykładach. Rozdzielenie tabel przyspiesza zresztą całość serwisu, gdyż nie musimy działać w wielu przypadkach na wszystkich rekordach bazy. Przykład? Wyświetl wszystkie miasta z różną nazwą

W Twoim wypadku szuka w całej tabeli różnych nazw. U mnie przeszuka tylko konkretna tabelkę kilkukrotnie przynajmniej mniejszą. To samo w wyszukaniem miasta o konkretnej nazwie w konkretnym województwie. Sprawdzę to także w tabeli miasto. Znowu działam na znacznie mniejszej bazie.
Co do formatu danych to ja stosuję zawsze dopasowany do sytuacji. Pole ma tylko określone wartości? No to szukam takiego typu, który najlepiej się dopasuje. Po co mam mieć do dyspozycji cały int z 4 bajtami, skoro i tak używam cząstki tego? A co gdy baza się rozrośnie do setek tysięcy lub milionów rekordów? 3 bajty razy 100k rekordów to już 1/4MB. Teraz popatrz na wartości INT i zapis choćby nazwy województwa lub słowa 'mężczyzna', 'kobieta' w bazie. Ile bajtów mają a ile zajmują w typie tinyint? Przemnóż razy setki tysięcy. I tak samo w każdym przypadku. Szybko się okazuje, że na głupim formacie danych tracisz Megabajty miejsca.
Poza tym znowu patrzysz źle na tabelę miast. Jeśli kilku użytkowników jest z tego samego miasta, to mają oni podany ten sam id_miasta. Ilość danych przy większych serwisach znowu się zmniejsza. Każdy user musiałby podawać inne miasta by liczba była ta sama. Przypisanie zaś mu tego id jest na poziomie skryptu. Sprawdzasz czy miasto takie już istnieje w bazie i czy jest w danym wojewodztwie. Zapytanie jest banalne. Istnieje? Wpisz userowi id miasta, a jeśli nie ma to dorzuć do bazy nowe. Weź po prostu zaprojektuj sobie rozbudowany bardziej serwis i zauważysz, że jedna tabela to bardzo nieefektywne rozwiązanie, bo wszystko dzieje się zawsze na tej tabeli, choć często nie potrzebujesz operacji na całej, ale działasz tylko na konkretnej kolumnie i co gorsze, dane w niej są w formacie spowalniającym całość lub sprawiającym problem. Spróbuj jako parametr w GET przesłać coś z polskimi znakami to zobaczysz o czym mówię

Format liczbowy ułatwia lub rozwiązuje wiele problemów jeśli jest stosowany z głową. Nie zawsze bowiem jest sens go stosować. Czasem dla optymalizacji wręcz dubluje się pewne informacje by niepotrzebnie nie używać JOIN. Posłużę się przykładem z miastami znów. Mamy usera, gdzie jedną z informacji jest miasto pochodzenia. Można to rozwiązać na kilka sposobów. Ja przedstawię dwa by zilustrować problem.
Tabela user z kluczem miasta, Tabela miasta z kluczem województwa, tabela województw.
Ładnie i sensownie podzielone patrząc od strony struktury. Prawda? Tyle by dokopać się do tego z jakiego wojewódzwa jest user musimy dwukrotnie robić JOIN a co jeśli chcemy znaleźć wszystkich userów z danego? W takim wypadku pewnym rozwiązaniem jest pozostawienie struktury jak jest by mieć te same możliwości, ale do tabeli user dodać jeszcze klucz województwa. Funkcjonalności dotychczasowe pozostały, ale jeśli wyszukiwanie po województwach jest częste, to kosztem jednego klucza i zajmowanego miejsca zwiększyliśmy szybkość zapytania.
Innymi słowy tabel nie produkuj na potęgę. Rób to z głową i dostosowuj do potrzeb i funkcjonalności. Ale stosowanie jednej do wszystkiego to tylko niepotrzebne zagracanie jej gorzej się indeksującymi i spowalniającymi całość. Przy nieco większej ilości userów zauważysz przez to znaczne spowolnienie działania. Pamiętaj, że nie można przeginać w żadną stronę. Nie stosuj jednej tylko, ale i nie rozczłonkuj bazy jak popadnie. Stąd projektowanie jest bardzo ważnym momentem, bo od niego zależy jakie problemy w przyszłości nastąpią lub jakich unikniemy.