Case 1: The difference between CHAR(4) and SMALLINT is insignificant. It should not influence you choice of datatypes. Instead, use the datatypes that match the data.
Case 2: If you are comparing TINYINT to VARCHAR(255), the answer is probably different. Note that there is a much bigger difference in the choices.
Case 3: If the choice comes down to whether to "normalize" a column, there are arguments either way. I much prefer using a CHAR(2) for country_code than normalizing in order to shrink to a TINYINT. The overhead of extra normalization always(?) outweighs the space savings.
Another consideration: How many secondary keys are on the table? And how many other tables will you be joining to?
Case 4: PRIMARY KEY(big_string) but no secondary keys. There is no possibly no advantage in switching to an int.
Case 5: Since secondary keys include the PK, consider:
PRIMARY KEY(big_string),
INDEX(foo),
INDEX(bar)
versus
PRIMARY KEY(id), -- surrogate AUTO_INCREMENT
INDEX(big_string),
INDEX(foo),
INDEX(bar)
The latter will take less disk space.
Another consideration: Fetching a row is far more costly than comparing an int or string. My point is that you should not worry about comparison performance; you should look at the bigger picture when optimizing.
Case 6: USA 5-digit zip code. CHAR(5) (5 bytes) is reasonable. MEDIUMINT(5) UNSIGNED ZEROFILL (3 bytes) is better because it does everything better. (And it is a very rare case of the *INT(n) being meaningful.)
And the debate goes on and on.