1

This is just driving me crazy. I have a small SQL Server 2012 table with the following columns:

id  int (this is an IDENTITY column)
DateNewsletter  smalldatetime
SubjectNewsletter   varchar(100)
ContentHeader   varchar(MAX)
ContentNewsletter   varchar(MAX)
ContentFooter   varchar(MAX)
NewsletterSent  bit
DateSent    smalldatetime
ApprovalPending bit
PriorityHigh    bit

There are 583 rows in the table.

Here's the problem:

  1. I can SELECT * with no problem
  2. I can INSERT INTO with no problem
  3. However, when I run the following query, everything blows up:

    UPDATE tblElinesNewsletter
    SET NewsletterSent = 1,
        DateSent = GETDATE(),
        ApprovalPending = 0
    WHERE (NewsletterSent = 0)
    

Whenever I run the above code, I get a timeout error. The timeout error is the same if I run it from ASP.NET page or from SQL Server Management Studio.

Also, if I right-click from SSMS and choose 'Edit Top 200 Rows...', it will display the grid and let me edit. But as soon as I click away and it's performing the update, it's blowing up.

I've tried the following already:

  1. Rebuild the index
  2. Create a brand new table with same schema and transfer data over there
  3. Restart the server
  4. Go to home depot and purchase large can of spackle to fix holes in office wall (from banging my head against)

This is not a large table. It simply stores HTML for an email newsletter.

It didn't always do this, and only started about a few months ago. It worked fine before that.

Now, all of the above said, here's the kicker: it doesn't do this all the time. Sometimes it works. What noticed is that if there's a longer newsletter in the table, it seems to not work vs. if the newsletter is very short. These are simple newsletters and not too fancy, here's one right here: http://unitedafa.org/news/elines/view/?id=104169

This is pulling from the database/table that I discuss above.

This SQL Server installation is on a Windows Server 2012 box with 6 GB RAM and only runs a few websites that don't get that much traffic.

One thing I'm noticing as I'm trying to debug this, is that when I execute the SQL Server stored procedure from the ASP.NET web page, when it fails, this seems to freeze up the SQL Server and I can't run the same stored procedure in SSMS. However, once I restart the server, I can then open SSMS and execute the stored procedure. Again, though if I execute the stored procedure from the web page, it 'freezes' up the server again and I can't run any update query against that table.

Just for reference, here's the actual stored procedure that's being called from the page:

SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
-- ======================================================
-- Author:      Christopher Lee
-- Create date: 16-Aug-2012
-- Modify date: 16-Aug-2012
-- Description: Mark all records as Sent.
-- ======================================================

ALTER PROCEDURE [dbo].[sProc_Elines_Send_MarkComplete]
AS
   BEGIN TRANSACTION

    UPDATE tblElinesNewsletter
    SET NewsletterSent = 1,
        DateSent = GETDATE(),
        ApprovalPending = 0
    WHERE (NewsletterSent = 0)

    COMMIT

Also, here's the CREATE TABLE script:

SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
SET ANSI_PADDING ON
GO

CREATE TABLE [dbo].[tblElinesNewsletter]
(
    [id] [int] IDENTITY(100543,1) NOT NULL,
    [DateNewsletter] [smalldatetime] NOT NULL,
    [SubjectNewsletter] [varchar](100) NOT NULL,
    [ContentHeader] [varchar](max) NOT NULL,
    [ContentNewsletter] [varchar](max) NOT NULL,
    [ContentFooter] [varchar](max) NOT NULL,
    [NewsletterSent] [bit] NOT NULL,
    [DateSent] [smalldatetime] NULL,
    [ApprovalPending] [bit] NOT NULL,
    [PriorityHigh] [bit] NOT NULL,

     CONSTRAINT [PK_tblElinesNewsletter2] 
         PRIMARY KEY CLUSTERED ([id] ASC)
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO

SET ANSI_PADDING OFF
GO

ALTER TABLE [dbo].[tblElinesNewsletter] 
    ADD CONSTRAINT [DF_tblElinesNewsletter2_PriorityHigh] DEFAULT ((0)) FOR [PriorityHigh]
GO

Any idea what on earth I'm doing wrong? BTW, I'm not a DBA and more familiar with ASP.NET web code, etc., more than SQL statements.

Any help or advice would be appreciated!

-- Chris

PS, here's some additional info:

SQL Server Query Execution Plan on the Stored Proc:

[SQL Server Execution Plan Screenshot][1]

Here's some additional info:

  1. There's enough hard drive space.
  2. There's no other concurrent processes.

The stored proc is actually executed in a sequence with two other before it. Here's the code from the executing page:

    Protected Sub btnSubmit_Click(sender As Object, e As EventArgs) Handles btnSubmit.Click

    Call WriteNewsletterFile()

    Call SendNewsletter()

    Call UpdateRecord()

End Sub

So the WriteNewsletterFile() and the SendNewsletter() subroutines work fine. It blows up on the UpdateRecord subroutine. Here's the code for each of the routines:

   Sub WriteNewsletterFile()

    Dim NewsID As String = ""

    Dim Conn As SqlConnection
    Dim Cmd As SqlCommand
    Dim Rdr As SqlDataReader
    Conn = New SqlConnection(ConfigurationManager.ConnectionStrings("UnitedAFAConnectionStringNSS").ConnectionString)
    Cmd = New SqlCommand()
    Cmd.CommandText = "sProc_Elines_Send_GetContentPending"
    Cmd.CommandType = CommandType.StoredProcedure
    Cmd.Connection = Conn
    Cmd.Connection.Open()
    Rdr = Cmd.ExecuteReader(CommandBehavior.CloseConnection)

    If Rdr.HasRows Then

        While Rdr.Read

            NewsID = Rdr("id")
            NewsletterSubject = Rdr("SubjectNewsletter")
            NewsletterHeader = Rdr("ContentHeader")
            NewsletterContent = Rdr("ContentNewsletter")
            NewsletterFooter = Rdr("ContentFooter")

        End While

    End If

    Conn.Close()
    Conn.Dispose()
    Cmd.Dispose()

    ' Header
    NewsletterHeaderForEmail = NewsletterHeader.Replace("<p>", "<p style=""font-family: Arial, Helvetica, sans-serif; font-size: 12pt; line-height: 1.3em;margin-bottom:1em"">")
    NewsletterHeaderForEmail = NewsletterHeaderForEmail.Replace("<li>", "<li style=""font-family: Arial, Helvetica, sans-serif; font-size: 12pt; line-height: 1.3em;margin-bottom:1em"">")
    NewsletterHeaderForEmail = NewsletterHeaderForEmail.Replace("[NewsID]", NewsID)

    ' Footer
    NewsletterFooterForEmail = NewsletterFooter.Replace("<p>", "<p style=""font-family: Arial, Helvetica, sans-serif; font-size: 12pt; line-height: 1.3em;margin-bottom:1em"">")
    NewsletterFooterForEmail = NewsletterFooterForEmail.Replace("<li>", "<li style=""font-family: Arial, Helvetica, sans-serif; font-size: 12pt; line-height: 1.3em;margin-bottom:1em"">")

    ' Content
    NewsletterContentForEmail = NewsletterContent.Replace("<p>", "<p style=""font-family: Arial, Helvetica, sans-serif; font-size: 12pt; line-height: 1.3em;margin-bottom:1em"">")
    NewsletterContentForEmail = NewsletterContentForEmail.Replace("<li>", "<li style=""font-family: Arial, Helvetica, sans-serif; font-size: 12pt; line-height: 1.3em;margin-bottom:1em"">")
    NewsletterContentForEmail = "<table border=""0"" cellpadding=""0"" cellspacing=""0""><tr><td style=""font-family: Arial, Helvetica, sans-serif; font-size: 12pt"">" & NewsletterContentForEmail & "</td></tr></table>"

    NewsletterFinalReadyForSending = NewsletterHeaderForEmail & NewsletterContentForEmail & NewsletterFooterForEmail

    Dim filePath2 As String = "C:\Programs\SendElines.bat"
    Dim w2 As StreamWriter
    w2 = File.CreateText(filePath2)
    w2.WriteLine("START """" ""C:\Program Files (x86)\Gammadyne Mailer\gm.exe"" /s /n /subject""" & NewsletterSubject & """ /html""C:\Programs\elines.html"" ""C:\Users\Public\Documents\Newsletters\Elines\Sending Template - Elines.mmp""")
    w2.Flush()
    w2.Close()


    Dim filePath As String = "C:\Programs\elines.html"
    Dim w As StreamWriter
    w = File.CreateText(filePath)
    w.WriteLine(NewsletterFinalReadyForSending)
    w.Flush()
    w.Close()

End Sub

Sub SendNewsletter()

        Dim Conn As New SqlConnection(ConfigurationManager.ConnectionStrings("UnitedAFAConnectionStringAfaNewsletters").ConnectionString)
        Dim MySQL As String = "sProc_SendElines"
        Dim Cmd As New Data.SqlClient.SqlCommand(MySQL, Conn)
        Cmd.CommandType = CommandType.StoredProcedure
        Conn.Open()
        Cmd.ExecuteNonQuery()
        Conn.Close()
        Conn.Dispose()

End Sub

Sub UpdateRecord()

    Dim Conn As New SqlConnection(ConfigurationManager.ConnectionStrings("UnitedAFAConnectionStringNSS").ConnectionString)
    Dim MySQL As String = "sProc_Elines_Send_MarkComplete"
    Dim Cmd As New Data.SqlClient.SqlCommand(MySQL, Conn)
    Cmd.CommandType = CommandType.StoredProcedure

    Conn.Open()
    Cmd.ExecuteNonQuery()
    Conn.Close()
    Conn.Dispose()

End Sub

And here's the code for the SendElines Subroutine:

        USE [afanewsletters]
    GO
    /****** Object:  StoredProcedure [dbo].[sProc_SendElines]    Script Date: 9/5/2017 2:51:08 PM ******/
    SET ANSI_NULLS ON
    GO
    SET QUOTED_IDENTIFIER ON
    GO
    -- ======================================================
    -- Author:      Christopher Lee
    -- Create date: 21-Aug-2012
    -- Modify date: 21-Aug-2012
    -- Description: Runs the Elines sending BAT file.
    -- ======================================================

    ALTER PROCEDURE [dbo].[sProc_SendElines]

    AS

    EXEC xp_logevent 67845, 'Send Elines', informational

I should point out that the SendElines() stored proc is on a different database (but on same server). Again though, to be clear, the first two subroutines work fine. It's just the sProc_Elines_Send_MarkComplete that blows up.

Here's the code for sProc_Elines_Send_GetContentPending:

    USE [Newsletters]
GO
/****** Object:  StoredProcedure [dbo].[sProc_Elines_Send_GetContentPending]    Script Date: 9/6/2017 6:57:06 AM ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
-- ======================================================
-- Author:      Christopher Lee
-- Create date: 16-Aug-2012
-- Modify date: 16-Aug-2012
-- Description: Get content for Elines for approval.
-- ======================================================

ALTER PROCEDURE [dbo].[sProc_Elines_Send_GetContentPending]

AS


BEGIN TRY
BEGIN TRANSACTION

SELECT * FROM tblElinesNewsletter
WHERE     (NewsletterSent = 0)

COMMIT
END TRY
BEGIN CATCH
IF @@TRANCOUNT > 0
ROLLBACK
  RAISERROR ('Problem with sProc_Elines_Send_GetContentPending, please contact the MEC Webmaster at webmaster@unitedafa.org.', 16, 1)
END CATCH

Here's the results of the DBCC OPENTRAN I ran. The first two stored procs (sProc_Elines_Send_GetContentPending and sProc_SendElines) executed successfully. However, when sProc_Elines_Send_MarkComplete ran and hung, I ran the DBCC OPENTRAN which contained the message: "No active open transactions." Screenshot enclosed: Here's the DBCC OPENTRAN screenshot

Chris Lee
  • 15
  • 7
  • 1
    Are there any triggers on this table? Also, can you post the exact error message that you are getting? – Sparrow Sep 05 '17 at 19:11
  • Your table does not have an `IDENTITY` column. maybe that'll help? (PS I'm not an DBA either) – VDWWD Sep 05 '17 at 19:11
  • 2
    A timeout on a small table suggests blocking. – David Browne - Microsoft Sep 05 '17 at 19:14
  • You may not have permission for an UPDATE or the table is being blocked by another process while you do this. – Isaiah3015 Sep 05 '17 at 19:14
  • @Sparrow No triggers. I'll post error message here, yes, good idea. – Chris Lee Sep 05 '17 at 19:20
  • @DavidBrowne-Microsoft, maybe so, but I have full admin access to both the server, as well as the database, and don't think I blocked myself and nobody has accessed it since I set it up. – Chris Lee Sep 05 '17 at 19:21
  • @ChrisLee Could you post the CREATE TABLE script, from SSMS? Right-click the table, then select scripts as. – SchmitzIT Sep 05 '17 at 19:23
  • Sure, here it is: – Chris Lee Sep 05 '17 at 19:31
  • I still think there is another query that is triggered by the update and blocks the update. To make sure, in SMS you can go to tools and run the SQL server Profiler and try the update again. Ptofiler will track and display every statement that is running on the DB and you can see if something else is happening and causing any deadlock – Sparrow Sep 05 '17 at 19:36
  • Check permissions of the ASP user that is connecting to SQL. Does that user have UPDATE permissions? – Shawn Sep 05 '17 at 19:36
  • @Shawn Yep, and in fact sometimes it works -- mostly when the newsletter doesn't contain a lot of content. – Chris Lee Sep 05 '17 at 19:37
  • @Sparrow I can check on that. Anything I need to set for SQL profile when I run the test? – Chris Lee Sep 05 '17 at 19:38
  • try the defaults first. – Sparrow Sep 05 '17 at 19:38
  • 1
    Will it complete if you comment out the TRANSACTION around the UPDATE? Do you have enough free space on the DB and log drives that this is running on? Is the log close to growing before you start this process. Is one of your other sites writing to the same drive and causing contention with IO or other resources? – Shawn Sep 05 '17 at 19:53
  • Hi @Shawn, commenting out TRANSACTION doesn't have any affect. Space is ok. See the update to my code above that shows the other processes that run before it. Again, this is just nuts. – Chris Lee Sep 05 '17 at 20:21
  • Running that update clause without an index similar to the where clause will usually do a table scan which it is claimed [locks all the rows in no particular order](https://dba.stackexchange.com/q/101465/9415). Consider adding an index on NewsletterSent. Just curious what percentage of rows have NewsletterSent=0? – crokusek Sep 05 '17 at 21:14
  • Only one newsletter is ever sent at a time. And you can't compose a new one until the first one is sent. Thus it's only ever 1 row that has NewsletterSent = 0. – Chris Lee Sep 05 '17 at 21:23
  • So then adding an index on NewsletterSent should reduce the locks required to cover the update from ~500 rows to 1 row which can be verified by comparing the Execution Plan before/after as per the answer comments by SchmitzIT. It should show an "index seek" and output 1 row. – crokusek Sep 05 '17 at 21:28
  • The code for the proc in your update, is logging the event the only thing the proc does, or is this part of the full code? Also, can you post the code for sProc_Elines_Send_GetContentPending? We ruled out the obvious things, as lack of space, or a terrible execution path. Is there perhaps another transaction starting in GetContentPending? Before you execute the MarkComplete, can you execute DBCC OPENTRAN in SSMS and share the output? – SchmitzIT Sep 05 '17 at 22:24
  • @SchmitzIT, yep I just posted the code for sProc_Elines_Send_GetContentPending. Basicaly it's only a SELECT * statement. Posting the code for DBCC OPENTRAN now... – Chris Lee Sep 06 '17 at 12:03

3 Answers3

1

In this code

Rdr = Cmd.ExecuteReader(CommandBehavior.CloseConnection)
If Rdr.HasRows Then
    While Rdr.Read
        ....
    End While
End If

Conn.Close()
Conn.Dispose()

you do not close and dispose the reader. I don't know the internals of SqlConnection and SqlDataReader, but an open reader may cause the conn.Close() the have no effect.

If the reader is open, this means that the records and tables read by sProc_Elines_Send_GetContentPending are probably locked, which means that the locks are not released, even though the records are not accessed any more.

You should also have a look at the Using statement (see this question) to handle connections, commands, and readers.

Further, you have a While loop, but only process a single record. And if the Reader does not return a record, you end up with a lot of uninitialized variables. But that's not related to your locking problem.

devio
  • 36,858
  • 7
  • 80
  • 143
  • Hi @devio: From what I can see in the code, it appears that the DB connection is being closed. Here's a snippet from the code above: If Rdr.HasRows Then While Rdr.Read NewsID = Rdr("id") NewsletterSubject = Rdr("SubjectNewsletter") NewsletterHeader = Rdr("ContentHeader") NewsletterContent = Rdr("ContentNewsletter") NewsletterFooter = Rdr("ContentFooter") End While End If Conn.Close() Conn.Dispose() Cmd.Dispose() – Chris Lee Sep 05 '17 at 21:06
  • "from what we all can see in the code", the code runs fine. – devio Sep 05 '17 at 21:22
0

The query looks fine, and if you have less than 1k rows, I don't know why it would end up timing out.

One thing you could try is to add a variable at the top, and add CAST(GETDATE() AS smalldatetime) into that. Then use the variable in your update.

DECLARE @myDate smalldatetime AS CAST(GETDATE() AS smalldatetime)

UPDATE tblElinesNewsletter
SET 
NewsletterSent = 1,
DateSent= myDate ,
ApprovalPending = 0
WHERE NewsletterSent = 0

I'm not sure it'll do anything, but the idea is to store the value of the date in a constant, rather than have it evaluated in the WHERE clause.

SchmitzIT
  • 9,227
  • 9
  • 65
  • 92
  • Thanks SchmitzIT, I tried that and it blew up again. I also modified the UPDATE statement to just be UPDATE tblElinesNewsletter SET ApprovalPending = 0. Both failed. – Chris Lee Sep 05 '17 at 19:27
  • What happens when you pull up the estimated execution plan in SSMS? What's it display? The only thing I can think off is the nvarchar(max) columns being an issue, as those are considered blobs. But you're not touching those with the update statement. – SchmitzIT Sep 05 '17 at 19:34
  • Yeah, that's the crazy thing. Now I'm going to sound ignorant: where/how do I pull up an estimate execution plan? – Chris Lee Sep 05 '17 at 19:37
  • In Management Studio, there's an icon called "Display Estimated Execution Plan". It uses the statistics SQL Server has to try and determine the optimal path to get the data requested. It will display a graphical execution path that is helpful in figuring out what's going on under the hood. (Note, this is based on estimates, and the actual execution path can differ). – SchmitzIT Sep 05 '17 at 19:39
0

All:

First off, thank you for your help/assistance. This was really the first time I've used this board with such an extensive issue. I wish I could awared ALL responses, as they all helped to paint a clearer picture of what was going on.

I solved the issue in part by what Devio said about connections not being closed. Digging into the .NET page further, I found that there was a Session State database call that managed the security of the page. This was coming from an iframe tag and actually on a DIFFERENT page using the same connection string and database. Apparently this connection string did NOT close and remained open with the same database and same overall table. Then the sProc_Elines_Send_GetContentPending proc tried to run -- which it appeared to do, as did the sProc_SendElines proc. However when the sProc_Elines_Send_MarkComplete proc tried to run, it failed as I mentioned eariler.

When I rewrote the database connection code to add to following statements:

        Cmd.ExecuteNonQuery()
        Conn.Close()
        Conn.Dispose()

It then closed the security Session State database connection, which then allowed all three of the other stored procedures to run.

So again, this resolved the issue and the genesis of figuring it out for me was from Devio's close connection suggestion.

I hope I did this question closure with the correct Stack Overflow protocol and please let me know if I missed anything. Thanks again everyone. You are the best!

-- Chris

Chris Lee
  • 15
  • 7