I usually suggest having a second history_x table for table x. history_x in this scenario is nearly identical to x; it differs in that it's copy of x's primary key is not primary (and not auto-incrementing even if 'x''s is), and it has it's own primary key and sometimes some sort of addition changed_when datetime field.
Then two triggers are made:
AFTER INSERT ON x basically just clones a new row in x to history_x
AFTER UPDATE ON x just clones the new state of row x to history_x
How to handle DELETE varies. Often, if you're going as far as to actually delete the x record, the corresponding history records can be deleted with it. If you're just flagging the x as "retired", that is covered by the UPDATE handling. If you need to preserve the history after a delete, you can just add a x_deleted "flag" field and a DELETE trigger that clones the last state of the row, but sets the x_deleted flag in history to "true".
Also, this obviously doesn't track PK changes to x, but could if history_x has two copies of x's PK; one would be the historical PK value captured by the triggers with the rest of the fields, and the second would be bound to a foreign key that would cascade all the old history to reference the new key.
Edit: If you can take advantage of the semi-global nature of session/@ variables, you can even add information such as who made the change; but often connection pooling can interfere with that (each connection is it's own session).
Edit#2/Warning: If you're storing large data such as BLOBs or large TEXT fields, they should probably NOT be cloned every update.
Oh yeah, the "changed_when" data can also be more useful if expressed as a valid_from and valid_until pair of fields. valid_until should be null for the newest history record, and when a new history record in added the previous newest should have it's valid_until field set. changed_when is enough for a log, but if you need to actually use the old values WHERE ? >= valid_from and ? < valid_until is a lot easier than WHERE valid_until < ? ORDER BY valid_until DESC LIMIT 1