資料庫SqlServer遷移PostgreSql實踐

来源:https://www.cnblogs.com/88223100/archive/2023/02/08/Database-SqlServer-Migration-PostgreSql-Practice.html
-Advertisement-
Play Games

SqlServer屬於商業資料庫,不可能像Mysql等資料庫一樣,去解析相關的資料庫binlog,從而實現增量數據的回放,結合應用屬性,最後確定採用離線遷移方式,從SqlServer中將表數據全部讀出,然後將數據寫入到pg中,採用此種方案的弊病就是程式端需停止寫入(應用可將部分數據緩存到本地),等待... ...


背景
公司某內部系統屬於商業產品,資料庫性能已出現明顯問題,服務經常卡死,員工經常反饋數據無法查詢或不能及時查詢,該系統所使用的資料庫為SqlServer,SqlServer資料庫屬於商業資料庫,依賴廠商的維護,且維護成本高,效率低,且存在版權等問題,考慮將該系統的資料庫,遷移至PostGresql資料庫,屬於BSD的開源資料庫,不存在版本問題,公司也有部分系統採用pg,維護成本也將大大減低。

遷移原理
SqlServer屬於商業資料庫,不可能像Mysql等資料庫一樣,去解析相關的資料庫binlog,從而實現增量數據的回放,結合應用屬性,最後確定採用離線遷移方式,從SqlServer中將表數據全部讀出,然後將數據寫入到pg中,採用此種方案的弊病就是程式端需停止寫入(應用可將部分數據緩存到本地),等待資料庫遷移完成後,程式端再遷移至PostGresql,遷移方法如下: 

表結構遷移原理
表結構主要包含欄位,索引,主鍵,外鍵等信息組成,主要採用開源工具sqlserver2pg進行表結構的轉換

表結構轉換
從SqlServer中讀寫表結構的欄位信息,並對欄位類型進行轉換,轉換核心代碼如下

sub convert_type
{
    my ($sqlstype, $sqlqual, $colname, $tablename, $typname, $schemaname) =
        @_;
    my $rettype;
    if (defined $types{$sqlstype})
    {
        if ((defined $sqlqual and defined($unqual{$types{$sqlstype}}))
            or not defined $sqlqual)
        {
           # This is one of the few types that have to be unqualified (binary type)
           $rettype = $types{$sqlstype};

           # but we might add a check constraint for binary data
           if ($sqlstype =~ 'binary' and defined $sqlqual) {
              print STDERR "convert_type: $sqlstype, $sqlqual, $colname\n";
              my $constraint;
              $constraint->{TYPE}  = 'CHECK_BINARY_LENGTH';
              $constraint->{TABLE} = $tablename;
              $constraint->{TEXT}  = "octet_length(" . format_identifier($colname) . ") <= $sqlqual";
              push @{$objects->{SCHEMAS}->{$schemaname}->{TABLES}->{$tablename}
                        ->{CONSTRAINTS}}, ($constraint);
           }
        }
        elsif (defined $sqlqual)
        {
            $rettype = ($types{$sqlstype} . "($sqlqual)");
        }
    }

    # A few special cases
    elsif ($sqlstype eq 'bit' and not defined $sqlqual)
    {
        $rettype = "boolean";
    }
    elsif ($sqlstype eq 'ntext' and not defined $sqlqual)
    {
        $rettype = "text";
    }

外鍵,索引,唯一鍵轉換

主要是從sqlserver導出的表結構數據中,對相關的索引,外鍵等語句進行轉換,轉換核心代碼如下

while (my ($schema, $refschema) = each %{$objects->{SCHEMAS}})
    {
        # Indexes
        # They don't have a schema qualifier. But their table has, and they are in the same schema as their table
        foreach my $table (sort keys %{$refschema->{TABLES}})
        {
            foreach
                my $index (
                   sort keys %{$refschema->{TABLES}->{$table}->{INDEXES}})
            {
                my $index_created = 0;
                my $idxref =
                    $refschema->{TABLES}->{$table}->{INDEXES}->{$index};
                my $idxdef .= "";
                if ($idxref->{DISABLE})
                {
                    $idxdef .= "-- ";
                }
                $idxdef .= "CREATE";
                if ($idxref->{UNIQUE})
                {
                    $idxdef .= " UNIQUE";
                }
                if (defined $idxref->{COLS})
                {
                   $idxdef .= " INDEX " . format_identifier($index) . " ON " . format_identifier($schema) . '.' . format_identifier($table) . " ("
                     . join(",", map{format_identifier_cols_index($_)} @{$idxref->{COLS}}) . ")";

                   if (defined $idxref->{INCLUDE}) {
                      $idxdef .= " INCLUDE (" .
                         join(",", map{format_identifier_cols_index($_)} @{$idxref->{INCLUDE}})
                         . ")";
                   }

                   if (not defined $idxref->{WHERE} and not defined $idxref->{DISABLE}) {
                      $idxdef .= ";\n";
                      print AFTER $idxdef;
                      # the possible comment would go to after file
                      $index_created = 1;
                   }

數據類型轉換原理

數據類型轉換

函數類型轉換

存儲過程

視圖部分需手動改造

遷移方法

表結構轉換

./sqlserver2pgsql.pl -b before.sql -a after.sql -u unsure.sql -k /opt/data_migration/data-integration/  -sd test -sh 127.0.0.1 -sp 1433 -su user_only -sw 122132321 -pd  test -ph 192.168.1.1 -pp 15432 -pu postgres -pw 12345678  -pi 8  -po 8 -f script.sql

表結構導入pg

/usr/local/pgsql1201/bin/psql -h 127.0.0.1 -U postgres -p 15432 <before.sql

數據遷移

cd /opt/data_migration/data-integration/
sh kitchen.sh -file=migration.kjb  -level=detailed >migration.log

數據比對

#!/usr/bin/env python
# -*- coding: utf-8 -*-


"""
@author:jacker_zhou
@create_time: 2017-04-07
@overview: mssql pg 
"""

__author__ = 'jacker_zhou'
__version__ = '0.1'

import psycopg2,pymssql 
import types
import time
TableSpace='public.' 
class CompareDataBase(): 
    def __init__(self): 
        
        self.pgcnotallow=psycopg2.connect(database="test",host="127.0.0.1",port=15432,user="postgres",password="test") 
        
        self.mscnotallow=pymssql.connect(host="192.168.1.1",user="test",password="test",database="test") 
    def commit(self): 
        self.pgconn.commit() 
    def close(self): 
        self.pgconn.close() 
        self.msconn.close() 
    def rollback(self): 
        self.pgconn.rollback() 
    def exesyncdb(self): 
        mscursor=self.msconn.cursor() 
        sql=("SELECT COUNT(COLUMNNAME) AS CT,TABLENAME FROM (SELECT A.NAME AS COLUMNNAME,B.NAME AS TABLENAME FROM SYSCOLUMNS A RIGHT JOIN SYSOBJECTS B ON A.ID=B.ID WHERE B.TYPE='U' AND B.NAME NOT IN ('dtproperties','0626')) A GROUP BY TABLENAME ") 
        mscursor.execute(sql) 
        table=mscursor.fetchall()
        print ("total table %d"%len(table))
        if(table is None or len(table)<=0): 
            return 
        else: 
            for row in table: 
                self.executeTable(row[1],row[0]) 
                print ("%s is execute success"%row[1])
    def comparedb(self): 
        mscursor=self.msconn.cursor() 
        sql=("SELECT COUNT(COLUMNNAME) AS CT,TABLENAME FROM (SELECT A.NAME AS COLUMNNAME,B.NAME AS TABLENAME FROM SYSCOLUMNS A RIGHT JOIN SYSOBJECTS B ON A.ID=B.ID WHERE B.TYPE='U' AND B.NAME NOT IN ('dtproperties','0626')) A GROUP BY TABLENAME ") 
        mscursor.execute(sql) 
        table=mscursor.fetchall()
        print ("total table %d"%len(table))
        if(table is None or len(table)<=0): 
            return 
        else: 
            for row in table: 
                self.compareTable(row[1]) 
    def executeTable(self,tablename,count): 
        #print tablename 
        sql1="SELECT * FROM %s"%tablename 
        print (sql1)
        mscursor=self.msconn.cursor() 
        mscursor.execute(sql1) 
        table=mscursor.fetchall()
        if(table is None or len(table)<=0): 
            mscursor.close() 
            return 
        lst_result=self.initColumn(table)
        #print "column" 
        mscursor.close() 
        print ("execute sync  %s data to postgresql"%tablename)
        sql2=self.initPgSql(tablename,count)
        pgcursor=self.pgconn.cursor() 
        pgcursor.executemany(sql2,lst_result) 
        pgcursor.close()
    def compareTable(self,tablename): 
        #print tablename 
        sql1="SELECT count(*) FROM %s"%tablename 
        mscursor=self.msconn.cursor() 
        mscursor.execute(sql1) 
        ms_res=mscursor.fetchall() 
        mscursor.close() 
        pgcursor=self.pgconn.cursor()
        pgcursor.execute(sql1) 
        pg_res=pgcursor.fetchall() 
        pgcursor.close()
        res =""
        if ms_res[0][0] == pg_res[0][0]:
            res ="ok"
        else:
            res = "fail"

        print ("execute compare  table  %s data  postgresql: %s  mssql:%s result: %s"%(tablename,pg_res[0][0],ms_res[0][0],res))
    if __name__=="__main__": 
        sdb= CompareDataBase()
        start_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
        print ("task start time %s"%start_time)
        try: 
            sdb.comparedb()
        except Exception as e: 
            print (e) 
            sdb.rollback() 
        else: 
            sdb.commit() 
        sdb.close() 
        end_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
        print ("task end time %s"%end_time)
        print ("ok........" )

參考

​ ​https://github.com/dalibo/sqlserver2pgsql​

本文來自博客園,作者:古道輕風,轉載請註明原文鏈接:https://www.cnblogs.com/88223100/p/Database-SqlServer-Migration-PostgreSql-Practice.html


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 之前一致以為索引就是簡單的在原表的數據上加了一些編號,讓查詢更加快捷。後來發現裡面還有更深的知識。 索引用於快速查找具有特定列值的行。如果沒有索引,MySQL 必須從第一行開始,然後通讀整個表以找到相關行。表數據越多,成本就越高。如果表有相關列的索引,MySQL 可以快速確定要在數據文件中間查找的位 ...
  • 摘要:在業務功能實現時,經常會用到視圖簡化查詢SQL。但有時候會因為視圖降低查詢效率,本文主要分析在業務需求滿足的情況下,將有效的過濾條件傳遞到基表,減少運算過程中資料庫需要處理的數據量,提升SQL執行效率。 本文分享自華為雲社區《GaussDB(DWS)業務視圖優化-過濾條件傳遞》,作者:衛小毛 ...
  • GreatSQL社區原創內容未經授權不得隨意使用,轉載請聯繫小編並註明來源。 GreatSQL是MySQL的國產分支版本,使用上與MySQL一致。 作者: wuyy 文章來源:GreatSQL社區原創 目錄 MySQL的sp運行SQL語句兩個步驟介紹 代碼跟蹤 知識應用 總結 一、MySQL的sp運 ...
  • group系列 之前覺得這裡簡單不需要再進行總結了。後來發現還是需要總結鞏固一下,還是有一些方法之類的之前未使用過。這裡來重新整理,記錄一下。 group by 將表中的數據根據某個條件進行分組。 比如下麵這張表: 如果我們按照班級進行分組 那麼數據將會是這樣的: 通過mysql執行表格數據進行校驗 ...
  • 摘要:對雲端用戶而言,業務價值發現是最重要的,華為MRS支持LakeFormation後,成功降低了數據應用的成本,幫助客戶落地“存”與“算”的管理,加快推進了數智融合進程,更大程度地釋放業務數據價值。 本文分享自華為雲社區《華為雲MRS支持lakeformation能力,打造一站式湖倉,釋放數據價 ...
  • 數棧作為袋鼠雲打造的一站式數據開發與治理平臺,從2016年發佈第⼀個版本開始,就始終堅持著以技術為核⼼、安全為底線、提效為⽬標、中台為戰略的思想,堅定不移地⾛國產化信創路線,不斷推進產品功能迭代、技術創新、服務細化和性能升級。 伴隨業務的⻜速發展,數棧設計團隊也啟動了針對數棧產品的體驗升級計劃,從開 ...
  • 輕便式Redis Monitor是面向研發人員的圖形可視化監控工具,借鑒了LEPUS(天兔)監控平臺以及redis-cli info命令輸出的監控指標項,去掉了一些不必要、看不懂的監控項,目前採集了資料庫連接數、QPS、記憶體使用率統計和同步複製延遲時長。 Redis Monitor可以監控單機... ...
  • GreatSQL社區原創內容未經授權不得隨意使用,轉載請聯繫小編並註明來源。 GreatSQL是MySQL的國產分支版本,使用上與MySQL一致。 作者: 葉金榮 文章來源:GreatSQL社區原創 MySQL 8.0版本計劃 MySQL 8.0開始採用快速迭代開發模式,基本上是每隔3個月就發佈一個 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...