Migrating from MySQL to MSSQL: A Step-by-Step Guide for DevelopersMigrating a production database from MySQL to Microsoft SQL Server (MSSQL) is a multi-step project that touches schema design, data types, SQL dialects, stored code, performance tuning, and application compatibility. This guide walks through planning, preparation, migration, validation, and post-migration optimization with concrete steps, examples, and practical tips to reduce downtime and risk.
1. When and why to migrate
Common reasons to move from MySQL to MSSQL:
- Corporate standardization — company policy or existing Microsoft ecosystem adoption.
- Feature requirements — need for advanced features such as SQL Server Integration Services (SSIS), Query Store, Transparent Data Encryption (TDE), or richer analytics with SQL Server Reporting Services (SSRS) / Analysis Services.
- Performance and scale — in some workloads MSSQL can offer better concurrency controls or enterprise features.
- Tooling and support — preference for Microsoft tooling (SSMS, Azure integration) and enterprise support.
Assess whether migration is necessary: migrating costs time and money; if requirements can be met by MySQL or managed MySQL (AWS RDS, Azure Database for MySQL), staying put may be preferable.
2. High-level migration plan
- Inventory current system: schema, data size, stored procedures/triggers, scheduled jobs, users/roles, application queries.
- Map feature gaps and differences: data types, SQL dialect, functions, transactions, isolation levels.
- Choose migration strategy: big-bang (downtime) vs. phased (replication/dual-write).
- Prepare target MSSQL environment: sizing, collation, authentication mode, network/security.
- Convert schema and code; migrate data; translate stored procedures and triggers.
- Test thoroughly (unit, integration, performance, regression).
- Cutover and monitor; rollback plan ready.
- Post-migration tuning and cleanup.
3. Key differences to consider
- Authentication: MySQL offers user accounts per server; MSSQL supports Windows and SQL authentication, Active Directory integration.
- Collation and case sensitivity: MySQL default collation may differ. MSSQL collation is set at server/database/column level — ensure consistent behavior.
- Data types: mapping is not always 1:1 (detailed mappings below).
- Auto-increment vs. IDENTITY: MySQL’s AUTO_INCREMENT → MSSQL IDENTITY.
- SQL dialect: LIMIT vs. TOP/OFFSET-FETCH, GROUP_CONCAT vs. STRING_AGG (SQL Server 2017+), different date functions.
- Stored code: MySQL uses different procedural language (stored procedures, triggers) — conversion often manual.
- Transactions and isolation: default isolation levels and behaviors differ — MSSQL default is READ COMMITTED.
- Indexing and optimizer hints: syntax and behavior differ; queries may need rewriting or different indexes.
4. Schema and datatype mapping (common mappings)
MySQL type | MSSQL equivalent | Notes |
---|---|---|
INT, TINYINT, SMALLINT, MEDIUMINT, BIGINT | INT, TINYINT, SMALLINT, INT (no MEDIUMINT), BIGINT | MEDIUMINT (MySQL) map to INT and check range |
VARCHAR(n) | VARCHAR(n) | MSSQL max 8000; use VARCHAR(MAX) for larger |
TEXT, TINYTEXT, MEDIUMTEXT, LONGTEXT | VARCHAR(MAX) or NVARCHAR(MAX) | Use NVARCHAR(MAX) if Unicode needed |
CHAR(n) | CHAR(n) | |
DATETIME, TIMESTAMP | DATETIME2 / DATETIME | DATETIME2 has higher precision; consider datetimeoffset for zones |
DATE | DATE | |
TIME | TIME | |
BLOB, LONGBLOB | VARBINARY(MAX) | |
ENUM | CHECK constraint or lookup table | Better to use small reference table for portability |
SET | bitmasking or separate table | Complex to emulate; consider normalized design |
BOOLEAN, BOOL | BIT | MySQL BOOLEAN is alias for TINYINT(1) |
FLOAT, DOUBLE, DECIMAL | FLOAT, FLOAT, DECIMAL(precision,scale) | Match precision carefully |
5. Tools and approaches
Options for migration:
- SQL Server Migration Assistant (SSMA) for MySQL — Microsoft tool that converts schema, data, and some procedural code. Good first pass but manual adjustments are common.
- Import/Export via CSV or BCP/BULK INSERT — simple but loses stored procedures/triggers and may require careful handling of encodings and identity values.
- Linked servers and OPENQUERY — useful for one-off queries across DBs during migration.
- Replication / CDC solutions — use change data capture to keep systems in sync for phased cutover (e.g., Debezium → Kafka → SQL Server, or custom ETL).
- Third-party ETL tools — Pentaho, Talend, AWS DMS, Azure Database Migration Service (DMS).
- Custom scripts (Python, Go, Node) — useful when transformations are complex.
Recommended starting point: run SSMA to convert schema and identify manual changes, use SSIS or DMS for data movement, and set up CDC/replication if minimal downtime is required.
6. Detailed step-by-step migration
Step A — Inventory and discovery
- List all databases, tables, row counts, and growth rates.
- Export schema DDL from MySQL.
- Extract list of views, stored procedures, functions, triggers, events.
- Capture user privileges, jobs, and external dependencies (cron jobs, application schedules).
Step B — Prepare target MSSQL
- Choose appropriate SQL Server edition (Express, Standard, Enterprise, Azure SQL).
- Set server-level collation that matches expected string behavior.
- Configure logins, users, roles, and permissions.
- Plan disk layout: data files (.mdf/.ndf) vs. log files (.ldf), tempdb sizing.
- Configure backups, maintenance plans, and high availability if needed.
Step C — Convert schema
- Run SSMA for MySQL to generate a baseline conversion.
- Manually review:
- Data type mappings, lengths/precision.
- Primary keys, unique constraints, indexes.
- Auto-increment columns → IDENTITY; preserve current values by setting IDENTITY_INSERT ON during import if needed.
- Default values and computed columns.
- Collation on text columns.
- Convert ENUM/SET to normalized tables or CHECK constraints.
- Rewrite views and stored procedures:
- Translate MySQL procedural constructs (IF, WHILE, LOOP) to T-SQL.
- Replace MySQL-specific functions (e.g., CONCAT, DATE_FORMAT) with T-SQL equivalents (CONCAT, FORMAT, CONVERT).
- For GROUP_CONCAT use STRING_AGG (SQL Server 2017+) or FOR XML PATH workaround.
Example: LIMIT 10 OFFSET 20 → OFFSET-FETCH
-- MySQL SELECT * FROM orders ORDER BY order_date DESC LIMIT 10 OFFSET 20; -- MSSQL SELECT * FROM orders ORDER BY order_date DESC OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY;
Step D — Migrate data
- For small-medium datasets: use SSMA or SSIS to bulk copy data.
- For large datasets: use BCP or BULK INSERT with batch sizes to avoid long transactions.
- Preserve NULLs and encoding (use UTF-8/Unicode mapping to NVARCHAR if your data contains non-ASCII).
- Handle identity columns:
- If preserving original IDs, enable IDENTITY_INSERT ON for the target table during import.
- For referential integrity: load parent tables before child tables or disable constraints and re-enable/validate after load.
- Examples:
- BCP export/import
- SSIS Data Flow with transformations for type differences
Step E — Migrate code and logic
- Convert stored procedures, functions, triggers manually — test each procedure.
- Replace MySQL session variables, user variables (@var) with T-SQL variables (DECLARE @var datatype).
- Reimplement triggers to match firing semantics (AFTER/INSTEAD OF differences).
- Replace user-defined functions carefully; scalar vs. inline table-valued functions have different performance profiles in MSSQL.
Step F — Testing
- Schema validation: compare column counts, types, nullability, and constraints.
- Data validation: row counts, checksums, sampling, and full hash comparisons for critical tables.
- Functional testing: run application test-suite against MSSQL backend.
- Performance testing: compare slow queries and execution plans; add indexes if needed.
- Concurrency and load testing: simulate production workloads to find locking/contention issues.
Sample data validation query (row counts and checksum):
-- Row counts SELECT 'table_name' = 'users', COUNT(*) FROM users; -- Simple checksum example SELECT CHECKSUM_AGG(BINARY_CHECKSUM(*)) FROM users;
Step G — Cutover strategies
- Big-bang: stop writes on MySQL, final sync, point application to MSSQL. Simpler but requires downtime.
- Dual-write: application writes to both DBs during transition (requires careful idempotency and reconciliation).
- Replication/CDC: replicate changes from MySQL to MSSQL until cutover moment (more complex but minimal downtime).
- Rolling cutover: migrate services one at a time to minimize risk.
Prepare a rollback plan and a tested timeline. Communicate downtime windows to stakeholders.
7. Performance tuning after migration
- Review execution plans in SQL Server Management Studio (SSMS) and Query Store.
- Update statistics and rebuild indexes after bulk loads:
- UPDATE STATISTICS schema.table; or sp_updatestats
- ALTER INDEX ALL ON table REBUILD
- Consider clustered vs. nonclustered indexes carefully; primary key choice affects clustered index default.
- Use filtered indexes, included columns, and indexed views when beneficial.
- Tune tempdb and max degree of parallelism (MAXDOP) as needed.
- Monitor waits (CXPACKET, LCK_M_X) to diagnose concurrency issues.
8. Security and maintenance
- Configure encryption at rest (TDE) and in transit (TLS).
- Migrate user accounts to Windows/AD authentication where possible.
- Apply principle of least privilege for db roles.
- Set up automated backups, log shipping, availability groups, or Azure failover groups for HA/DR.
- Configure auditing via SQL Server Audit if required.
9. Common pitfalls and how to avoid them
- Ignoring collation differences → unexpected string comparison behavior. Verify collations.
- Assuming 1:1 data type compatibility → data truncation or precision loss. Review types.
- Not converting stored procedures correctly → functional regressions. Manual testing is required.
- Overlooking time zone handling for TIMESTAMP/DATETIME data → use datetimeoffset if needed.
- Failing to test performance under load → surprises after cutover. Run realistic load tests.
- Not planning identity preservation → referential integrity may break. Use IDENTITY_INSERT when needed.
10. Checklist before cutover
- Schema converted and reviewed.
- All critical data migrated and verified (row counts, checksums).
- Stored procedures, triggers, and views converted and tested.
- Application tested against MSSQL in staging (integration and load tests).
- Backups and recovery tested on MSSQL.
- Monitoring and alerting configured.
- Rollback plan documented and rehearsed.
11. Appendix — Quick translation snippets
-
AUTO_INCREMENT → IDENTITY(1,1)
CREATE TABLE users ( id INT IDENTITY(1,1) PRIMARY KEY, username VARCHAR(100) );
-
MySQL variable → T-SQL “`sql – MySQL SET @count = (SELECT COUNT(*) FROM orders);
– T-SQL DECLARE @count INT; SELECT @count = COUNT(*) FROM orders;
- GROUP_CONCAT → STRING_AGG (SQL Server 2017+) ```sql SELECT customer_id, STRING_AGG(product_name, ',') AS products FROM order_items GROUP BY customer_id;
- Replace IFNULL(expr1, expr2) with ISNULL(expr1, expr2) in T-SQL.
Migrating from MySQL to MSSQL is a complex but manageable process with careful planning, tool-assisted conversion, thorough testing, and staged execution. Use SSMA to accelerate conversion, supplement with SSIS/DMS for data movement, and allocate time for manual translation of business logic and performance tuning.
Leave a Reply