One of our customer have one Oracle 10.2.0.5 RAC (HPUX) and two SQL Server 2012 (Windows server 2008R2). And we are helping them to publish data from Oracle to SQL Server. They also need to know which rows are added, updated and deleted, but they do not want to modify their apps.
The whole thing works like this :
Oracle as publisher -> SQL Server A as distributor -> SQL Server B as
subscriber
Our DBA had configured all the DBs through SSMS(SQL Server Management Studio) like this Create a Publication from an Oracle Database. It worked very well for several days. But the performance of Oracle is getting worse and worse. At last, we have to stop the data publish of Oracle.
It turns out that, SSMS will create one package called "HREPL" in Oracle, which has a procedure called "PollEnd". "PollEnd" will be executed in a very high frequency to delete data in table "HREPL_ARTICLE1LOG_1". But the execution time of "PollEnd" increases through time. At last, the execution time is longer than the time span to execute, and the table is locked, and the performance of Oracle will be very bad.
And we stuck here.
Does anybody have any idea how to fix this? Please help!
The "PollEnd" procedure:
-----------------------------------------------------------------------------------
--
-- Name: PollEnd
-- Purpose: PollEnd request signifies that the change entries identified with the current
-- interval have been successfully entered into the store and forward database
-- and can be deleted from the article log tables.
-- Input:
-- argLSN IN RAW(10) LSN from distributor that was associated
-- with this poll interval
-- Output:
-- Notes: This request causes those entries of the article log tables represented in the
-- Poll Table and having the current pollid to be deleted from both their article log
-- tables and from the Poll Table. The last request value is updated to reflect a
-- PollEnd request.
--
-----------------------------------------------------------------------------------
PROCEDURE PollEnd
(
argLSN IN RAW
)
AS
SQLCommand VARCHAR2(500);
LogTable VARCHAR2(255);
CurrentPollID NUMBER;
TableIDs number_tab;
InstanceIDs number_tab;
IDCount BINARY_INTEGER;
PublisherLSN RAW(10);
BEGIN
-- Put the published tableIDs in a PL/SQL table of IDs
HREPL.GetTableIDs(TableIDs, InstanceIDs);
-- Get the current Poll ID
SELECT Publisher_CurrentPollid INTO CurrentPollID FROM HREPL_Publisher;
IDCount := TableIDs.COUNT;
-- For each table represented in the ID list
FOR id_ind IN 1 .. IDCount
LOOP
LogTable := REPLACE( REPLACE(ArticleLogTemplate, MatchString, TO_CHAR(TableIDs(id_ind))),
MatchStringY, TO_CHAR(InstanceIDs(id_ind)));
BEGIN
-- Generate command to delete from the article log those entries appearing in the
-- Poll Table with the current PollID
SQLCommand := 'DELETE FROM ' || LogTable || ' l ' ||
'WHERE EXISTS (SELECT p.POLL_POLLID FROM HREPL_POLL p ' ||
' WHERE CHARTOROWID(l.ROWID) = p.Poll_ROWID ' ||
' AND p.Poll_PollID = :Pollid)';
HREPL.ExecuteCommandForPollID(SQLCommand, CurrentPollID);
EXCEPTION
WHEN OTHERS THEN NULL;
END;
END LOOP;
FOR POLLID IN (SELECT CurrentPollid FROM DUAL)
LOOP
-- Delete from HREPL_Event those entries appearing in the Poll Table
-- with the current PollID.
DELETE FROM HREPL_Event e
WHERE EXISTS (SELECT p.POLL_POLLID FROM HREPL_POLL p
WHERE CHARTOROWID(e.ROWID) = p.Poll_ROWID
AND p.Poll_PollID = POLLID.CurrentPollID);
-- Delete entries from the Poll Table having the current Pollid
DELETE FROM HREPL_Poll
WHERE Poll_PollID = POLLID.CurrentPollID;
END LOOP;
-- Drop all views associated with articles that are marked as UnPublishPending.
-- Note: We cannot perform these drops in UnPublish table, since UnPublish
-- table can execute concurrently with PollBegin and the querying
-- of published tables by the log reader. PollEnd, however, executes
-- synchronously with respect to these activities, so can be used
-- to cleanup log tables and views that are no longer needed.
HREPL.CleanupLogsandViews;
-- Mark the last request as PollEnd, and update the Publisher LSN
-- to reflect the LSN committed at the publisher.
UPDATE HREPL_Publisher
SET Publisher_PollInProcess = NoPollInProcess,
Publisher_LSN = argLSN;
-- Commit transaction
COMMIT;
EXCEPTION
WHEN OTHERS THEN
ROLLBACK;
RAISE;
END PollEnd;
Edit 01:
The full package is here: HREPL
Edit 02:
At last we give up. MS and Oracle blame each other.
We tried to use ogg to copy data from oracle to sql server, which is a mess too.
Now we are trying to use ogg to copy data from oracle to oracle.
Thanks for all the help.
Convert your DELETE ... WHERE EXISTS (...) queries to use multiple table delete syntax.
SQLCommand := 'DELETE l' ||
' FROM HREPL_POLL, ' || LogTable ||
' l WHERE CHARTOROWID(l.ROWID) = p.Poll_ROWID ' ||
' AND p.Poll_PollID = :Pollid)';
Create a function index on each table involved:
CREATE INDEX MYTABLE_CHARTOROWID ON MYTABLE(CHARTOROWID(ROWID));
And then further down:
DELETE e
FROM HREPL_POLL p, HREPL_Event e
WHERE CHARTOROWID(e.ROWID) = p.Poll_ROWID
AND p.Poll_PollID = POLLID.CurrentPollID;
Finally, delete entirely the LOOP over dual - it does absolutely nothing whatsoever; just execute the code inside it using CurrentPollid directly.
Some join conditions seem unreasonable, you might be more lucky with this version - if you try it on production, you do that on your very own risk!
-----------------------------------------------------------------------------------
--
-- Name: PollEnd
-- Purpose: PollEnd request signifies that the change entries identified with the current
-- interval have been successfully entered into the store and forward database
-- and can be deleted from the article log tables.
-- Input:
-- argLSN IN RAW(10) LSN from distributor that was associated
-- with this poll interval
-- Output:
-- Notes: This request causes those entries of the article log tables represented in the
-- Poll Table and having the current pollid to be deleted from both their article log
-- tables and from the Poll Table. The last request value is updated to reflect a
-- PollEnd request.
--
-----------------------------------------------------------------------------------
PROCEDURE PollEnd
(
argLSN IN RAW
)
AS
SQLCommand VARCHAR2(500);
LogTable VARCHAR2(255);
CurrentPollID NUMBER;
TableIDs number_tab;
InstanceIDs number_tab;
IDCount BINARY_INTEGER;
PublisherLSN RAW(10);
BEGIN
-- Put the published tableIDs in a PL/SQL table of IDs
HREPL.GetTableIDs(TableIDs, InstanceIDs);
-- Get the current Poll ID
SELECT Publisher_CurrentPollid INTO CurrentPollID FROM HREPL_Publisher;
IDCount := TableIDs.COUNT;
-- For each table represented in the ID list
FOR id_ind IN 1 .. IDCount
LOOP
LogTable := REPLACE( REPLACE(ArticleLogTemplate, MatchString, TO_CHAR(TableIDs(id_ind))),
MatchStringY, TO_CHAR(InstanceIDs(id_ind)));
BEGIN
-- Generate command to delete from the article log those entries appearing in the
-- Poll Table with the current PollID
SQLCommand := 'DELETE FROM ' || LogTable || ' l ' ||
'WHERE l.ROWID IN (SELECT chartorowid(p.Poll_ROWID) FROM HREPL_POLL p ' ||
' WHERE p.Poll_PollID = :Pollid)';
HREPL.ExecuteCommandForPollID(SQLCommand, CurrentPollID);
EXCEPTION
WHEN OTHERS THEN NULL;
END;
END LOOP;
-- Delete from HREPL_Event those entries appearing in the Poll Table
-- with the current PollID.
DELETE FROM HREPL_Event e
WHERE ROWID in (SELECT chartorowid(p.Poll_ROWID) FROM HREPL_POLL p
WHERE p.Poll_PollID = CurrentPollID);
-- Delete entries from the Poll Table having the current Pollid
DELETE FROM HREPL_Poll
WHERE Poll_PollID = CurrentPollID;
-- Drop all views associated with articles that are marked as UnPublishPending.
-- Note: We cannot perform these drops in UnPublish table, since UnPublish
-- table can execute concurrently with PollBegin and the querying
-- of published tables by the log reader. PollEnd, however, executes
-- synchronously with respect to these activities, so can be used
-- to cleanup log tables and views that are no longer needed.
HREPL.CleanupLogsandViews;
-- Mark the last request as PollEnd, and update the Publisher LSN
-- to reflect the LSN committed at the publisher.
UPDATE HREPL_Publisher
SET Publisher_PollInProcess = NoPollInProcess,
Publisher_LSN = argLSN;
-- Commit transaction
COMMIT;
EXCEPTION
WHEN OTHERS THEN
ROLLBACK;
RAISE;
END PollEnd;
Related
I have two databases: db1 and db2 (db2 was completely empty). I was copying all the db1 to db2 but the progress was interrupted and I need to know which tables are still left to copy. How can I compare the count of each table in these two databases to know which tables I still have to transfer?
Basically, you need to loop through the data dictionary and generate some dynamic SQL which executes a count for each table.
I have assumed you're only transferring one schema. If that's not true, or you're not connecting as the target schema, you'll need to use ALL_TABLES instead of USER_TABLES, and include the OWNER column in the driving query and the dynamic query too.
declare
n pls_integer;
stmt varchar2(32767);
begin
for r in ( select table_name from user_tables order by table_name ) loop
stmt := 'select count(*) from ' || r.table_name;
-- uncomment the next line to debug errors
-- dbms_output.put_line(stmt);
execute immediate stmt into n;
-- you may wish to only display empty tables
-- if n = 0 then
dbms_output.put_line(r.table_name || ' = ' || lpad(n, 10));
-- end if;
end loop;
end;
One would hope that your data copying process was clever enough to commit only completed tables. If so you only need to run this on DB2. Otherwise on both.
I use stored procedures to manage a warehouse. PDA scanners scan the added stock and send it in bulk (when plugged back) to a SQL database (SQL Server 2016).
The SQL database is fairly remote (other country), so there's sometimes delay in some queries, but this particular one is problematic: even if the stock table is fine, I had some problems with updating the occupancy of the warehouse spots. The PDA tracks the added items in every spot as a SMALLINT, then send back this value to the stored procedure below.
PDA "send_spots" query:
SELECT spot, added_items FROM spots WHERE change=1
Stored procedure:
CREATE PROCEDURE [dbo].[update_spots]
#spot VARCHAR(10),
#added_items SMALLINT
AS
BEGIN
BEGIN TRAN
UPDATE storage_spots
SET remaining_capacity = remaining_capacity - #added_items
WHERE storage_spot=#spot
IF ##ROWCOUNT <> 1
BEGIN
ROLLBACK TRAN
RETURN - 1
END
ELSE
BEGIN
COMMIT TRAN
RETURN 0
END
END
GO
If the remaining_capacity value is 0, the PDAs can't add more items to it on next round. But with this process, I had negative values because the query allegedly ran two times (so subtracted #added_items two times).
Is there a way for that to be possible? How could I fix it? From what I understood the transaction should be cancelled (ROLLBACK) if the affected rows are != 1, but maybe that's something else.
EDIT: current solution with the help of #Zero:
CREATE PROCEDURE [dbo].[update_spots]
#spot VARCHAR(10),
#added_racks SMALLINT
AS
BEGIN
-- Recover current capacity of the spot
DECLARE #orig_capacity SMALLINT
SELECT TOP 1
#orig_capacity = remaining_capacity
FROM storage_spots
WHERE storage_spot=#spot
-- Test if double is present in logs by comparing dates (last 10 seconds)
DECLARE #is_double BIT = 0
SELECT #is_double = CASE WHEN EXISTS(SELECT *
FROM spot_logs
WHERE log_timestamp >= dateadd(second, -10, getdate()) AND storage_spot=#spot AND delta=#added_racks)
THEN 1 ELSE 0 END
BEGIN
BEGIN TRAN
UPDATE storage_spots
SET remaining_capacity= #orig_capacity - #added_racks
WHERE storage_spot=#spot
IF ##ROWCOUNT <> 1 OR #is_double <> 0
-- If double, rollback UPDATE
ROLLBACK TRAN
ELSE
-- If no double, commit UPDATE
COMMIT TRAN
-- write log
INSERT INTO spot_logs
(storage_spot, former_capacity, new_capacity, delta, log_timestamp, double_detected)
VALUES
(#spot, #orig_capacity, #orig_capacity-#added_racks, #added_racks, getdate(), #is_double)
END
END
GO
I was thinking about possible causes (and a way to trace them) and then it hit me - you have no value validation!
Here's a simple example to illustrate the problem:
Spot | capacity
---------------
x1 | 1
Update spots set capacity = capacity - 2 where spot = 'X1'
Your scanner most likely gave you higher quantity than you had capacity to take in.
I am not sure how your business logic goes, but you need to perform something in lines of
Update spots set capacity = capacity - #added_items where spot = 'X1' and capacity >= #added_items
if ##rowcount <> 1;
rollback;
EDIT: few methods to trace your problem without implementing validation:
Create a logging table (with timestamp, user id (user, that is connected to DB), session id, old value, new value, delta value (added items).
Option 1:
Log all updates that change value from positive to negative (at least until you figure out the problem).
The drawback of this option is that it will not register double calls that do not result in a negative capacity.
Option 2: (logging identical updates):
Create script that creates a global temporary table and deletes records, from that table timestamps older than... let's say 10 minutes once every minute or so (play around with numbers).
This temporary table should hold the data passed to your update statement so 'spot', 'added_items' + 'timestamp' (for tracking).
Now the crucial part: When you call your update statement check if a similar record exists in the temporary table (same spot and added_items and where current timestamp is between timestamp and [timestamp + 1 second] - again play around with numbers). If a record like that exist log that update, if not add it to temporary table.
This will register identical updates that go within 1 second of each other (or whatever time-frame you choose).
I found here an alternative SQL query that does the update the way I need, but with a temporary value using DECLARE. Would it work better in my case, or is my initial query correct?
Initial query:
UPDATE storage_spots
SET remaining_capacity = remaining_capacity - #added_items
WHERE storage_spot=#spot
Alternative query:
DECLARE #orig_capacity SMALLINT
SELECT TOP 1 #orig_capacity = remaining_capacity
FROM storage_spots
WHERE spot=#spot
UPDATE Products
SET remaining_capacity = #orig_capacity - #added_items
WHERE spot=#spot
Also, should I get rid of the ROLLBACK/COMMIT instructions?
Got an error when ran update statement.
For one record it worked fine, but for a chunk of records it gives me an error.
Also, why it tells me that 64801 row(s) affected and then 1 row(s) affected and then 0? How should I interpret that?
This is the script:
update tblQuotes
set QuoteStatusID = 11, --Not Taken Up
QuoteStatusReasonID = 9 --"Not Competitive"
where CAST(EffectiveDate as DATE) < CAST('2013-11-27' as DATE)
and CompanyLocationGuid = '32828BB4-E1FA-489F-9764-75D8AF7A78F1' -- Plaza Insurance Company
and LineGUID = '623AA353-9DFE-4463-97D7-0FD398400B6D' --Commercial Auto
I added BEGIN TRANSACTION statement, but it still won't work.
BEGIN TRANSACTION
update tblQuotes
set QuoteStatusID = 11, --Not Taken Up
QuoteStatusReasonID = 9 --"Not Competitive"
where CAST(EffectiveDate as DATE) < CAST('2017-11-27' as DATE)
AND CompanyLocationGuid = '32828BB4-E1FA-489F-9764-75D8AF7A78F1' -- Plaza Insurance Company
and LineGUID = '623AA353-9DFE-4463-97D7-0FD398400B6D' --Commercial Auto
IF ##TRANCOUNT>0
COMMIT TRANSACTION
In my opinion this is a "flaw", if not a "bug" in SQL Server. When you COMMIT a transaction, TRANCOUNT is decremented by 1. When you ROLLBACK any transaction, all transactions in the calling stack are rolled back! This means that any calling procedure that tries to commit or rollback will have this error and you've lost the integrity of your calling stack.
I worked through this when building a mechanism do do unit testing on SQL Server. I get around it by always using named transactions as shown in the example below. You can obviously also check XACT_STATE. The point is simply that, rather than blindly committing and rolling back anonymous transactions, if you manage transactions by name or transaction id you have better control.
For unit testing, I write a stored procedure as a test that calls the procedure under test. The unit test is in either serializable or snapshot mode and ONLY includes a rollback statement. I call the procedure under test, validate the results, build test output (pass/fail, parameters, etc.) as XML output, then everything gets rolled back. This gets around the need to build "mock data". I can use the data on any environment as the transaction is always rolled back.
--
-- get #procedure from object_name(##procid)
-------------------------------------------------
DECLARE #procedure SYSNAME = N'a_procedure_name_is_a_synonym_so_can_be_longer_than_a_transaction_name'
, #transaction_id BIGINT;
DECLARE #transaction_name NVARCHAR(32) = RIGHT(#procedure + N'_tx', 32);
--
BEGIN TRANSACTION #transaction_name;
BEGIN
SELECT #transaction_id = [transaction_id]
FROM [sys].[dm_tran_active_transactions]
WHERE [name] = #transaction_name;
SELECT *
FROM [sys].[dm_tran_active_transactions]
WHERE [name] = #transaction_name;
-- Perform work here
END;
IF EXISTS
(SELECT *
FROM [sys].[dm_tran_active_transactions]
WHERE [name] = #transaction_name)
ROLLBACK TRANSACTION #transaction_name;
This error states that in SQL Server, you have given a Commit or Commit Transaction without specifying a Begin Transaction or the number of commit transactions is greater than the number of begin transactions. To avoid this make sure you check the existing transactions on the current session before committing.
So a normal Commit Transaction will be updated as below
IF ##TRANCOUNT>0
COMMIT TRANSACTION
There is a trigger, that making sure about the integrity of the QuoteStatusID. So im my WHERE clause I have to exactly specify what current status ID policy have to have in order to be updated.
I'm trying to write a stored procedure that truncates the first table on our data warehouse, then copies data from our local database to the DWH server.
Here's the code:
USE [ARGTPAWN-DB-DWH].[DWH].[dbo].[PML];
GO
TRUNCATE TABLE [ARGTPAWN-DB-DWH].[DWH].[dbo].[PML];
GO
SELECT *
INTO [ARGTPAWN-DB-DWH].[DWH].[dbo].[PML]
FROM [14TPAWNDB001].[FLMedicaid].[dbo].[PML]
GO
And the response I am getting is:
Msg 911, Level 16, State 1, Line 1
Database 'ARGTPAWN-DB-DWH' does not exist. Make sure that the name is entered correctly.
Msg 4701, Level 16, State 1, Line 3
Cannot find the object "PML" because it does not exist or you do not have permissions.
Msg 117, Level 15, State 1, Line 7
The object name 'ARGTPAWN-DB-DWH.DWH.dbo.PML' contains more than the maximum number of prefixes. The maximum is 2.
The servers are already linked, so that is not an issue, but I'm very curious as to why this is not working.
Linked Server and distributed query can be tricky in term of performance...
You should consider to write the Stored Procedure on the database that hosts the target tables even if you call it from the database that hosts the source tables.
On target database :
CREATE PROCEDURE [DBO].[TARGET_SIDE_PS]
AS
-- For error handling.
DECLARE #aERROR int
DECLARE #aCOUNT int
-- Start transaction.
BEGIN TRANSACTION
-- Drop target table.
IF OBJECT_ID('dbo.PML', 'U') IS NOT NULL
DROP TABLE dbo.PML;
-- Error catching
SELECT #aERROR = ##ERROR, #aCOUNT = ##ROWCOUNT
IF #aERROR<>0
BEGIN
-- Error : do what is needed.
--
ROLLBACK TRANSACTION
RETURN 1
END
SELECT *
INTO dbo.PML
FROM [SOURCELINKEDSERVER].[SOURCEDATABASE].[dbo].[PML]
-- Error catching
SELECT #aERROR = ##ERROR, #aCOUNT = ##ROWCOUNT
IF #aERROR<>0
BEGIN
-- Error : do what is needed.
--
ROLLBACK TRANSACTION
RETURN 2
END
IF #aCOUNT <= 0
BEGIN
-- No data: do what is needed.
--
PRINT 'NO DATA !!'
END
COMMIT TRANSACTION
RETURN 0
Call the target side PS from the source side (or from the target side) and it's done but source side database have to be linked to target side database.
Transaction can be remove due to DROP/CREATE/INSERT sequence.
You can do the oposite : PS on the source side, with drop and insert on the linked target side server database, but you must know :
- transaction will take a while.
- all the source data will be locked during the whole process.
- INSERT will take a while.
You don't need to execute:
USE [ARGTPAWN-DB-DWH].[DWH].[dbo].[PML];
Simply execute the lines below.
What is ARGTPAWN-DB-DWH? If this is the server name it is not needed.
Your USE statement should refer to the database only.
Your SELECT * INTO... will attempt to create the table PML, if this already exists (which it will if you are performing a truncate) it will fail - Use INSERT INTO... or DROP TABLE... instead of truncate.
Don't use SELECT *.
Business Scenario: This is a ticketing system, and we got so many user using the application. When a ticket(stored in 1st table in below) comes in to the application, any user can hit the ownership button and take ownershipf of it.
Only one user can take ownership for one ticket. If two user tries to hit the ownership button, first one wins and second gets another incident or message that no incident exists to take ownership.
Here i am facing a concurrency issue now. I already have a lock implementation using another table(2nd table in below).
I have two tables;
Table(Columns)
Ticket(TicketID-PK, OwnerUserID-FK)
TicketOwnerShipLock(TicketID-PK, OwnerUserID-FK, LockDate)Note: Here TicketID is set as Primary Key.
Current lock implementation: whenever user one tries to own ticket puts an entry to 2nd table with TicketID, UserID and current date,then goes to update the OwnerUserID in 1st table.
Before insert the above said lock entry, Procedure checks for any other user already created any lock for the same incident.
If already there is lock, lock wont be opened for the user. Else lock entry wont be entered and the user cannot update the ticket onwership.
More Info: There are so many tickets getting opened in 1st table, whenever user tries to take ownership, we should find the next available ticket to take ownership. So need to find ticket and to do some calculation and set a status for that ticket, there one more column in 1st table StatusID. Status will be assigned as Assigned.
Problem: Somehow two user's got the ownership for same ticket at excatly same time, i have even checked the millisecond but that too same.
1. I would like to know if any MS SQL Server LOCK would help in this scenario.
2. Or do i need to block table while insert.(This 2nd rable will not have much data approx. less than 15 rows)
Lock Creation Procedure Below:
ALTER PROCEDURE [dbo].[TakeOwnerShipGetLock]
#TicketId [uniqueidentifier],
#OwnerId [uniqueidentifier]
AS
BEGIN
SET NOCOUNT ON;
BEGIN TRANSACTION TakeOwnership
BEGIN TRY
DECLARE #Lock BIT
SET #Lock = 0
DECLARE #LockDate DATETIME
SELECT #LockDate = LockDate
FROM dbo.TakeOwnershipLock
WHERE TicketId = #TicketId
IF #LockDate IS NULL
AND NOT EXISTS ( SELECT 1
FROM dbo.TakeOwnershipLock as takeOwnership WITH (UPDLOCK)
INNER JOIN dbo.Ticket as Ticket WITH (NOLOCK)
ON Ticket.TicketID = takeOwnership.TicketId
WHERE takeOwnership.TicketId = #TicketId
AND Ticket.OwnerID is NULL )
BEGIN
INSERT INTO dbo.TakeOwnershipLock
( TicketId
,OwnerId
,LockDate
)
VALUES ( #TicketId
,#OwnerId
,GETDATE()
)
IF ( ##ROWCOUNT > 0 )
SET #Lock = 1
END
SELECT #Lock
COMMIT TRANSACTION TakeOwnership
END TRY
BEGIN CATCH
-- Test whether the transaction is uncommittable.
IF XACT_STATE() = 1
BEGIN
COMMIT TRANSACTION TakeOwnership
SET #Lock = 1
SELECT #Lock
END
-- Test whether the transaction is active and valid.
IF XACT_STATE() = -1
BEGIN
ROLLBACK TRANSACTION TakeOwnership
SET #Lock = 0
SELECT #Lock
END
END CATCH
END