I have encountered the same situation twice in a short period of time: SQL Server is running, everything seems fine, but the performance is not meeting what the hardware should be capable of. In both cases, the reason was in the virtual layer — the server was misconfigured before the installation of SQL Server.
When it comes to virtual machines, the two most common mistakes are: incorrect CPU topology and dynamic memory. Both are easy to overlook — and that's what makes these issues dangerous.
CPU Socket Issue — Standard Edition Limitation
SQL Server Standard Edition is licensed based on the number of sockets and total cores — both limitations apply simultaneously:
- SQL Server until 2022: maximum of 4 sockets and 24 cores
- SQL Server 2025: maximum of 6 sockets and 32 cores
In a physical server, there are usually 1–2 actual sockets and the number of cores remains within the limits, so this issue generally does not arise. However, in a virtual layer, one can go wrong in both ways.
Incorrect socket topology: If 8 logical cores are divided among 8 virtual sockets (8 × 1 core), SQL Server perceives it as 8 separate processors. The Standard Edition disables the excess sockets completely — utilizing only 4 × 1 core. Half of the allocated performance is lost.
Incorrect total core count: Even with the correct socket topology in a VM, one can still hit the core limitation. For example, 4 sockets × 8 cores = 32 cores — SQL Server 2022 will only utilize 24 of them. The correct configuration is not only about the number of sockets but also the total core count must fit within the limit.
In both cases, the result is the same: SQL Server operates quietly below its capabilities, without showing any error messages.
When initiating this event, SQL Server logs in the ERRORLOG:
SQL Server detected N sockets with X cores per socket and X logical processors;
using X of X total sockets based on SQL Server licensing.
If the values of "using X of X total sockets" do not match, there is an issue. Additionally, you can check with:
SELECT cpu_count, socket_count, cores_per_socket
FROM sys.dm_os_sys_info;
If the socket_count is significantly higher than cores_per_socket, it is worth reviewing the VM configuration.
Dynamic Memory — an Issue That SQL Server Itself Does Not See
In shared environments, it is common practice to allocate dynamic memory to VMs — the hypervisor allocates memory according to current needs and shares free resources among other VMs. In terms of resource utilization, this is reasonable. However, for SQL Server, this is problematic — and an important detail that many administrators are not aware of: Microsoft does not officially support Hyper-V Dynamic Memory with SQL Server. This does not mean it does not work, but Microsoft does not guarantee stability or performance.
SQL Server allocates memory for the buffer pool at startup and assumes that this memory is consistently available. SQL Server does not voluntarily release the buffer pool — the hypervisor must forcibly reclaim memory through the balloon driver. If the balloon driver operates aggressively, the consequences are not just increased latency — more serious problems can arise:
- IO storms — forcibly shrinking the buffer pool pushes data to disk that SQL Server otherwise kept in memory
- RESOURCE_SEMAPHORE wait — queries wait for memory allocation that would otherwise be immediate
- General instability during load spikes when the host itself is heavily loaded
Diagnostics here are essentially impossible from the SQL Server level. Unlike the CPU socket issue, dynamic memory does not leave a trace in SQL Server logs. The only way to ensure is by checking the VM configuration at the host level.
Why Do These Issues Occur So Easily?
The virtualization platform does not know or care about the software running in the VM. Dynamic memory is designed precisely for efficient resource sharing — and it works for normal workloads. However, the peculiarities of SQL Server's memory behavior and licensing logic need to be carefully considered before creating a VM.
The CPU topology issue is diagnostic — if you know where to look, you will see it immediately in the ERRORLOG. The memory misconfiguration is hidden: the problem only surfaces when someone inspects the configuration at the host level, or when the situation is already present.