I am using Liquibase 3.4.1 with MySQL56 and run Liquibase via Spring Boot.
I have a changeset that includes adding a column to the existing table. The new column
has valueComputed
attribute with a simple select.
When I made a mistake in that select the changeset failed and migration stopped at that changeset. However the new column was commited to the database but without the proper value and, what's really bad, this changeset was not marked as run! The next time I ran migration Liquibase attempted to execute the changeset again, but failed because the column had been created already.
Why didn't Liquibase rollback a transaction when the changeset failed? How can I make my changeset transactional?
EDIT: Apparently, MySQL commits after every DDL command. If I specify rollback commands in rollback tag will Liquibase run then in case of failed changeset?
Liquibase provides commands to allow you to undo changes you have made to your database, either automatically or with a custom rollback command. The intention of a rollback script is to return the database to a previous specified point in time. Note: Rollback support is available in command line, Ant, and Maven.
To use Liquibase and MySQL, you need the JDBC driver JAR file (Maven download). Place your JAR file(s) in the liquibase/lib directory. Read more: Adding and Updating Liquibase Drivers. If you use Maven, you must include the driver JAR as a dependency in your pom.
Liquibase tries to run changeset in transaction, but I think that mysql commits after ddl (column added): https://dev.mysql.com/doc/refman/5.5/en/implicit-commit.html
I'm not sure that it is possible to do it transactional, but you may split it into two changesets and if column population fails it will be rolled back and after rerun you will not have issues (column is already added in previous changeset).
EDIT by Ilya Novoseltsev:
Changesets involving DDL changes should be made atomic.
Bad:
<changeSet author="novoseltsevib (generated)" id="1445871764871-19">
<addColumn tableName="account_range">
<column name="cash2card_participation" type="BIT(1)" valueBoolean="false"/>
</addColumn>
<addColumn tableName="account_range_aud">
<column name="cash2card_participation" type="BIT(1)"/>
</addColumn>
</changeSet>
Good:
<changeSet author="novoseltsevib (generated)" id="1445871764871-19">
<addColumn tableName="account_range">
<column name="cash2card_participation" type="BIT(1)" valueBoolean="false"/>
</addColumn>
</changeSet>
<changeSet author="novoseltsevib (generated)" id="1445871764871-20">
<addColumn tableName="account_range_aud">
<column name="cash2card_participation" type="BIT(1)"/>
</addColumn>
</changeSet>
Writing your changesets this way allows to repeat a failed changeset without worrying about changes being partially commited.
One should be careful when using value = ...
, especially valueComputed
. These statements break addColumn
atomicity. They are executed as a separate update command after the DDL command. The proper way is to move them to their own update
tag in a separate changeset.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With