Skip to content

Conversation

@dereuromark
Copy link
Member

@dereuromark dereuromark commented Nov 10, 2025

Summary

Adds support for MySQL's ALGORITHM and LOCK clauses in ALTER TABLE operations, enabling zero-downtime schema migrations for compatible operations on MySQL 8.0+ and MariaDB 10.3+.

Why This Is Useful

Production Impact:

  • Zero-downtime migrations: Using ALGORITHM=INSTANT allows adding nullable columns, dropping columns, and other compatible operations without copying the entire table
  • Large table performance: On production databases with millions of rows, instant migrations complete in milliseconds instead of minutes or hours
  • Explicit locking control: Developers can specify LOCK=NONE to ensure migrations don't block concurrent reads/writes

Real-world scenario:

// Before: Adding a column to a 100M row table = 30+ minutes of downtime
$table->addColumn('status', 'string')->update();

// After: Same operation = instant, no downtime
$table->addColumn('status', 'string', [
    'null' => true,
    'algorithm' => MysqlAdapter::ALGORITHM_INSTANT,
])->update();

  // Or with explicit locking control (use INPLACE for LOCK options)
  $table->addColumn('price', 'decimal', [
      'precision' => 10,
      'scale' => 2,
      'null' => true,
      'algorithm' => MysqlAdapter::ALGORITHM_INPLACE,  // ✅ INPLACE with LOCK
      'lock' => MysqlAdapter::LOCK_NONE,
  ])->update();

Implementation

Class Constants (No Magic Strings)

// Algorithm options
MysqlAdapter::ALGORITHM_DEFAULT
MysqlAdapter::ALGORITHM_INSTANT  // MySQL 8.0+ / MariaDB 10.3+
MysqlAdapter::ALGORITHM_INPLACE
MysqlAdapter::ALGORITHM_COPY

// Lock options
MysqlAdapter::LOCK_DEFAULT
MysqlAdapter::LOCK_NONE       // No locks, full concurrency
MysqlAdapter::LOCK_SHARED     // Allow reads, block writes
MysqlAdapter::LOCK_EXCLUSIVE  // Block all access

Key Features

  • ✅ Validation of algorithm and lock values
  • ✅ Conflict detection in batched operations
  • ✅ Backward compatible (existing code unchanged)
  • ✅ Case-insensitive option values
  • ✅ Works with both single and batched operations
  • ✅ Comprehensive test coverage (11 new tests)

Important MySQL Restriction

⚠️ ALGORITHM=INSTANT cannot be combined with explicit LOCK modes (LOCK=NONE, LOCK=SHARED, LOCK=EXCLUSIVE) due to MySQL limitations. Use either:

  • ALGORITHM=INSTANT alone (recommended)
  • ALGORITHM=INSTANT with LOCK=DEFAULT
  • ALGORITHM=INPLACE with any LOCK option

Usage Examples

Single operation:

$table->addColumn('price', 'decimal', [
    'precision' => 10,
    'scale' => 2,
    'null' => true,
    'algorithm' => MysqlAdapter::ALGORITHM_INSTANT,
])->update();

Batched operations:

$table->addColumn('col1', 'string', [
        'algorithm' => MysqlAdapter::ALGORITHM_INSTANT,
    ])
    ->addColumn('col2', 'string', [
        'algorithm' => MysqlAdapter::ALGORITHM_INSTANT,
    ])
    ->update();

Changes

  • MysqlAdapter.php: Added constants, validation, and algorithm/lock clause generation
  • Column.php: Added algorithm and lock properties with getters/setters
  • MysqlAdapterTest.php: Added 11 comprehensive test cases

Compatibility

  • MySQL 8.0+ supports all algorithm options
  • MariaDB 10.3+ supports all algorithm options
  • Falls back gracefully on older versions (MySQL will use default behavior)
  • Backward compatible: existing migrations work unchanged

dereuromark and others added 2 commits November 10, 2025 17:55
Implements support for MySQL's ALGORITHM and LOCK clauses in ALTER TABLE
operations, enabling zero-downtime schema migrations for compatible operations.

Key additions:
- Class constants for ALGORITHM (DEFAULT, INSTANT, INPLACE, COPY) and LOCK
  (DEFAULT, NONE, SHARED, EXCLUSIVE) options to avoid magic strings
- Column class now supports algorithm and lock options via setAlgorithm()/setLock()
- MysqlAdapter validates and applies algorithm/lock clauses to ALTER operations
- Batched operations detect conflicts and throw clear error messages
- Comprehensive test coverage (11 new test cases)

Benefits:
- Near-zero downtime migrations on large tables with ALGORITHM=INSTANT
- Production-friendly migrations with explicit locking control
- Improved performance for compatible schema changes on MySQL 8.0+/MariaDB 10.3+

Usage:
```php
use Migrations\Db\Adapter\MysqlAdapter;

$table->addColumn('status', 'string', [
    'null' => true,
    'algorithm' => MysqlAdapter::ALGORITHM_INSTANT,
    'lock' => MysqlAdapter::LOCK_NONE,
])->update();
```

Closes #2323

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
- Remove duplicate algorithm/lock clauses from individual column instructions
- Add MySQL restriction validation: ALGORITHM=INSTANT cannot be combined with
  explicit LOCK modes (LOCK=NONE, LOCK=SHARED, LOCK=EXCLUSIVE)
- Update tests to use ALGORITHM=INPLACE with explicit LOCK values instead
- Add test for MySQL restriction validation
- Remove unused algorithmClause() method
- Update documentation to clarify MySQL restrictions

Fixes duplicate clause issue where algorithm/lock was being added twice:
once in getAddColumnInstructions() and once in executeActionsWithAlgorithmAndLock().

The algorithm/lock clauses should only be applied at the ALTER TABLE statement
level, not at individual column instruction level.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
* @throws \InvalidArgumentException
* @return void
*/
protected function executeActionsWithAlgorithmAndLock(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we not duplicate all of this logic? This feels like a big smell that we're missing a better design solution. Perhaps we could have an adapter hook method at the end of executeActions() that receives both the actions and instructions. That would allow the MySqlAdapter to append the lock/algorithm clauses without having to create all this duplication.

$alterTemplate = sprintf('ALTER TABLE %s %%s', $this->quoteTableName($table->getName()));

// Add algorithm and lock clauses
$algorithmLockClause = '';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Won't we be missing lock/algorithm when $adapter->addColumn() and changeColumn() are used because of where this logic is?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants