DEV Community

Cover image for I Thought MSSQL and MySQL Were the Same. I Was Wrong.
Satyam Gupta
Satyam Gupta

Posted on

I Thought MSSQL and MySQL Were the Same. I Was Wrong.

If you've worked with SQL, you've probably looked at Microsoft SQL Server and MySQL and thought:

"They're both SQL databases. How different can they really be?"

That was more or less how I thought about them.

At first, the differences seemed simple:

-- SQL Server
SELECT TOP 10 *
FROM Users;
Enter fullscreen mode Exit fullscreen mode

versus:

-- MySQL
SELECT *
FROM Users
LIMIT 10;
Enter fullscreen mode Exit fullscreen mode

Or:

-- SQL Server
GETDATE()
Enter fullscreen mode Exit fullscreen mode

versus:

-- MySQL
NOW()
Enter fullscreen mode Exit fullscreen mode

Then there are things like:

IDENTITY       → AUTO_INCREMENT
ISNULL()       → IFNULL()
dbo.Users      → database.Users
Enter fullscreen mode Exit fullscreen mode

Easy enough, right?

Not quite.

While working with an existing ASP.NET MVC application backed by Microsoft SQL Server, I started comparing the database layer with MySQL.

That's when I realized something important:

MSSQL and MySQL don't just have different SQL syntax. They make different architectural, indexing, transaction, storage, security, and operational choices.

And this matters a lot when you're migrating an existing application.

Especially if you're thinking:

ASP.NET → NestJS
MSSQL  → MySQL
Enter fullscreen mode Exit fullscreen mode

Those are actually two different migrations.

You don't have to do both.

This article explains what I learned while looking beyond SQL syntax and comparing the two databases from a developer's perspective.


First: SQL Isn't MSSQL or MySQL

Before comparing the databases, it's worth clearing up one common misconception.

SQL is a language standard.

MSSQL and MySQL are database systems that implement SQL, along with their own features and syntax.

A simplified way to think about it is:

                    SQL
                     │
          ┌──────────┴──────────┐
          │                     │
      SQL Server              MySQL
          │                     │
        T-SQL          MySQL-specific SQL
Enter fullscreen mode Exit fullscreen mode

Both databases understand concepts such as:

SELECT
INSERT
UPDATE
DELETE
JOIN
GROUP BY
ORDER BY
WHERE
HAVING
Enter fullscreen mode Exit fullscreen mode

So if you know SQL, you're not starting from zero when moving between them.

But SQL gives you the common language.

The database gives you the implementation.

That's where things start getting interesting.


MSSQL vs MySQL at a Glance

Feature MSSQL / SQL Server MySQL
Developer Microsoft Oracle
SQL dialect T-SQL MySQL SQL
Default port 1433 3306
Common ecosystem .NET, Microsoft/Azure Web, PHP, Node.js, Linux/cloud
Schema model Database → Schema → Object Database/schema → Object
Storage architecture SQL Server Database Engine Pluggable storage engines; InnoDB is the default
Transactions Supported Supported by InnoDB
Stored procedures Supported Supported
Views Supported Supported
Triggers Supported Supported
JSON Supported Supported
Full-text search Supported Supported
Clustered indexes Supported InnoDB organizes table data around a clustered primary-key index
Replication Supported Supported
Open-source ecosystem Depends on SQL Server edition/product Strong open-source ecosystem

One important detail here is the storage architecture.

MySQL has multiple storage engines, while InnoDB is the default storage engine in MySQL 8.4 and provides transactions, row-level locking, foreign keys, crash recovery, and clustered primary-key organization.

So even something as fundamental as "how a table is physically organized" isn't something you should assume is identical.


1. Database vs Schema: One of the First Things That Confused Me

This is one of the easiest places to get confused when moving from SQL Server to MySQL.

In SQL Server, you commonly have:

SQL Server Instance
        │
        ▼
     Database
        │
        ▼
      Schema
        │
        ▼
      Table
Enter fullscreen mode Exit fullscreen mode

For example:

propertyhive
    │
    └── dbo
         │
         ├── Users
         ├── Properties
         └── Agents
Enter fullscreen mode Exit fullscreen mode

So you might write:

SELECT *
FROM dbo.Users;
Enter fullscreen mode Exit fullscreen mode

Here:

dbo = schema
Users = table
Enter fullscreen mode Exit fullscreen mode

In MySQL, the terminology works differently.

A MySQL database is effectively the namespace containing tables and other objects. MySQL documentation commonly uses "database" and "schema" interchangeably.

You might have:

propertyhive
    │
    ├── Users
    ├── Properties
    └── Agents
Enter fullscreen mode Exit fullscreen mode

and query:

SELECT *
FROM propertyhive.Users;
Enter fullscreen mode Exit fullscreen mode

That means this:

dbo.Users
Enter fullscreen mode Exit fullscreen mode

is not something you can blindly translate into:

dbo.Users
Enter fullscreen mode Exit fullscreen mode

in MySQL.

You first have to understand what dbo represented in the original SQL Server application.

This becomes particularly important during migration because schema-qualified objects can appear everywhere:

dbo.Users
dbo.Properties
dbo.GetPropertyDetails(...)
dbo.vwUserwiseRatings
Enter fullscreen mode Exit fullscreen mode

If you simply search and replace syntax, you're going to miss the architectural meaning behind those references.


2. The Obvious Differences: SQL Syntax

Let's get the easy stuff out of the way.

TOP vs LIMIT

SQL Server:

SELECT TOP 10 *
FROM Users;
Enter fullscreen mode Exit fullscreen mode

MySQL:

SELECT *
FROM Users
LIMIT 10;
Enter fullscreen mode Exit fullscreen mode

For pagination, SQL Server commonly uses:

SELECT *
FROM Users
ORDER BY UserId
OFFSET 20 ROWS
FETCH NEXT 10 ROWS ONLY;
Enter fullscreen mode Exit fullscreen mode

MySQL:

SELECT *
FROM Users
ORDER BY UserId
LIMIT 10 OFFSET 20;
Enter fullscreen mode Exit fullscreen mode

The concepts are similar.

The syntax isn't.


IDENTITY vs AUTO_INCREMENT

SQL Server:

CREATE TABLE Users (
    Id INT IDENTITY(1,1) PRIMARY KEY,
    Name NVARCHAR(100)
);
Enter fullscreen mode Exit fullscreen mode

MySQL:

CREATE TABLE Users (
    Id INT AUTO_INCREMENT PRIMARY KEY,
    Name VARCHAR(100)
);
Enter fullscreen mode Exit fullscreen mode

Both can automatically generate numeric IDs.

But they're implemented differently.

That difference becomes important when you start dealing with:

  • Existing data
  • Seed scripts
  • Explicit ID insertion
  • ORM mappings
  • Last-generated IDs
  • Sequences
  • Migration tooling

3. Date and Time Functions

Here's another common translation.

SQL Server:

SELECT GETDATE();
Enter fullscreen mode Exit fullscreen mode

MySQL:

SELECT NOW();
Enter fullscreen mode Exit fullscreen mode

UTC:

-- SQL Server
SELECT GETUTCDATE();
Enter fullscreen mode Exit fullscreen mode
-- MySQL
SELECT UTC_TIMESTAMP();
Enter fullscreen mode Exit fullscreen mode

Date arithmetic also differs.

SQL Server:

SELECT DATEADD(DAY, 7, GETDATE());
Enter fullscreen mode Exit fullscreen mode

MySQL:

SELECT DATE_ADD(NOW(), INTERVAL 7 DAY);
Enter fullscreen mode Exit fullscreen mode

The underlying concept is identical:

"Give me a date seven days from now."

But the database-specific expression is different.

This is one reason automated SQL conversion can help with migration but cannot replace testing.


4. NULL Functions Aren't Always the Same

SQL Server commonly uses:

SELECT ISNULL(Phone, '');
Enter fullscreen mode Exit fullscreen mode

MySQL commonly uses:

SELECT IFNULL(Phone, '');
Enter fullscreen mode Exit fullscreen mode

Both can provide a fallback when a value is NULL.

But there's an important lesson here:

Never assume similarly named functions have identical type-conversion behavior.

For example, SQL Server also supports:

COALESCE()
Enter fullscreen mode Exit fullscreen mode

and MySQL supports it too.

COALESCE() is part of standard SQL and is often preferable when you want an expression that can work across multiple database systems.

Still, portability shouldn't be your only concern. You should understand the behavior of the actual database you're running.


5. Even String Length Isn't Identical

SQL Server:

SELECT LEN(Name)
FROM Users;
Enter fullscreen mode Exit fullscreen mode

MySQL:

SELECT LENGTH(Name)
FROM Users;
Enter fullscreen mode Exit fullscreen mode

But there's a catch.

In MySQL:

LENGTH()
Enter fullscreen mode Exit fullscreen mode

returns the length in bytes, while:

CHAR_LENGTH()
Enter fullscreen mode Exit fullscreen mode

returns the number of characters.

So for multilingual text, these can produce different results.

For example:

SELECT LENGTH('Mumbai');
Enter fullscreen mode Exit fullscreen mode

and:

SELECT CHAR_LENGTH('Mumbai');
Enter fullscreen mode Exit fullscreen mode

may appear equivalent for simple ASCII text.

But once you start working with Unicode characters or emojis, the distinction becomes important.

Which brings us to one of the most underestimated migration problems.


6. Unicode: NVARCHAR vs utf8mb4

Suppose your application stores:

Mumbai
मुंबई
தமிழ்நாடு
東京
🏠
Enter fullscreen mode Exit fullscreen mode

You don't want your migration to turn that into:

???
Enter fullscreen mode Exit fullscreen mode

SQL Server has historically distinguished Unicode types such as:

NVARCHAR
NCHAR
Enter fullscreen mode Exit fullscreen mode

MySQL uses character sets and collations.

For modern MySQL applications, utf8mb4 is the important character set to understand.

The key difference in thinking is:

SQL Server
    ↓
Unicode-aware data types

MySQL
    ↓
Character set + collation
Enter fullscreen mode Exit fullscreen mode

This isn't merely a syntax problem.

It can affect:

  • Names
  • Property descriptions
  • Locality names
  • City names
  • User-generated content
  • Emojis
  • Search
  • Sorting
  • Equality comparisons
  • Data migration

If you're migrating a property application, for example, a locality might contain multilingual data.

That's something you should test explicitly rather than assuming the database will handle it automatically.


7. Collation Can Change Your Application's Behavior

Here's a surprisingly important difference.

Imagine the database contains:

Mumbai
mumbai
MUMBAI
Enter fullscreen mode Exit fullscreen mode

Now your application executes:

WHERE City = 'mumbai'
Enter fullscreen mode Exit fullscreen mode

What should happen?

Does it match:

mumbai
Enter fullscreen mode Exit fullscreen mode

only?

Or:

Mumbai
mumbai
MUMBAI
Enter fullscreen mode Exit fullscreen mode

The answer depends on the database's collation and the specific column/expression involved.

Collation affects things such as:

  • Case sensitivity
  • Accent sensitivity
  • Sorting
  • Comparison
  • Searching

This can create a particularly nasty migration bug.

Your application works perfectly in SQL Server.

You migrate the data.

Then a search behaves differently in MySQL.

Nothing looks wrong in the query.

The difference is in the database configuration.

That's why collation should be part of your migration checklist, not an afterthought.


8. Data Types: Don't Treat Them as Simple 1:1 Mappings

Here's where migration starts getting more interesting.

A basic mapping might look like:

SQL Server MySQL
INT INT
BIGINT BIGINT
VARCHAR VARCHAR
NVARCHAR VARCHAR + appropriate character set
DECIMAL DECIMAL
BIT BOOLEAN / TINYINT(1) commonly used
DATETIME DATETIME
UNIQUEIDENTIFIER Often CHAR(36) / BINARY(16) depending on design
JSON JSON

The problem is that a table definition isn't just a list of names.

For example:

Price DECIMAL(18,2)
Enter fullscreen mode Exit fullscreen mode

isn't simply:

"Both databases have DECIMAL, so we're done."

You still need to validate:

  • Precision
  • Scale
  • Range
  • Default values
  • Nullability
  • ORM mapping
  • Existing data

The same applies to dates, UUIDs, booleans, binary data, and text.


9. Storage Engines: MySQL Has Another Layer You Need to Understand

This is one of the conceptual differences I didn't appreciate initially.

SQL Server is built around the SQL Server Database Engine.

MySQL, on the other hand, has a storage-engine architecture.

You can encounter engines such as:

InnoDB
MyISAM
MEMORY
Enter fullscreen mode Exit fullscreen mode

For modern transactional applications, InnoDB is the important one.

MySQL 8.4 uses InnoDB as its default storage engine. InnoDB provides ACID transactions, row-level locking, foreign keys, crash recovery, and clustered primary-key organization.

So when you're comparing SQL Server with MySQL, you're not just comparing:

SQL Server
vs
MySQL
Enter fullscreen mode Exit fullscreen mode

You're also comparing different database architectures and implementation choices.


10. Indexes: This Is Where "Just Convert the SQL" Really Breaks Down

Indexes are one of the biggest reasons you can't treat migration as a search-and-replace exercise.

In SQL Server, you can have:

  • Clustered indexes
  • Nonclustered indexes
  • Included columns
  • Filtered indexes
  • Columnstore indexes

For example:

CREATE NONCLUSTERED INDEX IX_Users_Email
ON dbo.Users(Email)
INCLUDE (Name, CreatedAt);
Enter fullscreen mode Exit fullscreen mode

Included columns can make an index cover a query so SQL Server doesn't need to fetch additional data from the underlying table.

SQL Server also supports filtered indexes:

CREATE INDEX IX_ActiveUsers
ON dbo.Users(Email)
WHERE IsActive = 1;
Enter fullscreen mode Exit fullscreen mode

This is a very different indexing feature from simply creating:

INDEX (Email)
Enter fullscreen mode Exit fullscreen mode

in MySQL.


11. MySQL/InnoDB Indexing Works Differently

InnoDB organizes table data around the primary key's clustered index.

That means the primary key isn't just another secondary lookup structure in the same conceptual sense.

InnoDB also has secondary indexes.

So this:

PRIMARY KEY (Id)
Enter fullscreen mode Exit fullscreen mode

has architectural consequences for how the table is organized and how secondary indexes locate rows.

MySQL documentation explicitly describes the InnoDB primary key index as the clustered index and explains that secondary indexes reference the primary-key value.

This is why blindly copying an MSSQL indexing strategy into MySQL is a bad idea.

You should ask:

What queries do we actually run?

What columns are filtered?

What columns are sorted?

What are the cardinalities?

What is the primary key?

How does the target database optimize this workload?
Enter fullscreen mode Exit fullscreen mode

Then design the indexes for the target database.


12. The Same Query Can Have Completely Different Performance

This is another misconception worth killing.

Suppose you have:

SELECT *
FROM Properties
WHERE CityId = 10
  AND IsActive = 1
ORDER BY CreatedAt DESC;
Enter fullscreen mode Exit fullscreen mode

You run it in SQL Server.

Then you migrate the database to MySQL and run essentially the same query.

Should it have the same execution time?

No.

The optimizer is different.

The indexes may be different.

Statistics may be different.

Data distribution may be different.

Configuration may be different.

The storage architecture may be different.

The execution plan may be different.

In SQL Server, you might inspect the execution plan and statistics through SQL Server tooling.

In MySQL, you can use:

EXPLAIN
Enter fullscreen mode Exit fullscreen mode

and:

EXPLAIN ANALYZE
Enter fullscreen mode Exit fullscreen mode

to investigate query execution.

MySQL's documentation emphasizes that indexes should be designed around the actual workload because unnecessary indexes consume space and add write overhead.

The lesson is simple:

Never migrate an index strategy based only on the old database's schema. Re-test the workload on the new database.


13. Transactions Are More Than BEGIN and COMMIT

Most developers know:

BEGIN TRANSACTION;

-- operations

COMMIT;
Enter fullscreen mode Exit fullscreen mode

and:

ROLLBACK;
Enter fullscreen mode Exit fullscreen mode

But transactions involve much more than those commands.

They involve:

  • Isolation
  • Locking
  • Concurrency
  • Deadlocks
  • Visibility of changes
  • Consistency

SQL Server supports isolation levels including:

READ UNCOMMITTED
READ COMMITTED
REPEATABLE READ
SNAPSHOT
SERIALIZABLE
Enter fullscreen mode Exit fullscreen mode

and its behavior can also be affected by database-level options such as READ_COMMITTED_SNAPSHOT.

InnoDB also has its own transaction and locking model, using row-level locking and consistent nonlocking reads.

So if your application contains something like:

Create property
    ↓
Create property images
    ↓
Create property metadata
    ↓
Update search index
Enter fullscreen mode Exit fullscreen mode

inside a transaction, you need to verify the behavior after migration.

Don't assume:

"Both support transactions, so we're good."

They both support transactions.

That doesn't mean every concurrency scenario behaves identically.


14. Deadlocks Don't Magically Disappear After Migration

Consider two requests:

Request A
    locks Property 1
    then wants Property 2

Request B
    locks Property 2
    then wants Property 1
Enter fullscreen mode Exit fullscreen mode

You can end up with:

A → waits for B
B → waits for A
Enter fullscreen mode Exit fullscreen mode

That's a deadlock.

Different databases have different locking implementations and behaviors.

SQL Server can use row, page, or table locks depending on circumstances, and lock escalation can occur.

InnoDB uses row-level locking as part of its transaction model and does not use lock escalation in the same way.

So when migrating a high-traffic application, transaction tests should include concurrent requests, not just happy-path unit tests.


15. Stored Procedures: This Is Where Migration Can Become Painful

Suppose your SQL Server application has:

CREATE PROCEDURE GetProperties
    @CityId INT
AS
BEGIN
    SELECT *
    FROM dbo.Properties
    WHERE CityId = @CityId;
END;
Enter fullscreen mode Exit fullscreen mode

You can't simply paste that into MySQL.

The procedural syntax is different.

SQL Server uses T-SQL.

MySQL has its own stored-program syntax.

And the problem isn't just syntax.

Existing procedures may contain:

  • Temporary tables
  • Variables
  • Cursors
  • Error handling
  • Transactions
  • Dynamic SQL
  • SQL Server functions
  • Table hints
  • SQL Server-specific system objects

For example, something like:

SCOPE_IDENTITY()
Enter fullscreen mode Exit fullscreen mode

has no direct "copy this exact line" equivalent.

MySQL commonly uses:

LAST_INSERT_ID()
Enter fullscreen mode Exit fullscreen mode

after an auto-increment insert.

The same business requirement exists.

The implementation differs.


16. Should You Keep Stored Procedures or Move Logic to NestJS?

This is where migration becomes an architectural decision.

Imagine your current system looks like:

ASP.NET MVC
     │
     ▼
Stored Procedures
     │
     ▼
MSSQL
Enter fullscreen mode Exit fullscreen mode

When moving to NestJS, you could choose:

Option A — Keep database logic

NestJS
   │
   ▼
MSSQL
   │
   ▼
Stored Procedures
Enter fullscreen mode Exit fullscreen mode

Option B — Move logic into NestJS

NestJS
   │
   ▼
Service / Repository
   │
   ▼
MSSQL
Enter fullscreen mode Exit fullscreen mode

Option C — Hybrid

NestJS
   │
   ├── Application logic
   │
   └── Critical DB operations
             │
             ▼
       Stored Procedures
Enter fullscreen mode Exit fullscreen mode

There isn't one universally correct answer.

It depends on:

  • Existing business logic
  • Team expertise
  • Performance requirements
  • Migration timeline
  • Testing coverage
  • Long-term architecture

This is also why I wouldn't automatically combine an ASP.NET-to-NestJS migration with an MSSQL-to-MySQL migration.

You're multiplying the variables you're changing at once.


17. Views, Functions and Triggers Need Migration Attention Too

A database isn't just tables.

An existing SQL Server application may contain:

Tables
Views
Stored Procedures
Functions
Triggers
Indexes
Constraints
Jobs
Permissions
Enter fullscreen mode Exit fullscreen mode

For example:

CREATE VIEW dbo.vwUserwiseRatings
AS
SELECT
    RegisteredUserId,
    CONVERT(DECIMAL(18,2), AVG(ISNULL(Rating, '0'))) AS Rating
FROM RegisteredUsersRatings
WHERE IsActive = 1
  AND IsApproved = 1
GROUP BY RegisteredUserId;
Enter fullscreen mode Exit fullscreen mode

Even if the underlying tables migrate successfully, this view still needs review.

You have:

dbo
CONVERT()
ISNULL()
Enter fullscreen mode Exit fullscreen mode

and SQL Server-specific syntax.

A migration isn't finished when:

Tables = migrated
Enter fullscreen mode Exit fullscreen mode

It's finished when the application's database behavior has been validated.


18. JSON Support Is Similar in Concept, Different in Implementation

Both databases support JSON.

SQL Server provides JSON functionality including:

FOR JSON
OPENJSON
JSON_VALUE
JSON_QUERY
Enter fullscreen mode Exit fullscreen mode

For example:

SELECT
    Id,
    Name
FROM Properties
FOR JSON PATH;
Enter fullscreen mode Exit fullscreen mode

SQL Server documentation describes FOR JSON as a way to format query results as JSON directly from the database.

SQL Server also supports indexing JSON-derived values through techniques such as computed columns and indexes.

MySQL has native JSON functionality as well, including JSON extraction and indexing options.

MySQL 8.4 also supports multi-valued indexes for JSON arrays.

So again:

Both support JSON
        ≠
Same JSON implementation
Enter fullscreen mode Exit fullscreen mode

If your application stores property metadata as JSON, test:

  • Inserts
  • Updates
  • Queries
  • Null behavior
  • Indexing
  • Serialization
  • ORM mapping

19. Full-Text Search Is Another Feature You Need to Test

Suppose your property application allows users to search:

"2 BHK apartment near Mira Road"
Enter fullscreen mode Exit fullscreen mode

You may eventually use database full-text search.

SQL Server has its own Full-Text Search implementation.

MySQL/InnoDB supports FULLTEXT indexes and uses syntax such as:

MATCH(description)
AGAINST('apartment mira road');
Enter fullscreen mode Exit fullscreen mode

MySQL's InnoDB full-text implementation uses inverted indexes and has its own transaction and indexing behavior.

So if the existing application relies on database-level full-text search, you can't assume the same search query and ranking behavior after migration.

Search is application behavior.

Treat it as such.


20. Permissions and Security Are Different Too

Database security isn't just:

username
password
Enter fullscreen mode Exit fullscreen mode

SQL Server commonly separates concepts such as:

Login
   ↓
Database User
   ↓
Role
   ↓
Permissions
Enter fullscreen mode Exit fullscreen mode

MySQL has its own account and privilege model.

During migration, think about application accounts separately from human developer accounts.

For example:

Application
    ↓
Read/Write account

Reporting service
    ↓
Read-only account

Admin
    ↓
Administrative account
Enter fullscreen mode Exit fullscreen mode

The goal should be least privilege regardless of the database.

Never migrate production credentials into source code or configuration files just because the old application did so.


21. Backup and Recovery Are Not Just "Take a Backup"

This is another area where database migration becomes an operations problem.

SQL Server has concepts such as:

Full backup
Differential backup
Transaction log backup
Recovery models
Point-in-time recovery
Always On
Enter fullscreen mode Exit fullscreen mode

SQL Server's recovery model affects transaction-log management and the types of restore operations available. The three recovery models are Simple, Full, and Bulk-logged.

MySQL has a different ecosystem around:

Logical backups
Binary logs
Replication
Group Replication
InnoDB Cluster
Enter fullscreen mode Exit fullscreen mode

The important point isn't to memorize every feature.

It's to recognize that:

Your backup and recovery strategy is part of your database architecture.

If you're migrating production infrastructure, you need to define:

What is our RPO?
What is our RTO?
How do we restore?
How do we test restoration?
How do we recover from accidental deletes?
How do we perform point-in-time recovery?
Enter fullscreen mode Exit fullscreen mode

A database migration without a rollback strategy is not a complete migration plan.


22. Replication and High Availability

Both SQL Server and MySQL support replication/high-availability architectures, but the technologies and operational models differ.

SQL Server provides features such as:

Always On availability groups
Replication
Log shipping
Enter fullscreen mode Exit fullscreen mode

MySQL provides mechanisms such as:

Replication
Group Replication
InnoDB Cluster
Enter fullscreen mode Exit fullscreen mode

MySQL replication is based around the binary log, and MySQL documents several replication configurations and behaviors.

The important thing is not to ask:

"Which one scales better?"

That's too simplistic.

Instead ask:

What is my workload?

How many reads?

How many writes?

Do I need read replicas?

What is my failover requirement?

What downtime is acceptable?

What does my cloud provider support?

What does my team know how to operate?
Enter fullscreen mode Exit fullscreen mode

Database performance is workload-dependent.


23. So What Actually Happens During MSSQL → MySQL Migration?

This is where everything above comes together.

A naive migration plan looks like:

Export MSSQL
      ↓
Import MySQL
      ↓
Change connection string
      ↓
Done
Enter fullscreen mode Exit fullscreen mode

Real migrations look more like:

                    MSSQL
                      │
        ┌─────────────┼─────────────┐
        │             │             │
      Tables        Views       Procedures
        │             │             │
      Types        Functions     Triggers
        │             │             │
     Indexes       Queries      Transactions
        │             │             │
        └─────────────┼─────────────┘
                      ↓
                 Analyze
                      ↓
                 Convert
                      ↓
                  Test
                      ↓
                Benchmark
                      ↓
                  Cutover
Enter fullscreen mode Exit fullscreen mode

And I'd add one more step:

                  Rollback
Enter fullscreen mode Exit fullscreen mode

You should always know how you're getting back if something goes wrong.


24. A Practical MSSQL → MySQL Migration Checklist

Here's how I'd break down the analysis.

Step 1: Inventory the database

Document:

☑ Tables
☑ Columns
☑ Primary keys
☑ Foreign keys
☑ Unique constraints
☑ Defaults
☑ Views
☑ Stored procedures
☑ Functions
☑ Triggers
☑ Indexes
☑ Full-text search
☑ JSON usage
☑ Scheduled jobs
☑ Permissions
Enter fullscreen mode Exit fullscreen mode

Step 2: Map data types

Create a mapping such as:

MSSQL                  MySQL
----------------------------------
INT                    INT
BIGINT                 BIGINT
VARCHAR                VARCHAR
NVARCHAR               VARCHAR + utf8mb4
DECIMAL                DECIMAL
DATETIME               DATETIME
UNIQUEIDENTIFIER       CHAR/BINARY design
BIT                    BOOLEAN/TINYINT design
Enter fullscreen mode Exit fullscreen mode

Then validate every non-trivial type.


Step 3: Analyze schemas

Find every occurrence of:

dbo.
Enter fullscreen mode Exit fullscreen mode

Then determine whether it represents:

  • A SQL Server schema
  • A naming convention
  • An object namespace
  • Something referenced by application code

Don't just replace it blindly.


Step 4: Convert database logic

Review:

Views
Procedures
Functions
Triggers
Enter fullscreen mode Exit fullscreen mode

For every object ask:

Can this be converted?

Should it be converted?

Should the logic move into NestJS?

Does it need to remain database-side?
Enter fullscreen mode Exit fullscreen mode

Step 5: Redesign indexes

Don't do:

MSSQL index
      ↓
Copy
      ↓
MySQL
Enter fullscreen mode Exit fullscreen mode

Instead:

Existing query workload
        ↓
Analyze queries
        ↓
Design MySQL indexes
        ↓
EXPLAIN
        ↓
Benchmark
Enter fullscreen mode Exit fullscreen mode

Step 6: Test transactions

Test real scenarios:

Concurrent updates
Concurrent inserts
Rollback
Deadlocks
Isolation
Long-running transactions
Foreign-key failures
Enter fullscreen mode Exit fullscreen mode

Step 7: Validate data

Don't only check:

Row count
Enter fullscreen mode Exit fullscreen mode

Also compare:

NULL values
Unicode
Dates
Decimals
IDs
Foreign keys
Duplicate values
Default values
Enter fullscreen mode Exit fullscreen mode

For example:

MSSQL Users = 1,000,000
MySQL Users = 1,000,000
Enter fullscreen mode Exit fullscreen mode

doesn't prove the migration is correct.

You could still have:

Incorrect dates
Broken Unicode
Changed precision
Missing relationships
Different collation behavior
Enter fullscreen mode Exit fullscreen mode

25. The Part I Think Is Most Important: ASP.NET → NestJS Does NOT Mean MSSQL → MySQL

This is the architectural distinction I wish more migration discussions made.

Suppose your current system is:

ASP.NET MVC
     │
     ▼
   MSSQL
Enter fullscreen mode Exit fullscreen mode

You can absolutely build:

NestJS
   │
   ▼
  MSSQL
Enter fullscreen mode Exit fullscreen mode

There is no requirement that changing the backend framework means changing the database.

Likewise, you could keep:

ASP.NET MVC
     │
     ▼
   MSSQL
Enter fullscreen mode Exit fullscreen mode

and migrate the database separately:

ASP.NET MVC
     │
     ▼
   MySQL
Enter fullscreen mode Exit fullscreen mode

Or migrate both:

ASP.NET MVC
     │
     ▼
   MSSQL

        ↓

NestJS
     │
     ▼
   MySQL
Enter fullscreen mode Exit fullscreen mode

All are technically possible.

But the last option changes significantly more things at the same time.


26. Why I Wouldn't Automatically Do Both Migrations Together

Imagine an API starts returning incorrect data after migration.

You changed:

ASP.NET → NestJS
Enter fullscreen mode Exit fullscreen mode

and:

MSSQL → MySQL
Enter fullscreen mode Exit fullscreen mode

Now you have two major areas to investigate.

Is the problem:

NestJS business logic?
Enter fullscreen mode Exit fullscreen mode

or:

MySQL query?
Enter fullscreen mode Exit fullscreen mode

or:

ORM mapping?
Enter fullscreen mode Exit fullscreen mode

or:

Database behavior?
Enter fullscreen mode Exit fullscreen mode

or:

Data migration?
Enter fullscreen mode Exit fullscreen mode

That's a lot of variables.

Instead, a staged approach could be:

Existing system

ASP.NET
   │
   ▼
 MSSQL

     ↓

NestJS
   │
   ▼
 MSSQL

     ↓

Test everything

     ↓

MSSQL
   │
   ▼
MySQL

     ↓

Test everything again
Enter fullscreen mode Exit fullscreen mode

Now if something breaks during the second migration, you have a much smaller search space.

This isn't a universal rule.

There are situations where a combined migration makes sense.

But if you can separate the changes, you often make debugging and rollback much easier.


27. A Migration Strategy I'd Actually Use

If I were approaching an existing ASP.NET + MSSQL application, I'd consider something like this.

Phase 1 — Understand

Document:

Controllers
Services
Repositories/DAL
Queries
Stored procedures
Views
Functions
Triggers
Indexes
Transactions
External integrations
Enter fullscreen mode Exit fullscreen mode

Don't migrate what you don't understand.


Phase 2 — Move the Backend

Build the NestJS application while keeping MSSQL.

NestJS
   │
   ├── Controllers
   ├── Services
   ├── Repositories
   │
   ▼
 MSSQL
Enter fullscreen mode Exit fullscreen mode

At this stage, you're primarily changing the application layer.


Phase 3 — Validate

Compare the old and new systems.

Test:

API responses
Authentication
Authorization
Business rules
Transactions
Errors
Edge cases
Performance
Enter fullscreen mode Exit fullscreen mode

The goal is:

New backend + old database behaves like old backend + old database.


Phase 4 — Decide Whether MySQL Is Actually Needed

Only after the backend migration is stable should you ask:

Why are we moving from MSSQL to MySQL?
Enter fullscreen mode Exit fullscreen mode

Possible reasons might include:

  • Infrastructure requirements
  • Licensing/cost
  • Existing team expertise
  • Hosting strategy
  • Application architecture
  • Organizational standards
  • Technical requirements

But "because we're using NestJS" isn't by itself a reason.

NestJS can work with SQL Server.


28. When Does MSSQL Make Sense?

SQL Server can be a strong choice when you have:

  • An existing Microsoft ecosystem
  • .NET applications
  • Existing SQL Server infrastructure
  • Enterprise requirements
  • SQL Server expertise
  • Existing SQL Server-specific database logic

If your company already has:

ASP.NET
SQL Server
SSMS
Azure
Microsoft infrastructure
Enter fullscreen mode Exit fullscreen mode

then SQL Server may be the most practical choice.

Changing it simply because the backend is changing may create unnecessary migration work.


29. When Does MySQL Make Sense?

MySQL can be an excellent choice when:

  • Your organization already uses MySQL
  • Your application architecture fits MySQL well
  • You want its open-source ecosystem
  • Your team has strong MySQL expertise
  • Your infrastructure is designed around it
  • Your workload fits it

It's also widely used in web application stacks.

But again:

Don't choose a database because another developer says it's "better."

Choose based on the workload and requirements.


30. MSSQL vs MySQL: A More Useful Decision Table

Requirement MSSQL MySQL
Existing .NET application Excellent fit Possible
Existing Node.js application Strong Strong
Existing SQL Server infrastructure Excellent fit Migration required
Open-source preference Depends on edition/product Strong fit
Stored procedures Strong Supported
Transactions Strong Strong with InnoDB
Enterprise workloads Strong Strong
Web applications Strong Strong
JSON workloads Supported Supported
Full-text search Supported Supported
Migration from the other database Requires analysis Requires analysis

The important phrase in almost every row is:

"It depends."

That's not avoiding the question.

That's the correct answer for database architecture.


31. Common Mistakes Developers Make

Mistake 1: Thinking SQL Server = SQL

They're not the same thing.

SQL is the language.

SQL Server is Microsoft's database system.


Mistake 2: Replacing TOP with LIMIT and calling it a migration

Changing:

TOP 10
Enter fullscreen mode Exit fullscreen mode

to:

LIMIT 10
Enter fullscreen mode Exit fullscreen mode

solves one syntax problem.

It doesn't migrate:

indexes
transactions
procedures
collations
permissions
views
triggers
performance
Enter fullscreen mode Exit fullscreen mode

Mistake 3: Assuming dbo works the same way

It doesn't.

Understand the schema architecture first.


Mistake 4: Ignoring collation

Your application can suddenly behave differently when comparing:

Mumbai
mumbai
MUMBAI
Enter fullscreen mode Exit fullscreen mode

Mistake 5: Ignoring Unicode

A migration that preserves:

Mumbai
Enter fullscreen mode Exit fullscreen mode

but corrupts:

मुंबई
東京
🏠
Enter fullscreen mode Exit fullscreen mode

isn't successful.


Mistake 6: Copying indexes blindly

An index that made sense in SQL Server may not be the right index for InnoDB.


Mistake 7: Assuming transactions behave identically

Both databases support transactions.

That doesn't mean their locking and isolation behavior are identical.


Mistake 8: Assuming stored procedures are portable

They're not.

Database-specific procedural code often requires substantial rewriting.


Mistake 9: Assuming the same query has the same performance

It doesn't.

Always inspect the execution plan on the target database.


Mistake 10: Changing everything at once

This is probably the biggest one.

If you change:

Backend
Database
ORM
Infrastructure
Authentication
API contracts
Enter fullscreen mode Exit fullscreen mode

at the same time, debugging becomes significantly harder.


32. The Bigger Lesson: SQL Knowledge Is Transferable, Database Behavior Isn't

After looking at all of this, I think the relationship can be summarized like this:

                    SQL
                     │
          ┌──────────┴──────────┐
          │                     │
      MSSQL                   MySQL
          │                     │
       T-SQL               MySQL SQL
          │                     │
          ├── Types             ├── Types
          ├── Indexes           ├── Indexes
          ├── Transactions      ├── Transactions
          ├── Locking           ├── Locking
          ├── JSON              ├── JSON
          └── Tooling           └── Tooling
Enter fullscreen mode Exit fullscreen mode

The common SQL layer gives you a huge advantage.

But the database-specific layer is where engineering decisions live.

That's why I now think about database knowledge in two layers:

Layer 1 — Transferable SQL knowledge

Learn:

SELECT
JOIN
GROUP BY
Indexes
Transactions
Constraints
Normalization
Aggregations
Subqueries
CTEs
Enter fullscreen mode Exit fullscreen mode

These concepts transfer extremely well.

Layer 2 — Database-specific knowledge

Then learn:

SQL Server
    ↓
T-SQL
    ↓
SQL Server indexes
    ↓
SQL Server locking
    ↓
SQL Server tooling
    ↓
SQL Server operations
Enter fullscreen mode Exit fullscreen mode

or:

MySQL
    ↓
MySQL SQL
    ↓
InnoDB
    ↓
MySQL indexes
    ↓
MySQL locking
    ↓
MySQL operations
Enter fullscreen mode Exit fullscreen mode

That's where the real differences start.


33. Final Takeaway

I started comparing MSSQL and MySQL expecting something like this:

MSSQL
  ↓
Different syntax
  ↓
MySQL
Enter fullscreen mode Exit fullscreen mode

What I found was closer to:

              SQL
               │
       Shared fundamentals
               │
        ┌──────┴──────┐
        │             │
      MSSQL          MySQL
        │             │
    Architecture   Architecture
    Data types     Data types
    Indexes        Indexes
    Locking        Locking
    Transactions   Transactions
    JSON           JSON
    Security       Security
    Backups        Backups
    Replication    Replication
        │             │
        └──────┬──────┘
               │
          Different
          behavior
Enter fullscreen mode Exit fullscreen mode

And that's the part that matters during migration.

If you're moving from SQL Server to MySQL, don't ask only:

"What SQL syntax do I need to change?"

Ask:

What database behavior am I depending on?

Which features are SQL Server-specific?

Which indexes need redesigning?

What happens to my transactions?

What happens to my stored procedures?

What happens to my collation?

What happens to my Unicode data?

What happens to my backup strategy?

What happens to query performance?

What happens under concurrency?
Enter fullscreen mode Exit fullscreen mode

And if you're moving from ASP.NET to NestJS, don't assume that means you also need to move from MSSQL to MySQL.

Those are separate engineering decisions.

You can do:

ASP.NET → NestJS
Enter fullscreen mode Exit fullscreen mode

while keeping:

MSSQL
Enter fullscreen mode Exit fullscreen mode

and only migrate the database later if there is a good reason.

That's probably the biggest lesson I took away from comparing these two databases:

Learn SQL first. Then learn the database you're actually working with.

Because knowing SQL tells you how to talk to a database.

Knowing the database tells you what that conversation actually means.


Frequently Asked Questions

Is MSSQL better than MySQL?

Not universally.

SQL Server can be a strong choice for Microsoft/.NET ecosystems and enterprise environments. MySQL can be a strong choice for many web applications and organizations already invested in its ecosystem.

The right choice depends on workload, infrastructure, team expertise, operational requirements, and cost.

Is MySQL easier than SQL Server?

That depends on what you're already familiar with.

If you've worked with MySQL, SQL Server will have unfamiliar features. If you've worked with SQL Server, MySQL will have its own learning curve.

The SQL fundamentals transfer, but the database-specific concepts don't always.

Can NestJS connect to MSSQL?

Yes.

Using NestJS does not require MySQL. A NestJS application can communicate with SQL Server through appropriate database drivers and ORMs/query libraries.

Can Node.js use SQL Server?

Yes.

Node.js applications can connect to Microsoft SQL Server.

The backend runtime and database engine are separate architectural choices.

Can I migrate MSSQL to MySQL?

Yes, but it is more involved than exporting tables and importing them.

You need to evaluate:

Data types
Schemas
Queries
Views
Procedures
Functions
Triggers
Indexes
Collation
Transactions
Permissions
Performance
Backups
Enter fullscreen mode Exit fullscreen mode

Is SQL Server syntax the same as MySQL?

No.

There is substantial overlap because both implement SQL concepts, but each database has its own syntax and extensions.

Examples include:

TOP vs LIMIT
IDENTITY vs AUTO_INCREMENT
GETDATE() vs NOW()
ISNULL() vs IFNULL()
T-SQL vs MySQL stored-program syntax
Enter fullscreen mode Exit fullscreen mode

Which is faster: MSSQL or MySQL?

There is no universal answer.

Performance depends on:

  • Query design
  • Indexes
  • Data volume
  • Hardware
  • Configuration
  • Workload
  • Concurrency
  • Database version
  • Application architecture

Benchmark the actual workload instead of comparing databases using a single query.

Should I use MySQL or MSSQL for a Node.js application?

Either can work.

If your organization already has SQL Server infrastructure and expertise, keeping MSSQL may be completely reasonable.

If your infrastructure and team are already centered around MySQL, MySQL may make more sense.

The fact that you're using Node.js isn't enough by itself to decide.

Does moving from ASP.NET to NestJS require changing the database?

No.

You can migrate:

ASP.NET → NestJS
Enter fullscreen mode Exit fullscreen mode

while keeping:

MSSQL
Enter fullscreen mode Exit fullscreen mode

Changing the backend framework and changing the database are separate decisions.

How difficult is MSSQL → MySQL migration?

It depends heavily on the existing application.

A simple CRUD application may be relatively straightforward.

An application with:

Hundreds of stored procedures
Complex views
Triggers
SQL Server-specific functions
Advanced indexing
Complex transactions
Full-text search
Database jobs
Enter fullscreen mode Exit fullscreen mode

can require significant engineering effort.

The database schema is only one part of the migration.


What I'd Remember

If I had to reduce this entire comparison to five points:

  1. MSSQL and MySQL both use SQL, but SQL isn't the database.
  2. Syntax differences are usually the easiest part of a migration.
  3. Indexes, transactions, collations, data types, stored procedures, and database architecture matter much more.
  4. Never blindly copy an MSSQL database design into MySQL.
  5. ASP.NET → NestJS and MSSQL → MySQL are two separate migrations.

Once I started looking at MSSQL and MySQL this way, the comparison became much more useful.

It stopped being:

"Which database has better syntax?"

and became:

"What assumptions does my application make about its database, and will those assumptions still be true after I migrate?"

That's the question I'd ask before changing a production database.

Top comments (0)