Limited Time Offer: 40% off
Back to Blog

MySQL vs SQL Server: Cost, Syntax, and Behaviour Compared

JayJay

MySQL vs SQL Server usually comes down to cost and ecosystem before it comes down to features. MySQL is the open source database behind a large share of web applications. Microsoft SQL Server (often shortened to MSSQL) is a commercial database built around Windows, .NET, and Microsoft's data tools. Both are mature relational databases that will run a typical business application without complaint.

The short verdict: choose MySQL for web applications, open source stacks, Linux hosting, and anywhere licence cost matters. Choose SQL Server when you are already a Microsoft shop, when you need its built-in BI and ETL tools, or when you want high availability and auditing features from a single vendor with one support contract.

If you are asking about SQL the language versus MySQL the product, that is a different question, answered in SQL vs MySQL. This post compares two database products.

I ran every example below in Docker against mysql:8.4 (MySQL 8.4.11) and mcr.microsoft.com/mssql/server:2025-latest (SQL Server 2025 CU9), with default settings on both.

MySQLSQL Server
VendorOracleMicrosoft
LicenceGPLv2 Community Edition, commercial Enterprise EditionCommercial; free Express and Developer editions
PlatformsLinux, Windows, macOSWindows and Linux
SQL dialectMySQL SQLTransact-SQL (T-SQL)
Row limitingLIMITTOP or OFFSET ... FETCH
Default collationCase and accent insensitiveCase insensitive, accent sensitive
Default isolationRepeatable read (MVCC, readers do not block)Read committed with shared locks (readers can block)
Transactional DDLNoYes
UpsertON DUPLICATE KEY UPDATEMERGE
Return changed rowsNo RETURNINGOUTPUT clause
Procedural codeStored programs onlyT-SQL in any batch
High availabilityReplication, Group Replication, InnoDB ClusterAlways On availability groups, failover cluster instances
BI and ETLThird partySSIS, Analysis Services, Power BI Report Server
Managed cloudEvery major cloud, plus PlanetScale and TiDB CloudAzure SQL, Amazon RDS, Google Cloud SQL

MySQL vs SQL Server on cost and licensing

This is where most decisions are made.

MySQL Community Edition is free under the GPLv2. You can run it on as many servers and cores as you like. Oracle sells MySQL Enterprise Edition as a subscription with extra features (such as audit, encryption, and backup tools) and support. Many teams never buy it.

SQL Server is licensed per edition. For SQL Server 2025, Microsoft's documentation lists these limits:

EditionUseCompute limitBuffer pool memoryDatabase size
ExpressFree, production allowedLesser of 1 socket or 4 cores1,410 MB50 GB
Standard DeveloperFree, development and test onlySame as StandardSame as StandardSame as Standard
Enterprise DeveloperFree, development and test onlySame as EnterpriseSame as EnterpriseSame as Enterprise
StandardPaidLesser of 4 sockets or 32 cores256 GB524 PB
EnterprisePaidOperating system maximumOperating system maximum524 PB

Paid editions are licensed per core, sold in two-core packs with a minimum of four core licences per processor, and Standard is also available as Server plus Client Access Licences. Pay-as-you-go billing is available through Azure Arc. Check current prices with Microsoft or a reseller, because they depend on your agreement.

The practical effect: a small application can run on SQL Server Express for free, but it hits the 50 GB and 1,410 MB limits sooner than you might expect. Once you need Standard or Enterprise, licences can cost more than the hardware. MySQL has no equivalent step.

Syntax differences in practice

Both speak SQL, and simple SELECT, INSERT, UPDATE, and DELETE statements look the same. The differences show up quickly after that.

Limiting rows is the first one most people hit. SQL Server has no LIMIT:

1> SELECT * FROM coupons LIMIT 5;
Msg 102, Level 15, State 1, Server 2c81f9644418, Line 1
Incorrect syntax near '5'.
SQL
-- SQL Server
SELECT TOP (5) * FROM coupons ORDER BY code;
SELECT code FROM coupons ORDER BY code OFFSET 0 ROWS FETCH NEXT 5 ROWS ONLY;

-- MySQL
SELECT * FROM coupons ORDER BY code LIMIT 5;

A few others:

TaskMySQLSQL Server
Auto-increment keyid INT AUTO_INCREMENT PRIMARY KEYid INT IDENTITY(1,1) PRIMARY KEY
Quote an identifier`order`[order]
Current timeNOW()GETDATE() or SYSDATETIME()
Concatenate stringsCONCAT(a, b)a + b, CONCAT(a, b), or the double pipe operator (2025)
SELECT 5/22.50002

String concatenation is a trap. SQL Server 2025 added || as a concatenation operator, matching standard SQL. In MySQL's default mode, || means logical OR:

mysql> SELECT 'DB' || ' Pro' AS joined;
+--------+
| joined |
+--------+
|      0 |
+--------+
Warning | 1287 | '|| as a synonym for OR' is deprecated and will be removed in a future release. Please use OR instead
Warning | 1292 | Truncated incorrect DOUBLE value: 'DB'

SQL Server returned DB Pro for the same query.

Integer division differs too. SQL Server returns 2 for 5/2 because both operands are integers. MySQL returns 2.5000. Ported reports can change totals without an error.

Procedural code and scripting

T-SQL is a full procedural language that works in any batch. Variables, IF, loops, and TRY...CATCH run in an ad hoc script:

SQL
DECLARE @n int = 3;
IF @n > 2 PRINT 'more than two';

MySQL's procedural statements only work inside stored procedures, functions, triggers, and events. The same logic in a plain script fails:

mysql> SET @n = 3; IF @n > 2 THEN SELECT 'x'; END IF;
ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'IF @n > 2 THEN SELECT 'x'' at line 1

Teams that write a lot of database-side logic find T-SQL more comfortable. Teams that keep logic in the application rarely notice.

Type strictness

Both reject oversized strings on insert in their default configuration:

MySQL:      ERROR 1406 (22001): Data too long for column 'code' at row 1
SQL Server: Msg 2628, Level 16, State 1
            String or binary data would be truncated in table 'shop.dbo.coupons', column 'code'. Truncated value: 'BLACKFRIDA'.

SQL Server's message names the column and shows the truncated value, which makes debugging faster. In expressions, MySQL converts types where SQL Server refuses:

ExpressionMySQLSQL Server
SELECT '12abc' + 113Msg 245: Conversion failed when converting the varchar value '12abc' to data type int.
SELECT 1/0NULLMsg 8134: Divide by zero error encountered.

Both have a session-level escape hatch. In MySQL, clearing sql_mode turns off strict mode, and oversized values are truncated with a warning. In SQL Server, SET ANSI_WARNINGS OFF with SET ARITHABORT OFF did the same: the insert stored BLACKFRIDA, and 1/0 returned NULL. Check what your driver or framework sets on connect, because the defaults above only hold if nobody changes them.

Collations and Unicode

Both compare strings case-insensitively by default, which surprises people moving from PostgreSQL. They differ on accents. SQL Server's default server collation in the Docker image is SQL_Latin1_General_CP1_CI_AS, where AS means accent sensitive. MySQL 8's default is utf8mb4_0900_ai_ci, accent insensitive:

ComparisonMySQLSQL Server
'Alice' = 'alice'truetrue
'café' = 'cafe'truefalse

Unicode storage is the bigger difference. MySQL's default character set is utf8mb4, so a VARCHAR column stores any character, including emoji. In SQL Server, varchar under a legacy collation stores single-byte code page data, and characters outside it are lost:

1> CREATE TABLE notes (plain varchar(20), wide nvarchar(20));
2> INSERT INTO notes VALUES (N'Zoë 🎉', N'Zoë 🎉');
3> SELECT plain, wide FROM notes;
plain wide
----- ----
Zoë ?? Zoë 🎉

No error was raised. In SQL Server, use nvarchar for user-entered text, or a UTF-8 collation (those ending in _UTF8) if you want varchar to store Unicode.

Transactions and schema changes

SQL Server runs DDL inside transactions. MySQL commits implicitly around DDL. I ran the same migration on both: add a plan column, then create a unique index on a column that already has a duplicate.

SQL Server rejected the index:

Msg 1505, Level 16, State 1
The CREATE UNIQUE INDEX statement terminated because a duplicate key was found for the object name 'dbo.users' and the index name 'users_email_key'. The duplicate key value is (ada@example.com).

After ROLLBACK, the table had only id and email. MySQL raised ERROR 1062 (23000): Duplicate entry 'ada@example.com' for key 'users.users_email_key', and after ROLLBACK the plan column was still there. A failed migration on MySQL leaves a partly changed schema to clean up by hand.

Locking and blocking

This difference causes the most production incidents for teams new to SQL Server.

MySQL's InnoDB uses multiversion concurrency control. A reader sees the last committed version of a row and does not wait for writers. SQL Server's default read committed level uses shared locks instead, so a reader waits for any writer holding a lock on the rows it needs.

To show it, I started a transaction that updated a row and held it open for ten seconds, then read the same row from a second session with a three second lock timeout:

SQL
-- Session 1
BEGIN TRAN;
UPDATE stock SET qty = 0 WHERE sku = 'KB-01';
WAITFOR DELAY '00:00:10';
ROLLBACK;

-- Session 2
SET LOCK_TIMEOUT 3000;
SELECT sku, qty FROM stock WHERE sku = 'KB-01';
Msg 1222, Level 16, State 51
Lock request time out period exceeded.

The same test in MySQL returned the committed row at once. Turning on read committed snapshot isolation in SQL Server changes the behaviour:

SQL
ALTER DATABASE shop SET READ_COMMITTED_SNAPSHOT ON WITH ROLLBACK IMMEDIATE;

With that set, session 2 returned KB-01 8 straight away. Microsoft's documentation says this setting is off by default in SQL Server and on by default in Azure SQL Database. For most new SQL Server applications, turning it on is the right call. SQL Server 2025 also adds optimized locking in Standard and Enterprise editions, which reduces lock memory and blocking.

Upserts and returning rows

MySQL's upsert is one clause on INSERT:

SQL
INSERT INTO stock (sku, qty) VALUES ('KB-01', 3) AS new
ON DUPLICATE KEY UPDATE qty = stock.qty + new.qty;

SQL Server uses MERGE, and its OUTPUT clause returns what happened to each row:

SQL
MERGE stock AS s
USING (VALUES ('KB-01', 3), ('MS-02', 7)) AS v (sku, qty) ON s.sku = v.sku
WHEN MATCHED THEN UPDATE SET qty = s.qty + v.qty
WHEN NOT MATCHED THEN INSERT (sku, qty) VALUES (v.sku, v.qty)
OUTPUT $action, inserted.sku, inserted.qty;
$action sku qty
------- --- ---
UPDATE KB-01 8
INSERT MS-02 7

MySQL has no MERGE and no RETURNING. On the other hand, Microsoft's MERGE documentation warns that concurrent upserts on unique keys may need a HOLDLOCK hint to prevent key violations, and suggests separate INSERT and UPDATE statements where heavy concurrency is expected. MySQL's ON DUPLICATE KEY UPDATE is simpler to get right under load.

DB Pro

Work With Your Databases Like A Pro

Query, explore, and manage your databases with a beautiful desktop app and built-in AI.

Download Now
DB Pro Dashboard

JSON, vectors, and newer features

SQL Server 2025 added a native json data type, a vector type, and regular expression functions such as REGEXP_LIKE. All three worked in the container without extra configuration:

SQL
CREATE TABLE events (
  id bigint IDENTITY(1,1) PRIMARY KEY,
  payload json NOT NULL,
  embedding vector(3)
);
SELECT id, JSON_VALUE(payload, '$.plan') AS [plan] FROM events;

Vector indexes and approximate vector search in SQL Server 2025 still require the PREVIEW_FEATURES database scoped configuration.

MySQL has had a JSON type since 5.7, with functional and multi-valued indexes, and regular expression functions since 8.0. MySQL 9.x added a VECTOR column type, but the DISTANCE() function needed for similarity search is only available in Oracle's HeatWave service, not the Community or Commercial distributions. SQL Server is ahead here for teams that want vector search in the same database.

SQL Server also has built-in system-versioned temporal tables, which keep row history automatically:

SQL
SELECT sku, price FROM prices FOR SYSTEM_TIME ALL ORDER BY valid_from;
sku price
--- -----
KB-01 49.00
KB-01 59.00

MySQL has no built-in equivalent. You write history tables with triggers or in the application.

High availability and scaling

SQL Server's high availability centres on Always On availability groups and failover cluster instances. Full availability groups need Enterprise edition. Standard edition supports basic availability groups (two replicas, one database each) and two-node failover clusters. Online index rebuilds are also Enterprise only.

MySQL includes asynchronous and semi-synchronous replication, Group Replication, and InnoDB Cluster in the free Community Edition. For horizontal sharding, Vitess and MySQL-compatible distributed databases such as TiDB are established options. SQL Server has no open source sharding layer of the same standing.

If you need automatic failover on a budget, MySQL gets you there without an edition upgrade.

Tools and ecosystem

SQL Server ships with a large toolset: SQL Server Management Studio (Windows only), SQL Server Agent for scheduled jobs (not in Express), Integration Services for ETL, Analysis Services, Query Store, and Power BI Report Server. It integrates with Active Directory and Microsoft Entra ID for authentication, and with Visual Studio and .NET. If your company runs on Microsoft, it fits in with little effort.

MySQL's ecosystem is broader but spread across vendors: MySQL Workbench, MySQL Shell, mysqldump (see our mysqldump guide), Percona's tools, and nearly every open source framework and CMS. WordPress, Drupal, and most PHP hosting assume MySQL or MariaDB.

A cross-platform client helps if you work with both. DB Pro connects to MySQL and SQL Server from macOS, Windows, and Linux. See the MySQL client and SQL Server client pages.

Cloud hosting

SQL Server's first-class cloud is Azure: Azure SQL Database, Azure SQL Managed Instance, and SQL Server on Azure virtual machines. Amazon RDS and Google Cloud SQL also offer SQL Server, typically with the licence included in the hourly price.

MySQL is available on every major cloud and many smaller ones: Amazon RDS and Aurora, Google Cloud SQL, Azure Database for MySQL, PlanetScale, TiDB Cloud, and most VPS hosts. More competition keeps managed MySQL cheaper, and MySQL alternatives covers the compatible options.

When to choose MySQL

  • You are building a web application on an open source stack.
  • Licence cost matters, now or at the scale you expect.
  • You deploy on Linux, containers, or a cloud other than Azure.
  • You run software built for MySQL, such as WordPress.
  • You want free built-in replication and failover.

When to choose SQL Server

  • Your organisation already runs Windows Server, Active Directory, and .NET.
  • You need SSIS, Analysis Services, or tight Power BI integration.
  • You want one vendor for the database, tools, and support.
  • Your team writes a lot of T-SQL, stored procedures, or database-side logic.
  • You need temporal tables, native vector search, or Enterprise auditing features today.

If PostgreSQL is also on your list, it sits between the two: free like MySQL, with SQL features closer to SQL Server. See PostgreSQL vs MySQL and PostgreSQL vs SQL Server.

Migrating between them

Moving from SQL Server to MySQL, or the other way, is a rewrite of the database layer more than a data copy:

  1. Convert IDENTITY to AUTO_INCREMENT, TOP to LIMIT, and bracketed identifiers to backticks.
  2. Map nvarchar to varchar with utf8mb4, and datetime2 to datetime.
  3. Rewrite stored procedures. T-SQL and MySQL's procedural syntax differ in variables, error handling, and temporary tables.
  4. Check every division, string concatenation, and accent-sensitive comparison, because the defaults differ.
  5. Replace MERGE with INSERT ... ON DUPLICATE KEY UPDATE, and OUTPUT with a follow-up SELECT.

The verdict

MySQL is the better default for most new web applications. It is free at any scale, runs anywhere, and does not block readers behind writers out of the box.

SQL Server earns its cost inside Microsoft-centred organisations, where its tools, T-SQL, and Azure integration save more time than the licences cost. Outside that environment, the licence bill is hard to justify for a typical application.

Keep Reading