2010年11月23日 星期二

僅變更來源表格中相符欄位的紀錄:UPDATE VS. MERGE INTO

從 B Table 將資料更新到 A Table的方式,大都會這樣寫:

UPDATE A 
   SET A.CODE = (SELECT DISTINCT (CODE)
                     FROM B
                    WHERE A.CODE_1 = B.CODE_1
                      AND A.CODE_2 = B.B_CODE_2)

但有個缺點是如果B與A若有沒有相符的紀錄,即在A表格中不符合 A.A_CODE = B.A_CODE & A.B_CODE = B.B_CODE 條件的紀錄,就會被更新成NULL;為了避免這種情況,我們可以加上NVL()函式跳過不相符的紀錄被更新:


UPDATE A 
   SET A.CODE = NVL((SELECT DISTINCT (CODE)
                     FROM B
                    WHERE A.CODE_1 = B.CODE_1
                      AND A.CODE_2 = B.CODE_2), A.CODE)

Oracle 在 9i 之後的版本新增一個 MERGE 的功能,在語意以及速度方面上更勝一籌:

  MERGE INTO A
  USING  B
  ON (A.CODE_1 = B.CODE_1 AND A.CODE_2 = B.CODE_2)
  WHEN MATCHED THEN
    UPDATE SET A.CODE = B.CODE;

語意上就更接近口語化,程式碼也容易變得簡潔。且不止於此,既然有 MATCHED,也就會有 NOT MATCHED,多增加了分支判斷的功能。假設我們想把不符條件的資料記錄到另外一個Table中,就可這樣寫:

MERGE INTO A
  USING  B
  ON (A.CODE_1 = B.CODE_1 AND A.CODE_2 = B.CODE_2)
  WHEN MATCHED THEN
    UPDATE SET A.CODE = B.CODE;
  WHEN NOT MATCHED THEN
INSERT INTO FAIL_REC VALUES (A.CODE_1, A,CODE_2);

分享囉~

2010年11月16日 星期二

Oracle複寫到SQL 2005時,卡在DELETE ARTICLE的終極解法

前篇文章提到當利用SQL 2005複寫機制,將Oracle複寫到SQL端時,一段時間後就會卡死在DELTE FROM Articlexxx的SQL Statement,除了速度緩慢之外,也連帶的影響其他工作效能。最後跟同事試了幾種方式,經由同事的實驗確認了以下方式會取得最佳狀態:
  • 建立好複寫機制後,替每個Table做好統計值分析;再確認執行計畫是否有使用Index即可(使用Range Scan而非Full Table Access)。這個步驟有點麻煩;我們的問題是當複寫的時候,DELETE FROM Articlexxx 會因為執行計畫不佳而Hang在哪裏。但每個Article都可能會有這樣的問題,所以我們以Oracle端部署時所用的複寫帳號登入後(假設是repadm),透過以下SQL Statement準備各Article的DELETE Article SQL Statement:

     SELECT 'DELETE FROM ' || TABLE_NAME  ||
           
    '
     WHERE EXISTS ' ||
              (SELECT p.POLL_POLLID ' ||

                 'FROM HREPL_POLL p ' || 
               'WHERE CHARTOROWID(l.ROWID) = p.Poll_ROWID ' || 

               '       AND p.Poll_PollID = :Pollid)'
       FROM USER_TABLES
     WHERE TABLE_NAME LIKE 'HREPL_ARTICLE%'


    之後,我們先塞點假資料給HREPL_POLL以及各個Article以模擬大量資料(剛開始沒資料,執行計畫就會用Full Table Access的方式規劃),然後分別開始做統計分析;這時候取得統計值就會接近實際狀況。
  • 準備好統計資訊後,利用 outline 的方式鎖住上述每一個DELETE Article SQL Statement的執行計畫,避免大量DML後Oracle對該DELETE Statement的執行計畫跑掉。
  • 以後每建立一個新的發行項目(Article),就要執行上述步驟(修改一下SQL Statement取得該新發行項目的Delete Articlexxx SQL Statement)以取得並鎖定最佳的執行計畫。
目前跑了一週,已經沒再發生Hang住的問題,這方法提供參考囉。

2010年10月26日 星期二

Workaround for hanging SQL Statement "DELETE Article" in replication

在處理Oracle複寫到SQL Server的問題之中,最令人難解跟抓狂的,莫過於LogReader在Oracle端執行類似以下的語法時,卻莫名的耗費大量時間,導致複寫機制整個停住:



DELETE FROM HREPL_ARTICLE326LOG_1 l
 WHERE EXISTS (SELECT p.POLL_POLLID
          FROM HREPL_POLL p
         WHERE CHARTOROWID(l.ROWID) = p.Poll_ROWID
           AND p.Poll_PollID = :Pollid)


這個問題目前尚未有答案,只知道一旦這個罕見的情況發生,整個複寫機制形同宣告死亡;如果這種情況在六日發生,禮拜一你來公司才發現的話,最好也檢查一下Oracle的UNDO Tablespace,因為它將產生大量的UNDO資料,很有機會將UNDO Tablespace搞爆;前兩次發生時我只能癡癡的拆掉重建 replication的機制(觀念沿襲於windows不穩定就FORMAT掉 XD),但今天總算試出一個workaround,解法如下:

  1. Kill掉Oracle上的Logread.exe session
  2. 找出所有Oracle端的article
  3. 刪除所有article的資料(強烈建議用truncate table)
  4. 打開複寫監控器觀察
過個一兩分鐘,複寫機制應該就可以復活了。


後記:

好事果然不能說出來,快樂指持續了一個下午就結束,隔幾天的DELETE ARTICLE還是會死掉。想破頭來探究原因,同事提出是否有可能統計值已經失真的問題,並且利用anaylze的方式證明似乎可以讓DELETE ARTICLE變快;那好,死馬當活馬醫,排程所有ARTICLE以及 HREPL_POOL都在適當時間做統計值分析後(改用DBMS_STATS.GATHER_TABLE_STATS處理才不會lock table,而參數ESTIMATE_PERCENT需設定成100強迫上工),的確可以明顯改善這問題。看來這種大量刪除跟新增的複寫機制,Oracle可要另外加工才能處理得好啊。@@....

Oracle複寫到SQL Server的概略圖

下圖為透過SQL Server複寫機制,將Oracle複寫到SQL Server的架構,概略說明日後補上:

相關在Oracle中建立的Table物件請參考 這裡,流程概念請參考 這裡

2010年10月18日 星期一

錯誤:18456,SQL 2005 "myUserAccount" 登入失敗

早上一來同事就大喊無法登入SQL Server測試機,快速檢查了一下,原來是登入機制有設定「強制設定密碼原則」,但因為測試機的關係隨手就把它關掉。再次嘗試登入後還是出現錯誤,這次的錯誤代碼為「18456」。Google了一下,發現 這裡 有解法,但它更具價值的地方在於文章下方的表格,已列出錯誤狀態碼各自代表的錯誤描述,好讓你更快速的判斷造成「18456」的根本錯誤原因。最後把幾個instance的登入者密碼改改就好啦,分享囉~



後記:


改了登入者密碼後,同事過沒多久又開始鬼叫無法alter procedure。找了半天才發現,原來是sp裡面有使用server link,連結到已經有修改過密碼的target。這只要再修改「遠端伺服器\屬性\安全性\」中的遠端登入密碼即可。

2010年10月14日 星期四

Workaround for failure of Orakill

還記得 Orakill也有失靈的時候 提到的,當你用Alter system kill session或者Orakill這兩個大絕都砍不掉死在裡面的session的時候,通常建議只有兩種,不是要你下定決心上patch外,就是要你直接閉著眼睛restart instance,雖然可以解決砍不掉的問題,但downtime時間令人難以忍受。今天恰巧又撞上這個難解之題,但這次手上有了Process Explore,對於Windows平台的Oracle來講可說是一大福音。Process Explore可以找出每個Process含有的Thread清單,這對於我們找出需要砍掉的session是很有幫助的。做法很簡單,首先利用下面SQL找出session的spid(thread handle):

select spid, osuser, s.program
  from 
v$process p, v$session s
where p.addr=s.paddr
   and s.sid=785    /*785是你session的session id */

然後打開Process Explore,將Process以Tree的方式展開,找到Oracke.exe後點選它,你就可以在下方看見Thread List;每個Thread後面都會有一個數字,那個就是Thread Handle ID,請確實的對它按右鍵(因為它會亂飄,挺好動的),選擇「Close Handle」,即可大功告成。一下子你就可以看到Oracle Database Control裡的Top Activities清爽多了。

2010年10月13日 星期三

升級你的RMAN CATALOG

今天撞上一個挺有趣的情況。日前我升級了RMAN 專屬的DataBase,但沒有跟著login到RMAN Catalog進行升級。今天一登入後就出現了下列的訊息:


圖1、未升級RMAN CATALOG前,登入RMAN的提示訊息

google了一下才知道原來rman catalog也要在Database升級後跟進才可正確的使用。升級方式很簡單,先以catalog owner的身分登入RMAN後,連打兩次 upgrade catalog即可,畫面參考如下:

圖2、升級RMAN CATALOG

第一次打upgrade catalog 會確認你是CATALOG的OWNER,之後再打一次upgrade catalog即可升級成功。

2010年9月22日 星期三

如何設定Oracle 10g Control File & Redo Log File的位置



閒來沒事,順手整理Control File & Redo Log File建立時會考量的幾個參數以及建立的決策。簡圖示意如下圖:
重點摘要如下:
  1. 判斷是否有定義DB_CREATE_ONLINE_LOG_DEST_n,如果有定義,則將Control File & Redo Log File寫入該參數(群)指定的路徑,然後便結束建立作業,否則繼續下一步驟。
  2. 判斷是否有定義CREATE_FILE_DEST,如果有定義,則將Control File & Redo Log File寫入該參數指定的路徑。而不管有無定義,都繼續下一步驟。
  3. 判斷是否有定義DB_RECOVERY_FILE_DEST,如果有定義,則將Control File & Redo Log File複寫至該參數指定的路徑,即FRA(Flash Recovery Area),然後便結束建立作業,否則繼續下一步驟。
  4. 如果以上參數都無設定,則將Control File & Redo Log File寫至Oracle預設路徑;預設路徑則依照OS有所不同。所謂Oracle預設的路徑,可以查詢Oracle參數檔內的CONTROL_FILES參數的設定值。查詢方式很簡單:

    OS> sqlplus sys/password@mydb as sysdba
    SQL>show parameter control_files


    NAMETYPEVALUE
    ----------------------------------------
    control_filesstring(因OS不同而有差異的檔案路徑)

下次要設定Control File & Redo Log File的位置就可參考這張圖,當作設定檢核點吧。

2010年8月12日 星期四

SQL Server 增加欄位的至指定位置的錯覺

如果你以SQL Server Management Studio來編輯現有Table的欄位位置,感覺好像很方便,但說穿了,不過是玩了一招移花接木的招數而已。有圖為證:

原本Test 表只有一個a欄位,我用Studio修改該表,加入一個新欄位v並且放到a前面,然後按下左上角的「產生變更指令」後,就可看到上面的圖示;我把指令給「放大」如下:

/* 為了避免發生資料可能遺失的任何問題,您應該先詳細檢視此指令碼,然後才能在資料庫設計工具環境以外的位置執行。*/
BEGIN TRANSACTION
SET QUOTED_IDENTIFIER ON
SET ARITHABORT ON
SET NUMERIC_ROUNDABORT OFF
SET CONCAT_NULL_YIELDS_NULL ON
SET ANSI_NULLS ON
SET ANSI_PADDING ON
SET ANSI_WARNINGS ON
COMMIT
BEGIN TRANSACTION
GO
CREATE TABLE dbo.Tmp_test
(
v nchar(10) NULL,
a varchar(20) NULL
) ON [PRIMARY]
GO
IF EXISTS(SELECT * FROM dbo.test)
EXEC('INSERT INTO dbo.Tmp_test (a)
SELECT a FROM dbo.test WITH (HOLDLOCK TABLOCKX)')
GO
DROP TABLE dbo.test
GO
EXECUTE sp_rename N'dbo.Tmp_test', N'test', 'OBJECT'
GO
COMMIT


就這樣,當你以為SQL 2005真的做了什麼了不起的事的時候,小心,說不定它也只是取巧而以。

2010年8月10日 星期二

遇見ORA-16014:Recovery空間不足的錯誤


早上一進辦公室就被同事追殺,劈頭就問測試區怎麼進不去,前端SQL開發工具又出現啥勞子「未存檔日誌...」之類的鬼訊息,從他們個個「殷切」關心的眼神來看,壓力很快就填滿整個心臟了。

好了,我試著用sqlplus 登入,並查詢目前$instance的狀態,發現是Mounted的狀態,那還不簡單?直接改成Open就好啦。可是直覺告訴這應該只是假象,事情沒那麼單純。等到一執行 alter database open之後,果不其然就出現了同事口中的鬼訊息了:

SQL> alter database open;
alter database open
*
ERROR 在行 1:
ORA-16014: 未存檔日誌 2 序號 171291, 沒有可用的目的地
ORA-00312: 線上日誌 2 繫線 1:
'D:\ORACLE\PRODUCT\10.2.0\ORADATA\myDatabase\REDO02.LOG'

上網快速查了一下,原來是Recovery的空間不夠了,我開了220G的Recovery還不夠,難道是因為每天凌晨排程的Impdp關係造成的嗎?無時間多想,先在sqlplus將Recovery空間加大到250(alter system set db_recovery_file_dest_size = 250G),再到RMAN下了ARCHIVELOG ALL DELETE INPUT 騰出空間來,耐心等了10分鐘左右,再回到sqlplus再將資料改成Open狀態後,搞定!

至於Impdp為何會產生大量Archive log的問題,腦袋中閃過一些些殘存的影像(全面啟動?),等我印證後再來報告。

2010年8月9日 星期一

[SQLSTATE 42000] (錯誤 20036) 解法

如果你發現在「作業活動監視器」有個「清除逾期的訂閱」作業歷程老是告訴你無法執行成功,錯誤訊息又是[SQLSTATE 42000] (錯誤 20036)的話,你應該犯了跟我一樣的「錯」。這個錯誤來自散發者伺服器未將本身加入發行者的所造成的,看起來應該是個SQL2005的Bug。解決方式很簡單,把散發者加入發行者就可以了;步驟參考 這裡,我翻譯成中文如下:

  1. 在複寫節點按右鍵,選擇「散發者屬性」。
  2. 在左邊「選取頁面」選擇「發行者」。
  3. 在右邊選擇「加入」按鈕,並選擇「加入SQL Server發行者」。
  4. 提供散發者登入資訊即可。
  5. 回到「作業活動監視器」,按右鍵點選出錯的「清除逾期的訂閱」作業,選擇「從下列步驟作業」即可。

一開始假設我沒犯了潔癖的毛病,把預設加入發行者的散發者給移掉的話,這錯誤也不會困擾我好些陣子,真是無語。

2010年6月7日 星期一

複寫不同定序的資料


如果你跟我遇到A、B兩台SQL 2005 Server DB 定序不同、卻要同步資料的情況時,那麼解法比想像中的簡單許多。請看下圖中複製定序的欄位,把它變成False即可。這樣一來,來源DB就不會將定序的限制明白的sch檔中,當複寫到目標DB時即可以DB預設的定序完成定序的自然轉換。

2010年5月22日 星期六

定序對於 User Provisioning Tool的影響

因為資料移轉需求,在安裝新的SQL Server 2005時,我將定序設定可區分大小寫的選項,好讓未來新增的資料庫都能依照相同的定序設定。沒想到,安裝步驟完後成,執行User Provisioning Tool以設定管理員的sysadamin權限時,竟然發生15007的錯誤:

標題: .Net SqlClient Data Provider
------------------------------
'mydomain\admin' 不是有效的登入,或者您沒有權限。

如需說明,請按一下: http://go.microsoft.com/fwlink?ProdName=Microsoft+SQL+Server&ProdVer=09.00.4035&EvtSrc=MSSQLServer&EvtID=15007&LinkId=20476

好笑了,我不就是以管理員身分登入嗎,怎麼還會有「不是有效的登入」錯誤訊息呢?苦思許久後,心想User Provisioning Tool其實也就是呼叫 sp_addsrvrolemember 預儲程序新增管理員,所以有沒有可能是因為使用用字串參數而導致大小寫比對有問題呢?試試看吧! 我試著把「要提供的使用者」欄位內的AD帳號改成正確的大小寫後,再試著加入一次,結果竟然就給我過了。呵,這.....真是嚴格啊。

看來我還是不要在Instance Level設定區分大小寫的定序選項,改在DB Level設定的話,或許這種怪力亂神的問題就比較能避免了吧~ =_=

2010年3月23日 星期二

意外解掉Oracle 10g統計值匯入緩慢的問題

還記得之前遇到的 Oracle 10g 統計值匯入緩慢的問題 ? 這個情況在我在測試區上了patch(10.2.0.4 64bit),情況已經大幅改善。

原本我是在fund 這邊export出一個 10.2.0.1版本的 dump檔,以適用於當時尚未升級的測試區DB。後來加了統計值,並且匯到64bit windows & 64-bit Oracle 10.2.0.1的環境時,整個作業需要12~13小時。但上禮拜我將這個測試區升級到 10.2.0.4後,意外發現整個作業只需要3小時又40分鍾左右,真的有给它驚訝到。

難道是版本搞的鬼???

真是意外的收穫。

2010年3月22日 星期一

解決SQL Server改變Hostname後無法安裝訂閱服務的問題

你遇過改掉SQL Server主機名稱後,就無法繼續安裝訂閱服務之類的事嗎?不管你怎樣試,都會得到一個無法辨識主機名稱之類的錯誤訊息,並且告訴你最好給定SQL Server一個hostname。但事實上這個錯誤來自於SQL Server內部仍記著原來舊的主機名稱,而與instancename不一致所引起的。至於為何如此,目前我尚未有結論,等精神好點再去追根究底一番;目前先請Google大神提供一個解決方案,原理是透過修改@@servername的方式,讓SQL Server重新取得修改過的hostname即可。

修正後請重新啟動SQL Server服務,之後就可以正確的安裝訂閱服務囉。

2010年3月21日 星期日

Oracle中一次編譯無效物件之方法

最近工作需要,翻出了用來重新編譯無效物件的 DBMS_UTILITY.compile_schema 套件(package),參數就只有一個schema name,只要指定好後就可以輕鬆寫意的完成無效物件的編譯。相關的文章則可參考 Recompiling Invalid Schema Objects 。但好巧不巧,前幾天在升級完 Oracle 後,意外的發現到用來更新 Dictionary 的指令檔尾註解中(D:\oracle\product\10.2.0\db_1\RDBMS\ADMIN\catupgrd.sql),會強烈的建議我們利用 utlrp.sql 指令檔案將無效的物件再recompile 一次,以避免當使用到無效的物件時,Oracle自動先行編譯而產生的不必要的延遲(latency)。

該指令檔 Utlsp.sql 位於 D:\oracle\product\10.2.0\db_1\RDBMS\ADMIN\utlrp.sql,指令只有短短個幾行,主要就是利用 utl_recomp.recomp_parallel(threads) 自動判斷主機上有幾個CPU後,以平行方式進行compile的作業。很簡單也很好用。如果你已經懶到指定schema name的話,那這個就絕對是你的最佳懶人包。不過,使用前請先用sqlplus / as sysdba的方式登入後再執行。

p.s 後話,在Recompiling Invalid Schema Object 文章末端,也提到了utlsp.sql的用法,而現在我才發現..@@ 。這證明我文章沒讀透啊......

2010年3月1日 星期一

從VBA到T-SQL

Database Journal 今天分享了一篇 T-SQL Equivalents for Microsoft Access VBA Functions這篇文章的重點在於提醒那些與MS Access共舞許久,Access SQL程式中也到處使用自訂或內建VBA函式的人,若日後有需要再度將這些焚膏繼晷的工作移轉到SQL上的時候,可就別夢想有什麼拖拖拉拉的工具可以直接幫你移轉了。更別提微軟提供的Upsize Wizard 根本就懶得幫你自動轉換那些內嵌在Aceess SQL中的VBA 函式。你得自己把所有內建的函式用T-SQL全部寫一次。

文章裡列出了T-SQL與VBA對應的函式表,以方便實作T-SQL時轉譯用。對應的函式名稱大體上是大同小異的,應該不會造成太大的困擾才對。還不錯的小品,分享囉...


2010年2月11日 星期四

Orakill也有失靈的時候

如果您遇到Orakill也砍不掉的Session,那建議你查閱一下Alert.log裡是否有以下的訊息:

orakill: ssthreadkill(tid=1988) unable to get the thread list mutex: err=0

如果有,那恭喜諸公,您又中Bug了。查詢過 Metalink後,確認為 Bug. 5753801,需要上10.2.0.3 Patch 21 or later後才能解決。

Oracle 10g 統計值匯入緩慢的問題

每天我都會以Data Pump的方式Export 一個約30G左右的 dump檔,然後再以Import (Content = ALL)到另外一台等級相同的的主機上;但令人訝異的,Schema的建立、Data 的 Import、以及Index的建立也僅大約花了三小時,而統計值的匯入卻足足花了8小時左右。查閱了metalink後,有個 Bug 5076838: IMPDP TAKES OVER 16 HOURS TO IMPORT TABLE_STATISTICS 指出了相同的問題,並且要到11g才會修正。Oracle對此也無workaround 解法。根據其他人的測試,主要原因可能是出在於 remap_* 的參數使然,只要target 的tablespace或schema不一樣,就會造成long~~long~~long~的等待。 對此,11g似乎才是磐涅之道。

就測試目的來說,remap_*是很常見的需求,正式區的資料也因此可restore到各式schema或tablespace內,以供各種測試目的專案執行。這種基本功都沒做好,真會讓人傷心的又訝異低。

SPOOL小技巧--砍掉欄位跟完成的提示訊息

工作上常透過SPOOL指令產出SQL指令批次檔,但如果只是簡單的將SPOOL ON/OFF的指令開關話,輸出的指令通常會有你不想要的額外訊息;舉例來說,

SPOOL D:\Autojob\Restore\ALTER.TXT;
select 'ALTER SYSTEM KILL SESSION' || ' ''' || sid || ',' || serial# || ''';' from v$session where username='SCHEMA_USER';
SPOOL OFF;
@D:\Autojob\Restore\ALTER.TXT;
exit;

ALERT.TXT通常會長成這樣:

'ALTERSYSTEMKILLSESSION'||''''||SID||','||SERIAL#||''';'
--------------------------------------------------------------------------------
ALTER SYSTEM KILL SESSION '521,415';
ALTER SYSTEM KILL SESSION '522,2428';
ALTER SYSTEM KILL SESSION '529,4564';
ALTER SYSTEM KILL SESSION '531,8493';
ALTER SYSTEM KILL SESSION '534,8057';
ALTER SYSTEM KILL SESSION '545,3068';
ALTER SYSTEM KILL SESSION '550,2691';

已選取 7 個資料列.

檔案內容裡我們就只要中間的 alter 指令,但檔案頭尾的訊息卻最好給我眼不見為淨。要避免產出頭尾的訊息,再另外加上 SET HEADING OFF 以及 SET FEED OFF 兩個指令即可:

SPOOL D:\Autojob\Restore\ALTER.TXT;
SET HEADING OFF;
SET FEED OFF;
SELECT 'ALTER SYSTEM KILL SESSION' || ' ''' || sid || ',' || serial# || ''';' from v$session where username='SCHEMA_USER';
SPOOL OFF;
@D:\Autojob\Restore\ALTER.TXT;
exit;

產出的結果就會得到漂亮的純指令內容了:

ALTER SYSTEM KILL SESSION '521,415';
ALTER SYSTEM KILL SESSION '522,2428';
ALTER SYSTEM KILL SESSION '529,4564';
ALTER SYSTEM KILL SESSION '531,8493';
ALTER SYSTEM KILL SESSION '534,8057';
ALTER SYSTEM KILL SESSION '545,3068';
ALTER SYSTEM KILL SESSION '550,2691';