I have a database table that hold customer details and when they call in we use their telephone to lookup their details, but this usually takes about 2-3 seconds but recently it has been taking 5 seconds with no extra data. If I query the table using the home_phone_no = '441903354676' this returns in sub second. But if it is queried using home_phone_no = '441903354676' or business_phone_no = '441903354676' then this takes 5 seconds.
Now there are some 1.4m customer records. But if anyone can see anything obvious or provide some helpful suggestions this be most welcome.
This is the structure of the table
CREATE TABLE [dbo].[CCDB_ICR]
(
[bill_account_no] [varchar](10) NOT NULL,
[reference_id] [varchar](11) NULL,
[bill_account_status] [varchar](2) NULL,
[customer_type] [varchar](1) NULL,
[customer_name] [varchar](56) NULL,
[property_address] [varchar](69) NULL,
[outer_post_code] [varchar](4) NULL,
[inner_post_code] [varchar](3) NULL,
[customer_move_in_date] [datetime2](7) NULL,
[customer_move_out_date] [datetime2](7) NULL,
[debt_flag] [varchar](1) NULL,
[payment_category_flag] [varchar](2) NULL,
[payment_plan_flag] [varchar](1) NULL,
[key_customer_flag] [varchar](1) NULL,
[home_phone_no] [varchar](12) NULL,
[business_phone_no] [varchar](12) NULL,
[contact_type] [varchar](4) NULL,
[contact_code] [varchar](1) NULL,
[contact_method] [varchar](1) NULL,
[contact_date] [date] NULL,
[contact_time] [varchar](5) NULL,
[contact_status] [varchar](1) NULL,
[contact_unit_resp] [varchar](4) NULL,
[outstanding_balance] [decimal](9, 2) NULL,
[date_time_inserted] [datetime] NOT NULL,
[date_time_updated] [datetime] NULL,
CONSTRAINT [PK_CCDB_ICR]
PRIMARY KEY CLUSTERED([bill_account_no] ASC)
WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF,
IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON,
ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
This is the structure of the index:
CREATE NONCLUSTERED INDEX [NC_Business&Home_Phone$CCDB_ICR]
ON [dbo].[CCDB_ICR] ([home_phone_no] ASC, [business_phone_no] ASC)
INCLUDE ([bill_account_no])
Here is the stored procedure
ALTER procedure [dbo].[GET_ACCOUNT_BY_PHONE_NUMBER]
(
@CLI varchar(12),
@RETURN_VALUE int out,
@NUMBER_OF_ACCOUNTS int out,
@ACCOUNT_NUMBER varchar(10) out
)
as
Begin Try
declare @ret int
Select @ret=COUNT(*) from CCDB_ICR where home_phone_no=@CLI or business_phone_no=@CLI
if @ret=0
select @RETURN_VALUE=0,
@NUMBER_OF_ACCOUNTS=0,
@ACCOUNT_NUMBER=null
else if @ret=1
select @RETURN_VALUE=0,
@NUMBER_OF_ACCOUNTS=1,
@ACCOUNT_NUMBER=(Select bill_account_no from CCDB_ICR where home_phone_no=@CLI or business_phone_no=@CLI)
else if @ret>1
select @RETURN_VALUE=0,
@NUMBER_OF_ACCOUNTS=@ret ,
@ACCOUNT_NUMBER=null
end Try
Begin Catch
select @RETURN_VALUE=-1,
@NUMBER_OF_ACCOUNTS=null ,
@ACCOUNT_NUMBER=null
End Catch
Therefore when we query for data, first the non-clustered index is searched to get the address of the data and then the lookup is performed on the clustered index to get the data. Hence this makes the non-clustered index usually slower than the clustered index. There can be multiple non-clustered indexes in a table.
Data is stored on the hard drive. Reading data from disk is costly in terms of time. To increase performance, any data read from disk to memory is left in memory “for a while” Done this way, subsequent queries that access the same data will find it in memory, and not have to read from disk again.
Disadvantages of Non-clustered index A non-clustered index helps you to stores data in a logical order but does not allow to sort data rows physically. Lookup process on non-clustered index becomes costly.
If you want to select only the index value that is used to create and index, non-clustered indexes are faster.
The index can not be used for home_phone_no = '441903354676' or business_phone_no = '441903354676'
, but it could be used for home_phone_no = '441903354676' and business_phone_no = '441903354676'
.
It will not be able to use the second column of the index key without a condition for the first column of the index key.
To use the or
, you would use separate supporting indexes, e.g.:
create nonclustered index [NC_Business&Home_Phone$CCDB_ICR]
on [dbo].[CCDB_ICR] ([home_phone_no] asc);
create nonclustered index [NC_Business&Business_Phone$CCDB_ICR]
on [dbo].[CCDB_ICR] ([business_phone_no] asc);
Also, you do not need to include [bill_account_no]
as an included column on your indexes as it is the clustering key, and as such is already implicitly included.
You can simplify your entire procedure down to:
alter procedure [dbo].[get_account_by_phone_number] (
@cli varchar(12)
, @return_value int out
, @number_of_accounts int out
, @account_number varchar(10) out
) as
begin;
set nocount, xact_abort on;
set @return_value = 0;
set @number_of_accounts = 0;
select
@number_of_accounts = count(*)
, @account_number = case when count(*)=1 then max(bill_account_no) else null end
from ccdb_icr
where home_phone_no=@cli
or business_phone_no=@cli;
end;
go
If you are still experiencing performance issues after creating the appropriate indexes and updating the procedure, then you should try to identify if parameter sniffing is causing the problem.
I would start with this article by Paul White which covers the following:
SQL Server provides a range of query hints and other options to tune the behaviour of parameter sniffing:
- The OPTIMIZE FOR (@parameter = value) query hint builds a reusable plan based on a specific value
- OPTIMIZE FOR (@parameter UNKNOWN) uses average distribution statistics for a particular parameter
- OPTIMIZE FOR UNKNOWN uses average distribution for all parameters (same effect as trace flag 4136)
- The WITH RECOMPILE stored procedure option compiles a fresh procedure plan for every execution
- The OPTION (RECOMPILE) query hint compiles a fresh plan for an individual statement
~ Parameter Sniffing, Embedding, and the RECOMPILE Options - Paul White
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With