I make sure that schema changes are always additive. So I don't drop columns and tables, because that would zap the data and cannot be rolled back later. This way the code that uses the database can be rolled back without losing data or functionality.
I have a migration script that contains statements that creates tables and columns if they don't exist yet and fills them with data.
The migration script runs whenever the production code is updated and after new installs.
When I would like to drop something, I do it by removing them from the database install script and the migration script so these obsolete schema elements will be gradually phased out in new installs. With the disadvantage that new installs cannot downgrade to an older version before the install.
And of course I execute DDLs via these scripts and never directly on the database to keep things in sync.