Error handling Defensive Programming

transaction and concurrency control and common concurrent transaction execution problems and concurrent transaction processing in database
Dr.NaveenBansal Profile Pic
Dr.NaveenBansal,India,Teacher
Published Date:25-10-2017
Your Website URL(Optional)
Comment
Chapter 8: Defensive Error Handling The ability to handle errors is essential in any programming language and, naturally, we have to implement safe error handling in our T-SQL if we want to build solid SQL Server code. SQL Server 2005 (and later) superseded the old-style E err rro or handling, with r the TRY…CATCH blocks that are more familiar to Java and C programmers. While use of TRY…CATCH certainly is the best way to handle errors in T-SQL, it is not without difficulties. Error handling in T-SQL can be very complex, and its behavior can sometimes seem erratic and inconsistent. Furthermore, error handling in Transact SQL lacks many features that developers who use languages such as Java and C take for granted. For example, in SQL Server 2005 and 2008, we cannot even re-throw an error without changing its error code. This complicates handling errors, because we have to write separate conditions for detecting exceptions caught for the first time, and for detecting re-thrown exceptions. This chapter will demonstrate simple SQL Server error handling, using XACT_ABORT and transactions; it will describe the most common problems with TRY… err CAT or CH handling, and advocate that the defensive programmer, where possible, should implement only simple error checking and handling in SQL Server, with client-side error handling used to enforce what is done on the server. Prepare for Unanticipated Failure Any statement can, and at some point inevitably will, fail. This may seem to be a statement of the obvious, but too many programmers seem to assume that, once their code "works," then the data modifications and queries that it contains will always succeed. In fact, data modifications can and do fail unexpectedly. For example, the data may not validate against a constraint or a trigger, or the command may become a deadlock victim. Even if the table does not have any constraints or triggers at the time the code is 259Chapter 8: Defensive Error Handling developed, they may be added later. It is wise to assume that our modifications will not always succeed. Many queries, too, can fail. Just as a modification can become a deadlock victim, so can a SELECT (unless that SELEC is running under either of the two snapshot iso T lation levels). If a SELECT statement utilizes a user-defined function, then errors may occur in that function that will cause the query to fail. Other common causes of failure are queries that attempt to use a temporary table that does not exist, or contain subqueries that return more than one value. Listing 8-1 demonstrates a very simple case of a SELE statemen CT t that may succeed or fail, depending on locale settings. CREATE VIEW dbo.NextNewYearEve AS SELECT DATEADD (YEAR, DATEDIFF(year, '12/31/2000', CURRENT_TIMESTAMP), '12/31/2000' ) AS NextNewYearEve ; GO SET LANGUAGE us_english ; SELECT NextNewYearEve FROM dbo.NextNewYearEve ; Changed language setting to us_english. NextNewYearEve - 2009-12-31 00:00:00.000 SET LANGUAGE Norwegian ; SELECT NextNewYearEve FROM dbo.NextNewYearEve ; GO 260Chapter 8: Defensive Error Handling Changed language setting to Norsk. NextNewYearEve - Msg 241, Level 16, State 1, Line 2 Conversion failed when converting date and/or time from character string. DROP VIEW dbo.NextNewYearEve ; Listing 8-1: Language settings can cause certain date queries to fail. The main point is clear: when we develop T-SQL code, we cannot assume that our queries or data modifications will always succeed, and we need to be prepared for such failures and handle them gracefully. When an unexpected error occurs during data modification, it is essential that execution of the statement is terminated, the database is returned to the state it was in before the statement started, and a message is sent to the calling client, giving some details of the error that occurred and the action taken as a result. Likewise, if a SELEC fails that is part of a l T onger transaction that has already modified data, then these modifications must be undone as well. Using Transactions for Data Modic fi ations In many cases, during data modifications, we cannot take our database from one consistent state to another in a single statement. If a data modification requires more than one statement to effect the required change, then explicit transactions should be used to ensure that these statements succeed or fail as a unit, and that our error handling can handle them as a unit. For example, suppose that we need to log, in one table, all the modifications made to another table. Listing 8-2 shows the code to create the table to be modified ( ) and Codes the table in which the modifications will be logged (CodeDescriptionsChange ).Log 261Chapter 8: Defensive Error Handling IF EXISTS ( SELECT FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = 'Codes' AND TABLE_SCHEMA = 'dbo' ) BEGIN; we used a Codes table in a previous chapter let us make sure that is does not exist any more DROP TABLE dbo.Codes ; END ; GO CREATE TABLE dbo.Codes ( Code VARCHAR(10) NOT NULL , Description VARCHAR(40) NULL , CONSTRAINT PK_Codes PRIMARY KEY CLUSTERED ( Code ) ) ; GO we did not use this table name before in this book, so there is no need to check if it already exists CREATE TABLE dbo.CodeDescriptionsChangeLog ( Code VARCHAR(10) NOT NULL , ChangeDate DATETIME NOT NULL , OldDescription VARCHAR(40) NULL , NewDescription VARCHAR(40) NULL , CONSTRAINT PK_CodeDescriptionsChangeLog PRIMARY KEY ( Code, ChangeDate ) ) ; Listing 8-2: The Codes and CodeDescriptionsChangeLog tables. Note that the log table does not have a FORE KEIGYN constraint referring to the Codes table, because the log records need to be kept even if we delete the corresponding rows in Codes. The procedure shown in Listing 8-3 modifies the Codes table, and logs the change in the CodeDescriptionsChangeLog table. 262Chapter 8: Defensive Error Handling CREATE PROCEDURE dbo.ChangeCodeDescription Code VARCHAR(10) , Description VARCHAR(40) AS BEGIN ; INSERT INTO dbo.CodeDescriptionsChangeLog ( Code , ChangeDate , OldDescription , NewDescription ) SELECT Code , CURRENT_TIMESTAMP , Description , Description FROM dbo.Codes WHERE Code = Code ; UPDATE dbo.Codes SET Description = Description WHERE Code = Code ; END ; Listing 8-3: The ChangeCodeDescription stored procedure. Listing 8-4 runs a simple smoke test on the new procedure. INSERT INTO dbo.Codes ( Code, Description ) VALUES ( 'IL', 'Ill.' ) ; GO EXEC dbo.ChangeCodeDescription Code = 'IL', Description = 'Illinois' ; GO 263Chapter 8: Defensive Error Handling SELECT Code , OldDescription + ', ' + NewDescription FROM dbo.CodeDescriptionsChangeLog ; Code - IL Ill., Illinois Listing 8-4: A smoke test on the ChangeCodeDescription stored procedure. It looks like the procedure works, right? Note, however, that this stored procedure does not attempt to determine whether or not either of the two modifications failed, and it does not handle possible errors. Of course, one might argue that this stored procedure could be a component of a perfectly valid system, if it is invoked by an application that does all the error handling. However, that does not make it, as is, a valid component. There's a huge risk that a developer who builds another application may find this procedure and decide to call it, unaware of the required error handling in the calling procedure. It may seem that nothing could possibly go wrong during these two trivial modifi - ca tions, but we still cannot assume that both modifications will always succeed. In fact, even in this trivial example, we can devise a case where one modification can fail: if two modifications occur simultaneously, we may get a primary key violation on the CodeDescriptionsChangeLog table. Rather than reproduce that case here, we can prove the same point simply by creating a CHEC constrain K t that prohibits inserts and updates against the Code- DescriptionsChangeLog table, and demonstrating what happens when one of our modifications fails and we do nothing to detect and handle it. SET XACT_ABORT OFF ; if XACT_ABORT OFF were set to ON , the code below would behave differently. We shall discuss it later in this chapter. DELETE FROM dbo.CodeDescriptionsChangeLog ; 264Chapter 8: Defensive Error Handling BEGIN TRANSACTION ; GO This constraint temporarily prevents all inserts and updates against the log table. When the transaction is rolled back, the constraint will be gone. ALTER TABLE dbo.CodeDescriptionsChangeLog ADD CONSTRAINT CodeDescriptionsChangeLog_Immutable CHECK(10) ; GO EXEC dbo.ChangeCodeDescription Code = 'IL', Description = 'other value' ; GO dbo.Codes table has been updated SELECT Code , Description FROM dbo.Codes ; dbo.CodeDescriptionsChangeLog has not been updated SELECT Code , OldDescription + ', ' + NewDescription FROM dbo.CodeDescriptionsChangeLog ; GO ROLLBACK ; Msg 547, Level 16, State 0, Procedure ChangeCodeDescription, Line 6 The INSERT statement conflicted with the CHECK constraint "CodeDescriptionsChangeLog_Immutable". The conflict occurred in database "Test", table "dbo.CodeDescriptionsChangeLog". The statement has been terminated. (1 row(s) affected) 265Chapter 8: Defensive Error Handling Code Description IL other value (1 row(s) affected) Code (0 row(s) affected) Listing 8-5: An INSERT into CodeDescriptionsChangeLog fails, but the UPDATE of Codes succeeds, and we end up with an UPDATE that has not been logged. In order to avoid this situation, we need to begin a transaction, attempt to do both modifications, determine whether or not both completed successfully, and commit the transaction only if both modifications succeeded. If either modification failed, we need to roll back the transaction, as part of our error handling. T-SQL allows several ways to accomplish that. Let's begin with the simplest approach: using transactions along with the XACT_ABORT setting. Using Transactions and XACT_ABORT to Handle Errors In many cases, we do not need sophisticated error handling. Quite frequently, all we need to do in case of an error, is roll back all the changes and throw an exception, so that the client knows that there is a problem and will handle it. In such situations, a perfectly reasonable approach is to make use of the XACT_ABO setting. RT By default, in SQL Server this setting is , w OFhi F ch means that in some circumstances SQL Server can continue processing when a T-SQL statement causes a run-time error. In other words, for less severe errors, it may be possible to roll back only the statement that caused the error, and to continue processing other statements in the transaction. 266Chapter 8: Defensive Error Handling If XACT_ABORT is turned on, SQL Server stops processing as soon as a T-SQL run-time error occurs, and the entire transaction is rolled back. When handling unexpected, unanticipated errors, there is often little choice but to cease execution and roll back to a point where there system is in a "known state." Otherwise, you risk seeing partially completed transactions persisted to your database, and so compromising data integrity. In dealing with such cases, it makes sense to have XACT_AB turned ORT ON . Data modifications via OLE DB Note that, in some cases, XACT_ABORT is already set to ON by default. For example, OLE DB will do that for you. However, it is usually preferable to explicitly set it, because we do not know in which context our code will be used later. Listing 8-6 illustrates a basic error-handling approach, whereby our modifications take place within an explicit transaction, having set XACT_A to BOORNT . The PRINT commands in the procedure are for demonstration purposes only; we would not need them in production code. ALTER PROCEDURE dbo.ChangeCodeDescription Code VARCHAR(10) , Description VARCHAR(40) AS BEGIN ; SET XACT_ABORT ON ; BEGIN TRANSACTION ; INSERT INTO dbo.CodeDescriptionsChangeLog ( Code , ChangeDate , OldDescription , NewDescription ) SELECT Code , current_timestamp , Description , Description FROM dbo.Codes WHERE Code = Code ; 267Chapter 8: Defensive Error Handling PRINT 'First modifications succeeded' ; UPDATE dbo.Codes SET Description = Description WHERE Code = Code ; the following commands execute only if both modifications succeeded PRINT 'Both modifications succeeded, committing the transaction' ; COMMIT ; END ; Listing 8-6: Using the XACT_ABORT setting and an explicit transaction. Note that, although we want to roll back all the changes if an error occurs, we do not need to explicitly determine if there are any errors, and we do not need to explicitly invoke ROLLBAC in our code; w K hen XACT_ABOR is set to T ON , it all happens automatically. Listing 8-7 tests our altered stored procedure. SET NOCOUNT ON ; SET XACT_ABORT OFF ; DELETE FROM dbo.CodeDescriptionsChangeLog ; BEGIN TRANSACTION ; GO This constraint temporarily prevents all inserts and updates against the log table. When the transaction is rolled back, the constraint will be gone. ALTER TABLE dbo.CodeDescriptionsChangeLog ADD CONSTRAINT CodeDescriptionsChangeLog_Immutable CHECK(10) ; GO EXEC dbo.ChangeCodeDescription 268Chapter 8: Defensive Error Handling Code = 'IL', Description = 'other value' ; GO transaction is rolled back automatically SELECT TRANCOUNT AS TRANCOUNT after stored procedure call ; dbo.Codes table has not been updated SELECT Code , Description FROM dbo.Codes ; dbo.CodeDescriptionsChangeLog has not been updated SELECT Code , OldDescription + ', ' + NewDescription FROM dbo.CodeDescriptionsChangeLog ; Msg 547, Level 16, State 0, Procedure ChangeCodeDescription, Line 8 The INSERT statement conflicted with the CHECK constraint "CodeDescriptionsChangeLog_Immutable". The conflict occurred in database "test", table "dbo.CodeDescriptionsChangeLog". TRANCOUNT after stored procedure call - 0 Code Description IL Illinois Code Listing 8-7: Testing the altered stored procedure. As we have seen, the stored procedure worked perfectly well. Of course, this is just the first in a series of tests we should perform on our stored procedure. 269Chapter 8: Defensive Error Handling Complete testing would include: • making sure that, if both the modification of the Cod tab esle and the INSER in T to the CodeDescriptionsChangeLog table succeed, then the transaction commits and both changes persist • verifying that, if an UPDAT of the E Codes table fails, then the transaction rolls back. To reproduce a failure, we can use a similar technique; a CH constrain ECK t that makes sure all UPDATs against the E Code tab s le fail • invoking the stored procedure without an outstanding transaction, when TRANCOUNT is 0. In that case, we shall have to explicitly drop the CHECK constraint which we create in our test. I encourage you to tweak Listing 8-7 and try out these other tests. In many cases, this simple approach of setting XACT_ABO to RTON and using an explicit transaction for modifications gets the job done without much effort. We should use this simple and robust approach unless we really need more sophisticated functionality from our error handling. If we really want to do some more complex error handling on the server, using T-SQL, then we should use TRY…CATC bHlocks, which are available in SQL Server 2005 and upwards. Using TRY…CATCH blocks to Handle Errors To handle errors in T-SQL modules, in SQL Server 2005 and upwards, we can use TRY…CATCH blocks. If any command inside the TR bY lock raises an error, the execution of the TRY block terminates immediately, which is similar to the behavior under the XACT_ABORT setting. But, unlike with XACT_ABO , w RThere the whole batch terminates, only the execution of the code inside the bT lR ock terminates, and the Y CAT bClHock begins to execute. In cases where you are aware that a certain specific error could occur, your error- handling strategy can be different. You may attempt to add code to your b CA lT ock CH that corrects the error, or at least allows processing to continue. In these cases, it makes more sense to have XACT_ABOR set to T OFF , so that you can handle the errors, and inform the calling client of what happened, without rolling back the entire batch. 270Chapter 8: Defensive Error Handling As will become clear as we progress, my current philosophy is that all but the simplest error handling should ideally be implemented in a client-side language where the error handling is more robust and feature rich than it is in SQL Server TRY… .CATCH My goal here is, therefore, not to cover TRY…C in full detail, but to set out, with ATCH examples, some of the reasons why error handling in T-SQL can be complex and a little bewildering. I really want to encourage you to either fully understand all the ins and outs of T-SQL error handling, or to avoid using it at all, except in the simplest cases. Erland Sommarskog's website, http://www.sommarskog.se/, is an excellent source of information on error handling. The book entitled Expert SQL Server 2005 Development by Adam Machanic, Hugo Kornelis, and Lara Rubbelke is another great resource. Finally, note that I do not cover "old-style" error handling using at all in this ERROR chapter. Use of ERRO has some well R -known problems, such as the inability to handle errors raised by triggers, and the fact that sometimes SQL Server simply fails to set its value correctly. In general, my advice would be to upgrade from to ERRTORRY… CATCH or, even better, to client-side error handling for all but the simplest cases, as soon as possible. A TRY…CATCH example: retrying after deadlocks Sometimes, it may make sense to use TRY…CATC b Hlocks to retry the execution of a statement after a deadlock. One must exercise caution when doing so, as retrying an UPDATE statement in this manner may lead to lost updates, as we discuss in detail in Chapter 10, Surviving Concurrent Modifications. The defensive programmer must take all possible measures to ensure that the possibility of deadlocks is minimized but, in some cases, it may be deemed acceptable, in the short term at least, to automatically retry after a deadlock. In order to provide an example that you can run on your server, we'll alter our ChangeCodeDescription stored procedure, as shown in Listing 8-8, so that it is highly likely to be chosen as a deadlock victim, if it embraces in a deadlock with a competing session. Our goal here is not to demonstrate how to develop stored procedures that are unlikely to embrace in deadlocks, but to see how to use a TRY…CATCH block to retry after a deadlock. 271Chapter 8: Defensive Error Handling If processing switches to our CAT bClH ock, we will attempt to re-execute our transaction once more, in response to a deadlock; otherwise we will simply re-throw the error so that the calling client is notified and can respond. ALTER PROCEDURE dbo.ChangeCodeDescription Code VARCHAR(10) , Description VARCHAR(40) AS BEGIN ; DECLARE tryCount INT , OldDescription VARCHAR(40) ; SET DEADLOCK_PRIORITY LOW ; SET XACT_ABORT OFF ; SET tryCount = 1 ; WHILE tryCount 3 BEGIN BEGIN TRY BEGIN TRANSACTION ; SET OldDescription = ( SELECT Description FROM dbo.Codes WHERE Code = Code ) ; UPDATE dbo.Codes SET Description = Description WHERE Code = Code ; INSERT INTO dbo.CodeDescriptionsChangeLog ( Code , ChangeDate , OldDescription , NewDescription ) SELECT Code , CURRENT_TIMESTAMP , OldDescription , Description ; 272Chapter 8: Defensive Error Handling PRINT 'Modifications succeeded' ; COMMIT ; RETURN 0 ; END TRY BEGIN CATCH transaction is not rolled back automatically we need to roll back explicitly IF TRANCOUNT 0 BEGIN ; PRINT 'Rolling back' ; ROLLBACK ; END ; IF ERROR_NUMBER() 1205 BEGIN if this is not a deadlock, "re-throw" the error DECLARE ErrorMessage NVARCHAR(4000) ; SET ErrorMessage = ERROR_MESSAGE() ; RAISERROR('Error %s occurred in SelectCodeChangeLogAndCode' ,16,1,ErrorMessage) ; RETURN -1 ; END ; ELSE BEGIN ; PRINT 'Encountered a deadlock' END ; END CATCH ; SET tryCount = tryCount + 1 ; END ; RETURN 0 ; END ; Listing 8-8: Altering the ChangeCodeDescription stored procedure so that it retries after a deadlock. Before we run our test, let's reset the test data in our and CodeCsodeDescriptions- ChangeLog tables. 273Chapter 8: Defensive Error Handling reset our test data DELETE FROM dbo.CodeDescriptionsChangeLog ; DELETE FROM dbo.Codes ; INSERT INTO dbo.Codes ( Code, Description ) VALUES ( 'IL', 'IL' ) ; GO EXEC dbo.ChangeCodeDescription Code = 'IL', Description = 'Ill.' ; GO SELECT Code , Description FROM dbo.Codes ; SELECT Code , OldDescription + ', ' + NewDescription FROM dbo.CodeDescriptionsChangeLog ; (1 row(s) affected) (1 row(s) affected) Modifications succeeded Code Description IL Ill. (1 row(s) affected) Code IL IL, Ill. (1 row(s) affected) Listing 8-9: Resetting the test data. 274Chapter 8: Defensive Error Handling We're now ready to run the test. From one tab in SSMS, we'll start a SERIALIZABLE transaction against the CodeDescriptionsChangeL tab og le, as shown in Listing 8-10. SET DEADLOCK_PRIORITY HIGH ; SET TRANSACTION ISOLATION LEVEL SERIALIZABLE ; BEGIN TRANSACTION ; SELECT FROM dbo.CodeDescriptionsChangeLog ; / UPDATE dbo.Codes SET Description = 'Illinois' WHERE Code = 'IL' ; COMMIT ; / Listing 8-10: Tab 1, start a transaction against the CodeDescriptionsChangeLog table. From a second tab, invoke our stored procedure, as shown in Listing 8-11. The session will "hang" in lock waiting mode, due to our SERIALIZA transacti BLE on accessing the CodeDescriptionsChangeLog table. EXEC dbo.ChangeCodeDescription code='IL', Description='?' ; SELECT Code , Description FROM dbo.Codes ; SELECT Code , OldDescription + ', ' + NewDescription FROM dbo.CodeDescriptionsChangeLog ; Listing 8-11: Tab 2, invoke the ChangeCodeDescription stored procedure. 275Chapter 8: Defensive Error Handling Now return to Tab 1, and execute the commented UPD against the ATE Code tab s le, from Listing 8-10, including the COMM . As soon as this code tri IT es to execute, a deadlock is detected. SQL Server chooses our stored procedure execution from Tab 2 as the deadlock victim, since we deliberately contrived for this to be the case. The transaction in our T b RY lock is rolled back, but then our CA bTlC ock is ex H ecuted and we try to execute our stored procedure again. This time, since Tab 1 has now committed, the modification succeeds. The output from Tab 2 is shown in Listing 8-12. Rolling back Encountered a deadlock (1 row(s) affected) (1 row(s) affected) Modifications succeeded Code Description - IL ? (1 row(s) affected) Code - IL IL, Ill. IL Illinois, ? Listing 8-12. Tab 2, output from execution of the stored procedure. Note also, however, that the UPD we ex ATE ecute from Tab 1 is "lost;" its changes were overwritten when the retry succeeded. From these examples, we have learned the following: • if several modifications must succeed or fail together, use transactions, and roll the modification back, as a unit, if any one of them fails 276Chapter 8: Defensive Error Handling • always anticipate that any modification may fail; use XACT_ABO to ensur RT e that transactions roll back after a failure; alternatively, we can wrap our transactions in TRY blocks, and roll them back in CAT bC lH ocks. Unfortunately, there are a few problems with using TRY…C err ATCor handling that we H need to discuss. In the next section, we'll look at some ways in which TRY… err CAT or CH handling is limited and its behavior surprising. We'll then see what we can achieve when using C for error handling, instead of T-SQL. TRY…CATCH Gotchas T-SQL is not really an efficient language for error handling, and is certainly less robust than error handling in client-side languages such as C++, Java, and C. As such, although in most cases TRY…CATCH blocks work as expected and catch errors as they should, there are also quite a few "special cases" that we need to know about, where the behavior is not as we might expect. Furthermore, TRY…CATC err H or handling does not really facilitate code reuse. If we want to use this approach in another stored procedure, we cannot fully reuse our T-SQL error handling code; we have to cut and paste much of it into that other stored procedure. This, as we proved in Chapter 5, Reusing T-SQL Code, is a recipe for bugs and inconsistencies. Over the following sections, we'll discuss some of the special cases of which we need to be aware when using TRY…CATC.H Re-throwing errors In many cases, we do not wish to handle certain errors in our bCA lock TCH, and instead want to re-throw them, so that they are handled elsewhere. In our previous example, where we wished to retry execution after a deadlock, all other errors were handled by capturing the error message, using the ERROR_MESS functi AGE on, and re-throwing the error using RAISERRO . H R owever, the error message on its own is generally insufficient; we should also retrieve the information from the ERROR_L , INE ERROR_NUMBER, ERROR_PROCEDURE, ERROR_SEVERITY, and ERROR_STATE functions, declare variables to store this information, and then use RAI to SERROR 277Chapter 8: Defensive Error Handling re-throw it. This is very verbose and, as we shall see later, we can achieve exactly the same outcome in C by issuing one single command: thr.ow However, the real problem with the TRY…CA appr TCH oach is this: RAISERRO cannot R preserve ERROR_NUMBER , so when we re-throw an error we often change its error code. For example, consider the ConversionErrorDem stor o ed procedure in Listing 8-13. It attempts to cast a string as an integer in the blT ock RY , and then in the CAT b CH lock invokes two of the seven error handling functions and re-throws the error. CREATE PROCEDURE dbo.ConversionErrorDemo AS BEGIN TRY ; SELECT CAST('abc' AS INT) ; some other code END TRY BEGIN CATCH ; DECLARE ErrorNumber INT , ErrorMessage NVARCHAR(4000) ; SELECT ErrorNumber = ERROR_NUMBER() , ErrorMessage = ERROR_MESSAGE() ; IF ErrorNumber = 245 BEGIN ; we shall not handle conversion errors here let us try to re-throw the error, so that it is handled elsewhere. This error has number 245, but we cannot have RAISERROR keep the number of the error. RAISERROR(ErrorMessage, 16, 1) ; END ; ELSE BEGIN ; handle all other errors here SELECT ErrorNumber AS ErrorNumber , ErrorMessage AS ErrorMessage ; END ; END CATCH ; GO 278

Advise: Why You Wasting Money in Costly SEO Tools, Use World's Best Free SEO Tool Ubersuggest.