To understand latches and
spinlocks, you will need to consider their actual definitions, and
consider why they are required in SQL Server.
Definitions
You might recall from the discussion
about locks that they are vital to the protection of data. In fact, it
was suggested that without locks, there is no guarantee of data
integrity, and all would be chaos. Latches perform the same function,
but at another level. While locks protect our data from a logical
perspective, ensuring that no one accesses the same table (or whatever)
during someone else’s transaction, latches do this for memory.
In other words, despite
the fact that you might think of your data as living in tables and
indexes, these tables and indexes must be implemented in memory in
order to enable the database engine to work its magic. To be used at
all, data needs to be loaded off disk into RAM, making it available
when needed. Latches protect this process of loading the data, and
protect the data that has been already loaded. Similar to locks,
latches are acquired when required, and have modes with various levels
of compatibility. You’ll learn more about these modes in a minute, and
about some of the techniques that the SQL Server engine uses to
efficiently manage latches.
You may already be thinking that because you can,
to a certain extent, avoid locking trouble by simply setting the
appropriate isolation level in your application, you ought to be able
to do the same with latches. You can’t.
A latch is an internal object, used by the SQL
Server engine. It is not something that you, the database developer,
can directly influence. If you need to get data from a particular page,
the SQL Server engine needs to acquire a latch. You have no choice over
this. Nor can you tell it what kind of latch to acquire — that’s
determined by the SQL Server engine. The difference is that this is not
just about the protection of data, it’s about the protection of server
memory. Although you might be willing to tolerate dirty reads, and
choose your locking strategy accordingly, you don’t have that luxury
with latches.
Spinlocks are a similar concept to latches, in
that they are also lightweight synchronization primitives, but they act
slightly differently. A lot of the effects can seem similar, and the
kinds of systems that can exhibit spinlock contention are similar to
those that can exhibit latch contention.
The main difference between a spinlock and a
latch is this: If a thread fails to acquire a latch immediately, it
yields, enabling the CPU to be used for other things. If a thread fails
to acquire a spinlock, the thread starts looping (spinning), checking
the resource repeatedly, with the expectation that it will become
available soon. It won’t spin forever, though. After a bit of time, it
will back off, at which point it yields to other processes on the CPU.
Latching Example
All of the code in this section uses the Ch7Understanding.sql code file.
To begin, suppose you have a table that contains a single row of data. The following code will set up such an environment.
CREATE DATABASE LatchInAction;
GO
USE LatchInAction;
CREATE TABLE dbo.LatchTable
( COL1 INT
,COL2 INT
);
INSERT INTO dbo.LatchTable ( COL1, COL2 )
VALUES (1,100);
Running DBCC IND will provide information about the pages that are used in the table. You will use the PagePID value of the row which has a PageType value of 1. The PageType column is the tenth column returned, so you may need to scroll. In my system, the value I’m looking for is 73, as seen in Figure 1.
DBCC IND(LatchInAction,'dbo.LatchTable',-1);
Now run DBCC PAGE to get the output of the table. Before doing that, though, you need to use DBCC TRACEON(3604) to output the results to the screen.
DBCC TRACEON(3604);
DBCC PAGE('LatchInAction',1,73,1);
The output is shown in Figure 2.
The noteworthy elements for this example are:
- In the PAGE HEADER section, the values m_slotCnt = 1 and m_freeData = 111
- In the DATA section, in Slot 0, the value Length 15
- In the OFFSET TABLE section, the Offset 96
This tells us that there is a single row
(slot) in the page. This is Slot 0, which is 15 bytes long. This row
starts at position 96 in the page. From position 111 on is empty
(freedata). Not coincidentally, 111 = 96 + 15.
You can picture the page as in Figure 3.
Consider that the white text on black background
indicates the page header information, including the offset table. The
grey background is the row containing (1,100), at position 96. The
white background blocks are freedata, waiting to be allocated to
further slots.
Now you can try doing some inserts, from two different sessions:
/*TRANSACTION 1 SESSION 1*/
INSERT INTO LatchTable
VALUES (2,200);
/*TRANSACTION 2 SESSION 2*/
INSERT INTO LatchTable
VALUES (3,300);
These inserts are concurrent and are
received by the Lock Manager at the same time. Neither row exists, so
there is no Exclusive (X) lock available on the row just yet. Both
sessions receive an Intent Exclusive (IX) lock on the page, which are
compatible with one another.
The transactions now proceed to the
Buffer Manager to write their respective rows. The page is in memory,
and both start to read it. The following two sections describe what can
happen next. In the first fictitious scenario, latches do not exist.
Then, once you have seen the problem that causes, the second section
demonstrates how latches prevent it.