Wednesday, April 11, 2012

Filtered Indexes


                There are many performance and space-saving strategies that we can employ as Database Administrators and Developers.  One of the tried-and-true methods is through the utilization of filtered indexes.  Below is how you would create a filtered index:

create index IX_YourIndexName
on YourTable(YourKeyColumn)
where YourColumn < 10
go

                In the above snippet, you can see that creating a filtered index is very much like creating any other index:  The alteration is through the use of the WHERE clause to narrow down the scope of the indexed rows.  Namely two advantages of filtered indexes are the saved space, as well as the maintenance of the index could potentially be less.

                When would you use a filtered index?  Often times we have tables that contain data that won’t ever be queried (or queried often enough for the storage consumption and indexing benefits).  For instance, if you only typically look for products that actually have a cost associated, you are narrowing down your query.  And because it may heed us to have an additional index on this table, the rows that have no cost associated with them will most likely never be retrieved (or not enough to warrant inclusion in the index).

                Here’s an example of real-time space-saving proof.  (The below example uses the AdventureWorks2012 sample database which can be found here)

                I wanted to choose a relatively large table in the AdventureWors2012 database to emphasize the filtered index advantage, so I chose the Sales.SalesOrderDetail table.  Run the below query to view a summary of the indexes on this table:

use AdventureWorks2012
go

select
       object_name(p.object_id) as table_name,
       i.name as index_name,
       i.type_desc as index_type,
       ps.row_count,
       ps.reserved_page_count,
       ps.reserved_page_count * 8 as reserved_space_kb
from sys.partitions p
inner join sys.indexes i
on p.object_id = i.object_id
and p.index_id = i.index_id
inner join sys.objects o
on p.object_id = o.object_id
inner join sys.dm_db_partition_stats ps
on p.partition_id = ps.partition_id
where o.is_ms_shipped = 0
and object_name(p.object_id) = 'salesorderdetail'
order by ps.row_count desc

                Now focus in on the nonclustered index called IX_SalesOrderDetail_ProductID:  It consumes a total of 2624 KB of space.  In order to get what the indexed columns are for this index, we can run the following simple query:

use AdventureWorks2012
go

select
       object_name(i.object_id) as table_name,
       i.name as index_name,
       i.type_desc,
       c.name as column_name,
       ic.is_included_column
from sys.indexes i
inner join sys.index_columns ic
on i.object_id = ic.object_id
and i.index_id = ic.index_id
inner join sys.objects o
on i.object_id = o.object_id
inner join sys.columns c
on c.object_id = i.object_id
and ic.column_id = c.column_id
where o.is_ms_shipped = 0
and object_name(i.object_id) = 'salesorderdetail'
order by table_name, i.type_desc, index_name

So now we know that the indexed column is ProductID (as the index name would have suggested).  Let’s go ahead and drop that index (for testing purposes only).

-- drop the out-of-the-box NCI on ProductID
drop index IX_SalesOrderDetail_ProductID
on Sales.SalesOrderDetail
go

                Why?  Well say that for whatever business domain reason we typically never query records in this table that have a ProductID of greater than 800.  We don’t want the overhead of maintaining this extra index, so we are going to drop it with hopes of a newer, smaller filtered index.  Now create the filtered index based off of our guidelines:

-- create the filtered index
create index IX_SalesOrderDetail_ProductID_Filtered
on Sales.SalesOrderDetail(ProductID)
where ProductID < 800
go

                There you have it.  Now we have a filtered index on the Sales.SalesOrderDetail.  Go ahead and run the index consumption query again to find out our space savings:

use AdventureWorks2012
go

select
       object_name(p.object_id) as table_name,
       i.name as index_name,
       i.type_desc as index_type,
       ps.row_count,
       ps.reserved_page_count,
       ps.reserved_page_count * 8 as reserved_space_kb
from sys.partitions p
inner join sys.indexes i
on p.object_id = i.object_id
and p.index_id = i.index_id
inner join sys.objects o
on p.object_id = o.object_id
inner join sys.dm_db_partition_stats ps
on p.partition_id = ps.partition_id
where o.is_ms_shipped = 0
and object_name(p.object_id) = 'salesorderdetail'
order by ps.row_count desc

                We should get a reserved_space_kb of now roughly 968 KB.  Due to the use of a filtered index (intelligently created, of course), we were able to knock down the size of that index to about 37% of the original size.

                Filtered indexes can be a great way to minimize wasted storage and index maintenance overhead.  With the knowledge of this tool and the appropriate application, it can yield impressive results.

                The applicable code for this blog post can be found on my GitHub SQLSalt repository.  The following links to the source code are:

                If there are any comments, questions, or issues please feel free to leave a comment or email me at sqlsalt@gmail.com.

Monday, April 9, 2012

Dedicated Administrator Connection

                There will be times when a standard connection to SQL Server isn’t possible and as a DBA you’ll need to connect to troubleshoot issues with the instance.  You can achieve this through the Dedicated Administrator Connection (known as the DAC).  The DAC has its own set of resources so that even upon memory contention, you will be able to obtain a connection (provided you are utilizing a login in the sysadmin fixed server role).

Enabling Remote Admin Connections
                By default, you can only connect to the instance’s DAC on the server locally (i.e. Network connections aren’t allowed).  This ensures the surface area of the instance is as small as possible out of the box.  But to enable remote connections to the DAC, you simply need to set the instance configuration appropriately:

-- set 'remote admin connections' enabled
exec sp_configure 'remote admin connections', 1
go
reconfigure
go

DAC Port
                When SQL Server starts up, it will establish a listening port for the DAC.  For a Default instance SQL Server will attempt to establish TCP 1434.  But if it can’t allocate this port, or if it is a named instance then SQL Server will dynamically reserve a port.  During instance startup when this happens, SQL Server logs the established port in the Error Log.  In order to find out what port this is, you can do the following:

exec sp_readerrorlog 0, 1, 'dedicated admin connection'
go

The Text field will show something like this:
Dedicated admin connection support was established for listening remotely on port 1434.

Connecting to the DAC
                There are two ways to connect to the DAC: through SQLCMD and SSMS.

SQLCMD
                To connect to the DAC through SQLCMD, you will need to utilize the –A switch.  A sample connection would look as follows:

      sqlcmd –S YourServer\YourInstance –U sa –P My$ecur3P@$$w0rd –A

SQL Server Management Studio (SSMS)
                To connect through SSMS, there’s a bit of a “gotcha”.  You can’t connect the Object Explorer to the DAC due to the nature of SSMS using multiple connections normally.  To get around this, you will need to follow the below steps:
1.       Click on the “New Query” button to open a New Query Window: 

2.       Right-click in the query window and mouse-over “Connection”.  If this window already has a connection, you can select “Change Connection…” or if not simply select “Connect…”: 

3.       Now you will be prompted with the connect dialog, and to connect to the DAC through SSMS you just need to prefix the ServerName\InstanceName with the string “admin:”, as follows: 



As shown above, the DAC is a relatively easy portion of SQL Server to analyze and connect to, but equally an essential instrument in the troubleshooting DBA’s toolbox.  If there are any comments, questions, or issues please feel free to leave a comment or email me at sqlsalt@gmail.com.

Code
                As always, this code/information can be found on my SQLSalt GitHub repository:

Thursday, April 5, 2012

Database Accessibility


                There are going to be many times throughout the administration of databases that a DBA will need to control how the database is accessed.  Sometimes we need to shut it down altogether, or leave it open just enough to troubleshoot.  There are a few variations that can give you the end result for database accessibility.

Database State
                The database state controls whether or not the database is accessible, and the level of accessibility.  There are three options for database state
·         ONLINE – the database is accessible for user connections
·         OFFLINE – the database is shut down and can’t receive user connections
·         EMERGENCY – the database is READ_ONLY, and logging is disabled

Here are some examples in action:

Setting a database OFFLINE: (two variations for rolling back open transactions.  See code comments for descriptions of each)
use master
go

-- take database immediately offline
-- rollback all transactions right away
alter database YourDatabase
set offline
with rollback immediate
go

-- take database offline when all open trans are
--  committed or rolled back
-- rollback all transactions after 30 seconds
alter database YourDatabase
set offline
with rollback after 30
go

Setting a database ONLINE:
use master
go

alter database YourDatabase
set online
go

Setting a database in EMERGENCY:
alter database YourDatabase
set emergency
with rollback immediate
go


Database Updateability
                There are two options to control whether or not write operations can be executed on a database:
          ·         READ_ONLY – data can be read but not written
          ·         READ_WRITE – data can be read and written to (given appropriate privileges)

Put database in READ_ONLY:
alter database YourDatabase
set read_only
with rollback immediate
go

Put database in READ_WRITE:
alter database YourDatabase
set read_write
with rollback immediate
go


Database User Access
                There are no doubt times when you’ll absolutely need to change how many users and if only certain roles can connect to the database.  There are three options that control database user access:
          ·         MULTI_USER – all users are allowed to connect to the database (given appropriate privileges)
          ·         SINGLE_USER – only one user (with privileges to connect) can be connected at any given time
          ·         RESTRICTED_USER – only members of the following roles can connect to the database: db_owner (database role), sysadmin (server role), and dbcreator (server role)

Set database for MULTI_USER access:
alter database AdventureWorks2012
set multi_user
go

Set database for SINGLE_USER access:
alter database AdventureWorks2012
set single_user
with rollback immediate
go

Set database for RESTRICTED_USER access:
alter database AdventureWorks2012
set restricted_user
with rollback immediate
go

                In summary, this post has showed you numerous ways to control the way users, and who (and how many) is able to access the database.  Any comments, questions, or problems you can leave a comment below or email me at sqlsalt@gmail.com.

                As for the code to this post, it is kept in my SQLSalt GitHub repository, as follows: