Help: SQL Server

Sharing my knowlege about SQL Server Troubleshooting Skills

  • Blog Stats

    • 2,153,609 hits
  • Select GETDATE()

    December 2025
    M T W T F S S
    1234567
    891011121314
    15161718192021
    22232425262728
    293031  

Posts Tagged ‘database’

A-Z of In-Memory OLTP : Hash Indexes (Part 1)

Posted by blakhani on January 9, 2014


Let’s imagine you came to meet my friend Vinod (b|t) and asked him to find “Readable Secondary” word in our book on AlwaysOn of 420 pages. How would he do it? Do you think he would start reading each and every page to find that word? No! He knows that there is a Index page at the end of the book which will tell him the word and the page number where its located. Our search becomes easier with the help of Index, correct? In the same way, SQL Server also uses indexes to find a row which you are asking to select/update/delete.

In my previous blog about T-SQL constructs, we have discussed that index on in-memory need to be created during table creation itself. Alter of in-memory table is not allowed. Here are the few point we should be aware.

  • Indexes on in-memory tables reside only in memory. Only index definition is stored on disk and index structure is created while starting of the database. Refer this blog.
  • At least one index is needed. For SCHEMA_AND_DATA durability a primary key needs to be created.

Msg 41321, Level 16, State 7, Line 21
The memory optimized table ‘TableName’ with DURABILITY=SCHEMA_AND_DATA must have a primary key.
Msg 1750, Level 16, State 0, Line 21
Could not create constraint or index. See previous errors.

  • If we use SCHEMA_ONLY as durability then here is the error message if there is no index declared.

Msg 41327, Level 16, State 7, Line 21
The memory optimized table ‘TableName’ must have at least one index or a primary key.
Msg 1750, Level 16, State 0, Line 21
Could not create constraint or index. See previous errors.

  • There is no concept of clustered index on in-memory table. Only non-clustered indexes are supported.
  • Total 8 Indexes are supported on in-memory tables. Exceeding this would cause below error message

Msg 1910, Level 16, State 1, Line 14
Could not create in-memory index ‘IndexName’ because it exceeds the maximum of 8 allowed per table or view.
Msg 1750, Level 16, State 0, Line 14
Could not create constraint or index. See previous errors.

  • String column on which index is created must use *_BIN2 collation else we would encounter below error.

Msg 12328, Level 16, State 106, Line 14
Indexes on character columns that do not use a *_BIN2 collation are not supported with indexes on memory optimized tables.
Msg 1750, Level 16, State 0, Line 14
Could not create constraint or index. See previous errors.

Here is the sample failure and the solution for error 12328:

CREATE TABLE Failure (
    [iID] [int] NOT NULL,
    [vName] [nvarchar](20) NOT NULL PRIMARY KEY NONCLUSTERED HASH WITH ( BUCKET_COUNT = 1024),
)WITH ( MEMORY_OPTIMIZED = ON , DURABILITY = SCHEMA_AND_DATA )
GO

/*
Msg 12328, Level 16, State 106, Line 14
Indexes on character columns that do not use a *_BIN2 collation are not supported with indexes on memory optimized tables.
Msg 1750, Level 16, State 0, Line 14
Could not create constraint or index. See previous errors.
*/

CREATE TABLE Success (
    [iID] [int] NOT NULL,
    [vName] [nvarchar](20) COLLATE Latin1_General_100_BIN2 NOT NULL PRIMARY KEY NONCLUSTERED HASH WITH ( BUCKET_COUNT = 1024),
)WITH ( MEMORY_OPTIMIZED = ON , DURABILITY = SCHEMA_AND_DATA )
GO
/*
Command(s) completed successfully.
*/

Another option would be to specify the collation during database creation itself. Using this, we need not specify different collation during table creation as column’s default collation would be database collation (highlighted in below script)

CREATE DATABASE SQLSeverHelp_IMO 
    ON PRIMARY (
    NAME = [SQLSeverHelp_IMO_data]
    ,FILENAME = 'C:\IMO_Database\SQLSeverHelp_IMO.mdf'
    )
    
    ,FILEGROUP [SQLSeverHelp_IMO_FG] CONTAINS MEMORY_OPTIMIZED_DATA ( 
    NAME = [SQLSeverHelp_IMO_dir]
    ,FILENAME = 'C:\IMO_Database\SQLSeverHelp_IMO_dir'
    ) 
    
    LOG ON (
    NAME = [SQLSeverHelp_IMO_log]
    ,Filename = 'C:\IMO_Database\SQLSeverHelp_IMO_log.ldf'
    ) 
COLLATE Latin1_General_100_BIN2
GO
use SQLSeverHelp_IMO
go
CREATE TABLE Failure(
    [iID] [int] NOT NULL,
    [vName] [nvarchar](20) NOT NULL PRIMARY KEY NONCLUSTERED HASH WITH ( BUCKET_COUNT = 1000),
)WITH ( MEMORY_OPTIMIZED = ON , DURABILITY = SCHEMA_AND_DATA )
GO

 

Now, let’s understand the type of indexes which can be created on in-memory tables. There are two kind of Indexes, Hash Indexes and Range Indexes. In this blog, I would explain hash indexes. Since there is a lot of information, I will continue about hash indexes in next post as well.

At this point, it is important that you know the row structure of in-memory tables. I have already blogged about it in my previous post here.

HASH INDEX

Hash index is a list of pointers and each of them is called as bucket. (Wikipedia) The number of buckets needs to be specified while creating the index. It is important to note that the number of hash buckets can’t be changed once table is created. There is no alter operation possible so we would end up in dropping and recreating the table for re-declaring the bucket count. Let’s assume for simplicity that I have created index with 4 buckets. I will start with very simple example with 4 has buckets with three rows and only one hash index.

image

For simplicity purpose, I have assumed the hash function as length of the column and it’s created on FName column (Note that the real hash function used in SQL is much more random and unpredictable, but to illustrate I am using Length function) Len(Vinod) = 5 so it would fall into first bucket and there would be a pointer which is pointing to actual row (middle row in image). In the same way (length), PinalD would be in hash bucket 2 (hashed values = 6) and Balmukund would be in hash bucket 3 (hashed values = 9)

Now, let’s assume that I insert another value SQLServer into Name column. Which bucket it would be? Since len(SQLServer) = 9 it would fall into bucket # 3 but we already have a row sitting there. This is the situation which is called as “Hash Collision” In this case, SQL Server would add a pointer in the existing row to point to next row as shown below.

image

Now imagine a situation that we have many rows (may be 10000) and have less buckets (only 4) then there would be a lots of hash collisions and length of chain would keep on increasing for each bucket. This would cause performance degradation while search for data in a bucket. Let’s understand a small concept about BUCKET_COUNT parameter in SQL Server.

BUCKET_COUNT

Number of bucket for a hash index is defined using BUCKET_COUNT parameter during index creation. SQL Server creates number of hash buckets as always power of 2. If we declare number of bucket which is not a power of 2 then it would be automatically rounded-off to next number which is exact power of 2. In my example above, I have given BUCKET_COUNT as 1000. Doing a little math, we can easily find that next valid value would be 1024 (2^10). Let’s have a look at demo to understand this better.

In below script, I would create 65 tables with there name as MemporyOptimizedTable<n> where n is a integer between 1 to 65 and also denotes number of buckets for each table. (MemporyOptimizedTable1 has 1 bucket, MemporyOptimizedTable2 has 2 buckets and so on…)

-- use the database which is already created
use SQLSeverHelp_IMO
go
-- declare the looping variables
declare @loopCounter int , @CreateString nvarchar(4000)
-- initialize
Select @loopCounter = 1, @CreateString = ''

while @loopCounter <= 65 -- loop 65 times
begin
    -- build dynamic string with table name and bucket count having loop counter
    set @CreateString = '

    CREATE TABLE MemporyOptimizedTable'+ CONVERT(varchar(10), @loopCounter)+'
    (
    iID INT NOT NULL PRIMARY KEY NONCLUSTERED HASH WITH (BUCKET_COUNT = '+CONVERT(varchar(10), @loopCounter) +'),
    vFName VARCHAR(20) NOT NULL,
    vLName VARCHAR(20) NOT NULL
    ) WITH (MEMORY_OPTIMIZED = ON, DURABILITY = SCHEMA_AND_DATA)'

    -- execute dynamic query
    Exec (@CreateString)
    -- increment the counter
    set @loopCounter = @loopCounter+1
end

 

Now, let’s look at the bucket_count which SQL has created using catalog view sys.hash_indexes

Select    object_name( object_id) 'Table Name',
        bucket_count            'Number of Buckets'
from    sys.hash_indexes
order    by 2, 1 asc

 

image

As we can see above that even if we have declared Bucket_count as 9, SQL Server has rounded off to 16. Here is the simple script which can be used to find the number of buckets for a given input value.

DECLARE @inputNumber int = 65 ;    -- BUCKET_COUNT during index creation

DECLARE @BucketsCreatedBySQL int = 0;
-- Check of @inputNumber is power of 2. if yes, that's the bucket number

IF (LOG(@inputNumber,2) = ROUND(LOG(@inputNumber,2),0))    
    SELECT @BucketsCreatedBySQL = @inputNumber;
ELSE
-- If not power of 2, round off to next power of 2
    SELECT @BucketsCreatedBySQL = POWER( 2, floor(LOG(@inputNumber,2))+1); 
SELECT @BucketsCreatedBySQL [BucketsCreatedBySQL]


image

This blog has become longer than I expected.. There is a lot more to cover about hash index I would continue discussion about hash indexes in next blog.

  • Cheers,
  • Balmukund Lakhani
  • Twitter @blakhani
  • Author: SQL Server 2012 AlwaysOnPaperback, Kindle
  • Posted in A - Z Series, In Memory OLTP, In-Memory OLTP, SQL Server 2014 | Tagged: , , , , , , , , , , , , , , | 5 Comments »

    A-Z of In-Memory OLTP : Storage structure of rows

    Posted by blakhani on January 7, 2014


    In our earlier posts we have learned about various concepts. In this post, we would understand how the data is actually stored in the row. This would form a foundation to understand indexes created on in-memory table. There are major difference in storage format of disk based table vs. in-memory table. Here are few of them.

    • There is no 8 KB page concept for in-memory table and so there is no concept of extent.
    • Rows belonging to same table need not be in “near” other rows.
    • The rows are linked to each other via index pointers and that’s why we need at least one index on in-memory table.
    • No concept of clustered index because there is no sorting of data rows.
    • Theoretically rows are free flowing and there is not limit on size of row BUT since we are allowed to move the data to disk-based table (using insert), there is a limit of row size to 8060 bytes.
    • Every data type is NOT allowed for columns (like varchar(max), LOB, sqlvariant etc.)

    Here is the pictorial representation of a row:

    image

    Row Data is the actual “data” of the row, i.e. the values which we insert into table columns. On the other hand, Row Header is information about row itself. Row headed consists of many fields. Begin TimeStamp is a field which contains the transaction timestamp when row was inserted. End TimeStamp is a field which contains the transaction timestamp when row was deleted. Each Timestamp field is 8 bytes (16 total). This means that these two values are used to identify “visibility” of a row for a transaction. You might ask what would be the value of End Timestamp when a row is inserted? Well, it’s a special value which is referred at infinity. In my earlier post, we have learned that UPDATE = DELETE + INSERT. This means that if any update happens, we “close” the existing row by putting end timestamp and “open” a new row by inserting new row with new begin timestamp and end timestamp as infinity. Here is the example.

    image

    After the update transaction, the visibility of first row is only for those transactions which have timestamp between 100 and 200.

    Another field in Row Header is Statement ID which is unique for each statement. This is 4 bytes filed. This field stores the ID of the statement which has created the row.

    Next field is Index Link Count which specifies the number on indexes the row has. And final field in the row header is the pointer to the next row for the index. Since each pointer is 8 bytes so the length of this field depends on number of Index on row. If we have 4 indexes, the length of this field would be 4*8 = 32 bytes.

    Once you are clear with row storage, it would be easy to understand about special indexes which are used for in-memory tables. In next blog posts we would discuss about hash indexes and range indexes.

    Stay Tuned!

  • Cheers,
  • Balmukund Lakhani
  • Twitter @blakhani
  • Author: SQL Server 2012 AlwaysOnPaperback, Kindle
  • Posted in A - Z Series, In Memory OLTP, In-Memory OLTP, SQL Server 2014 | Tagged: , , , , , , , , , , , , | 3 Comments »

    A-Z of In-Memory OLTP : Troubleshooting database Startup and recovery failures

    Posted by blakhani on January 2, 2014


    As promised in my previous blog, I am going to demonstrate few failures of database startup and technique to find the cause and fix them. First trick which you should always remember is that whenever you see any “operating system error” with some number (2, 5, 32 etc.) try to find the text of that error number. The trick here is to use “Net HelpMSG” windows command to convert the number to text. Here are few quick samples.

    C:\WINDOWS\system32>net helpmsg 2
    The system cannot find the file specified.

    C:\WINDOWS\system32>net helpmsg 5
    Access is denied.

    C:\WINDOWS\system32>net helpmsg 32
    The process cannot access the file because it is being used by another process.

    image

    Now, we would first cause DLL generation failure during startup of database. As learned in earlier post, database startup (a.k.a. recovery) process would fail.

    image

    Here is what we would see in ERROLROG.

    2013-12-30 03:25:44.280 spid51       Starting up database ‘SQLSeverHelp_IMO’.
    2013-12-30 03:25:44.520 spid39s      restoreHkDatabase: DbId 8, Msg 41312, Level 16, State 4, Unable to call into the C compiler. GetLastError = 2.
    2013-12-30 03:25:44.520 spid39s      [ERROR] Database ID: [8]. Failed to load XTP checkpoint. Error code: 0x82000008. (e:\sql12_main_t\sql\ntdbms\hekaton\sqlhost\sqlmin\hkhostdb.cpp : 2558 – ‘RecoverHkDatabase’)

    Now, you would understand why I spoke about Net Helpmsg command. Error 2 = The system cannot find the file specified. Now, next task is to find out which path/file. To find any activity done by a process, one of the famous tool to use is Process Monitor (free tool by Mark Russinovich). I have run the tool and applied filter for Process Name = sqlservr.exe. Here is the filtered information from process monitor.

    image

    It means that SQLServr.exe is looking for CL.EXE under C:\Program Files\Microsoft SQL Server\MSSQL12.MSSQLSERVER\MSSQL\Binn\Xtp\VC\bin folder and unable to find the path. Indeed that is the problem. To do this failure, I have renamed bin folder to bin_xxx as shown below.

    image

    Once I renamed the folder back to “bin”, I was able to bring the database online.

    I have simulated another failure of DLL generation by making some changes on operating system level and restarted SQL Server.

    image

    Always check errorlog and here is what we would see.

    2013-12-31 20:01:35.040 spid20s      SetNamedSecurityInfoW failed for directory C:\Program Files\Microsoft SQL Server\MSSQL12.MSSQLSERVER\MSSQL\DATA\xtp for reason 5.

    2013-12-31 20:01:35.040 spid20s      Error: 41334, Severity: 16, State: 2.
    2013-12-31 20:01:35.040 spid20s      The code generation directory ‘C:\Program Files\Microsoft SQL Server\MSSQL12.MSSQLSERVER\MSSQL\DATA\xtp’ cannot be created or set up correctly.
    2013-12-31 20:01:35.060 spid20s      Error: 41334, Severity: 16, State: 2.
    2013-12-31 20:01:35.060 spid20s      The code generation directory ‘C:\Program Files\Microsoft SQL Server\MSSQL12.MSSQLSERVER\MSSQL\DATA\xtp’ cannot be created or set up correctly.
    2013-12-31 20:01:35.060 spid20s      Error: 41334, Severity: 16, State: 2.
    2013-12-31 20:01:35.060 spid20s      The code generation directory ‘C:\Program Files\Microsoft SQL Server\MSSQL12.MSSQLSERVER\MSSQL\DATA\xtp’ cannot be created or set up correctly.
    2013-12-31 20:01:35.170 spid19s      Error: 41334, Severity: 16, State: 2.
    2013-12-31 20:01:35.170 spid19s      The code generation directory ‘C:\Program Files\Microsoft SQL Server\MSSQL12.MSSQLSERVER\MSSQL\DATA\xtp’ cannot be created or set up correctly.

    Important piece of error message is reason 5. If you recall what you read above, net helpmsg 5 = Access Denied.

    How would you troubleshoot? Again same tool, process monitor. If we filter for all access denied (right click on Access Denied text and say "Include"), it’s clear that Service account is trying to generate the DLL of table but unable to do so.

    image

    Checking properties of XTP folder reveals the same. To break the things, I given “deny” on that folder to SQL Service account.

    image

    Once I removed “deny” and gave “allow”, I was able to bring database online using below command

    Alter database SQLSeverHelp_IMO set online
    go

    Another possible reason of database not able to recover would be out-of-memory errors. Since all data needs to be loaded into memory before in-memory tables can be accessed. In-memory engine needs to have all the memory available to hold the data at startup of the database. If the system is low on memory or max server memory is set to too low, we might encounter recovery failure. In such situation, you may want to stop other processes running on the server so that SQL is having enough RAM to load in-memory tables.

    I hope that this blog has given you some hints about possible failures, troubleshooting tricks and their resolutions.

  • Cheers,
  • Balmukund Lakhani
  • Twitter @blakhani
  • Author: SQL Server 2012 AlwaysOnPaperback, Kindle
  • Posted in A - Z Series, In Memory OLTP, In-Memory OLTP, SQL Server 2014 | Tagged: , , , , , , , , , , , , | 11 Comments »