The syntax is one line in every major database. The reason this is worth an article is that running that one line against a live table breaks every piece of code still referring to the old name, instantly, with no grace period — and the deployment that fixes the code has not finished rolling out yet.
So there are two answers here. The syntax, which is short, and the deployment sequence, which is what actually matters if the table is in production. The second one is a pattern worth internalising because it applies to far more than renames.
Table of contents
- The syntax, by database
- Why the obvious approach breaks production
- Expand and contract
- Finding everything that references the column
- When not to rename at all
- How this fits the rest of the stack
- FAQ
The syntax, by database
Modern versions have converged on the standard form, with SQL Server as the outlier.
-- Standard SQL, supported by Postgres, MySQL 8.0+, MariaDB 10.5+, SQLite 3.25+, Oracle 9i+
ALTER TABLE users RENAME COLUMN full_name TO display_name;
-- MySQL before 8.0 -- requires restating the full column definition
ALTER TABLE users CHANGE full_name display_name VARCHAR(255) NOT NULL;
-- SQL Server -- a stored procedure, not ALTER TABLE
EXEC sp_rename 'users.full_name', 'display_name', 'COLUMN';
The pre-8.0 MySQL form is worth flagging because it is a real hazard. CHANGE requires the complete column definition, and anything you omit is reset to the default. Leave off NOT NULL and the column becomes nullable. Leave off a DEFAULT and it is dropped. Leave off the character set and it reverts. Always read the current definition with SHOW CREATE TABLE and copy it exactly.
SQL Server’s sp_rename works but raises a warning that renaming may break scripts and stored procedures — which is accurate. It also does not update references inside views, stored procedures or computed columns, so those need finding and fixing separately.
On the plus side, a rename is metadata-only in every major engine. It does not rewrite the table, so it completes almost instantly regardless of row count. The lock it takes is brief — but it is still a lock, and on a very busy table it can queue behind and ahead of other statements.
Why the obvious approach breaks production
Consider the naive deployment: run the migration, then deploy the code. Between those two events, every running instance of your application is querying a column that no longer exists. Every request touching that table fails.
Reverse the order and it is no better — deploy code referencing a column that does not exist yet, and it fails until the migration lands.
There is no ordering of those two steps that works, and the gap is not brief:
- A rolling deploy across several instances takes minutes, during which old and new code are both running against one database.
- Anything with a persistent connection or a prepared statement may hold a cached plan referencing the old name.
- Background workers, cron jobs and queue consumers deploy on their own schedule and are frequently forgotten.
- Read replicas apply the change with replication lag, so replicas briefly disagree with the primary.
- Rolling back the code deploy does not roll back the migration.
The last point is the one that turns an incident into a long incident. Once the column is renamed, reverting the application to the previous version makes things worse rather than better, and you are now writing a migration under pressure.
Expand and contract
The pattern that solves this — and every other backward-incompatible schema change — is to make each individual step safe with both old and new code running.
- Add the new column.
ALTER TABLE users ADD COLUMN display_name VARCHAR(255);Nullable, no default that forces a rewrite. Nothing reads it yet and old code is unaffected. - Deploy code that writes both. Every write sets
full_nameanddisplay_name; reads still usefull_name. Safe with old code, which simply ignores the new column. - Backfill. Copy existing values in batches —
UPDATE users SET display_name = full_name WHERE display_name IS NULL AND id BETWEEN ? AND ?— with a pause between batches. One large UPDATE on a big table holds locks and generates enormous replication lag. - Verify.
SELECT COUNT(*) FROM users WHERE display_name IS DISTINCT FROM full_name;should return zero. Do not proceed until it does. - Deploy code that reads the new column and still writes both. Now reversible — rolling back returns to reading the old column, which is still current.
- Deploy code that only uses the new column. Stop writing the old one.
- Drop the old column, in a separate change, days later, after you are confident nothing references it.
Every step is individually reversible, and at no point do old and new code disagree about what the database contains. It is more steps than a rename, and it is the difference between a routine change and an outage.
The gap between steps six and seven matters. Leave it long enough that a rollback to any recently-deployed version still works, and long enough for a weekly job you forgot about to have run at least once.
Finding everything that references the column
A rename breaks more than your application queries. Before starting, find every reference.
- Views referencing the column. Postgres tracks the dependency and renames within views automatically; other engines may leave a broken view that fails on next use.
- Stored procedures, functions and triggers. Frequently not validated until executed, so a break here surfaces later and somewhere unexpected.
- Indexes and constraints whose names embed the old column name. They keep working but become misleading, which is its own cost.
- Application code, including raw SQL strings, ORM models, and serialisers.
- Reporting and analytics. Dashboards, scheduled reports, data pipelines and warehouse syncs — these live outside the application repository and are the most commonly missed.
- API responses. If the column name is exposed in a public API, renaming it is a breaking change for your consumers regardless of how carefully you handle the database.
For the database side, query the information schema — information_schema.columns for the column itself, and the engine’s dependency views for what references it. For the code side, grep the whole organisation’s repositories rather than just the one, because the analytics pipeline is nearly always in a different repository from the application.
That last point about APIs is worth pausing on: if the name is in a public contract, the database rename is the easy half and the API deprecation is a much longer process.
When not to rename at all
A rename has a real cost and sometimes the honest answer is not to do it.
Do it when the current name is actively misleading — a column called status holding a timestamp, or email holding a username. Confusion compounds and the cost of the rename is paid once.
Consider a view instead when the underlying name cannot change safely but you want a better one for consumers. A view exposing the column under a new name gives readers the clarity without touching the table, and it is instantly reversible.
Do not do it purely for style — user_name to username, or a casing convention change — on a large production table with many consumers. The improvement is marginal and the sequence above is a week of careful work. Add it to a list, and do it during a change that is already touching the table.
Fix it properly next time by naming columns well initially and adding column comments where the meaning is not obvious. COMMENT ON COLUMN in Postgres and the COMMENT clause in MySQL cost nothing, appear in schema documentation, and prevent the confusion that leads to renames.
How this fits the rest of the stack
Backfills and batched updates are where a schema change meets the database’s actual capacity — a batch size that is fine on a small plan is not on a large table, and replication lag is the symptom. The RunxBuild hosting calculator shows the database alongside the service, storage and bandwidth so the plan matches the workload. RunxBuild runs managed MySQL and Postgres with backups you can restore before a migration, configurable connection limits, user management and private networking.
Useful related references:
- MySQL Rename Column: The SQL Is Easy, the Deployment Is the Work
- Postgres Add Column: The Safe Way on a Live Table
- How to Rename a File in Linux: mv, rename, and git mv
- Services on RunxBuild
FAQ
How do I rename a column in SQL?
ALTER TABLE table_name RENAME COLUMN old_name TO new_name works in Postgres, MySQL 8.0 and later, MariaDB 10.5+, SQLite 3.25+ and Oracle. SQL Server uses EXEC sp_rename with a COLUMN argument. MySQL before 8.0 requires ALTER TABLE … CHANGE with the full column definition restated.
Why does MySQL CHANGE require the full column definition?
Because CHANGE redefines the column rather than only renaming it, so anything you omit reverts to a default — NOT NULL is dropped, DEFAULT values are lost, and the character set resets. Read the current definition with SHOW CREATE TABLE and copy it exactly, or upgrade to 8.0 and use RENAME COLUMN.
Does renaming a column lock the table?
It is metadata-only in every major engine, so it does not rewrite the table and completes almost instantly regardless of row count. It still takes a brief lock, which on a very busy table can queue behind and ahead of other statements.
How do I rename a column without downtime?
Use expand and contract: add the new column, deploy code writing both, backfill in batches, verify, deploy code reading the new column while still writing both, deploy code using only the new one, then drop the old column days later. Each step is safe with old and new code running simultaneously.
What else breaks when I rename a column?
Views, stored procedures, functions, triggers, indexes and constraints with the name embedded, application code including ORM models, and — most commonly missed — reporting dashboards and data pipelines that live outside the application repository. If the name appears in a public API response, that is a breaking change for consumers too.