最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

PostgreSQL使用COPY協(xié)議高效批量數(shù)據(jù)寫入的實(shí)戰(zhàn)指南

 更新時(shí)間:2025年11月20日 09:45:40   作者:ezreal_pan  
這篇文章主要介紹了PostgreSQL的COPY協(xié)議,這是一種高效批量數(shù)據(jù)導(dǎo)入導(dǎo)出的二進(jìn)制協(xié)議,適用于需要高效寫入大量數(shù)據(jù)的場(chǎng)景,COPY協(xié)議通過(guò)流式處理、事務(wù)安全和無(wú)參數(shù)限制等優(yōu)勢(shì),顯著提升了數(shù)據(jù)寫入性能,并結(jié)合事務(wù)管理保證了數(shù)據(jù)一致性,需要的朋友可以參考下

問(wèn)題背景

在開發(fā)過(guò)程中,我們經(jīng)常會(huì)遇到需要批量寫入大量數(shù)據(jù)到 PostgreSQL 數(shù)據(jù)庫(kù)的場(chǎng)景。當(dāng)使用傳統(tǒng)的參數(shù)化插入語(yǔ)句時(shí),可能會(huì)遇到如下錯(cuò)誤:

pq: got 86575 parameters but PostgreSQL only supports 65535 parameters

這是因?yàn)?PostgreSQL 對(duì)單個(gè)查詢的參數(shù)數(shù)量有限制(通常為 65535)。傳統(tǒng)的解決方案是進(jìn)行數(shù)據(jù)分片,分批寫入數(shù)據(jù)庫(kù)。但這種方法存在以下問(wèn)題:

  • 需要手動(dòng)管理分片邏輯
  • 多次數(shù)據(jù)庫(kù)往返,網(wǎng)絡(luò)開銷大
  • 事務(wù)管理復(fù)雜
  • 性能不夠理想

COPY 協(xié)議解決方案

COPY 協(xié)議簡(jiǎn)介

PostgreSQL 的 COPY 協(xié)議是專門為高效批量數(shù)據(jù)操作設(shè)計(jì)的二進(jìn)制協(xié)議,具有以下優(yōu)勢(shì):

  1. 高性能:避免了 SQL 解析開銷,直接使用二進(jìn)制格式傳輸數(shù)據(jù)
  2. 低內(nèi)存占用:流式處理,不需要在內(nèi)存中構(gòu)建龐大的 SQL 語(yǔ)句
  3. 事務(wù)安全:可以在事務(wù)中執(zhí)行,保證數(shù)據(jù)一致性
  4. 無(wú)參數(shù)限制:不受 PostgreSQL 參數(shù)數(shù)量限制

二進(jìn)制協(xié)議原理

COPY 協(xié)議使用 PostgreSQL 的前后端協(xié)議進(jìn)行數(shù)據(jù)傳輸,其工作流程如下:

  1. 啟動(dòng) COPY 模式:客戶端發(fā)送 COPY FROM STDIN 命令
  2. 數(shù)據(jù)傳輸:使用二進(jìn)制格式按行發(fā)送數(shù)據(jù)
  3. 結(jié)束傳輸:發(fā)送特定的結(jié)束標(biāo)記
  4. 確認(rèn)完成:服務(wù)器返回處理結(jié)果

二進(jìn)制格式避免了文本解析的開銷,直接使用網(wǎng)絡(luò)字節(jié)序傳輸數(shù)據(jù),大大提高了傳輸效率。

實(shí)戰(zhàn)實(shí)現(xiàn)

依賴庫(kù)

import (
    "github.com/lib/pq"
    "gorm.io/gorm"
)

核心實(shí)現(xiàn)代碼

// BatchCreate 批量創(chuàng)建消息接收者記錄 - 使用 COPY 協(xié)議
func (r *receiverRepo) BatchCreate(ctx context.Context, db *gorm.DB, data []*define.WecomMsgReceiver) (rowsAffected int64, err error) {
    db = r.WithTrace(ctx, db)
    db = db.Table(r.TableName())

    if len(data) == 0 {
        return 0, nil
    }

    // 過(guò)濾掉 nil 的數(shù)據(jù)
    validData := make([]*define.WecomMsgReceiver, 0, len(data))
    for _, item := range data {
        if item != nil {
            validData = append(validData, item)
        }
    }
    if len(validData) == 0 {
        return 0, nil
    }

    // 獲取底層 sql.DB
    sqlDB := db.DB()
    
    // 開始事務(wù)
    tx, err := sqlDB.BeginTx(ctx, nil)
    if err != nil {
        return 0, fmt.Errorf("開始事務(wù)失?。?+v", err)
    }
    defer func() {
        if err != nil {
            tx.Rollback()
        }
    }()

    // 創(chuàng)建 COPY writer
    stmt, err := tx.Prepare(pq.CopyIn(r.TableName(), "send_log_id", "user_id", "status", "created_at", "updated_at"))
    if err != nil {
        return 0, fmt.Errorf("準(zhǔn)備 COPY 語(yǔ)句失?。?+v", err)
    }
    defer stmt.Close()

    // 批量寫入數(shù)據(jù)
    for _, item := range validData {
        _, err = stmt.Exec(item.SendLogID, item.UserID, item.Status, item.CreatedAt, item.UpdatedAt)
        if err != nil {
            return 0, fmt.Errorf("寫入數(shù)據(jù)失?。?+v", err)
        }
    }

    // 執(zhí)行 COPY
    _, err := stmt.Exec()
    if err != nil {
        return 0, fmt.Errorf("執(zhí)行 COPY 失?。?+v", err)
    }

    // 提交事務(wù)
    if err = tx.Commit(); err != nil {
        return 0, fmt.Errorf("提交事務(wù)失?。?+v", err)
    }
    
    rowsAffected = int64(len(validData))
    return rowsAffected, nil
}

代碼說(shuō)明

  1. 數(shù)據(jù)驗(yàn)證:首先過(guò)濾掉 nil 數(shù)據(jù),確保數(shù)據(jù)有效性 
  2. 事務(wù)管理:使用事務(wù)確保數(shù)據(jù)一致性,出錯(cuò)時(shí)自動(dòng)回滾
  3. COPY 準(zhǔn)備:通過(guò) pq.CopyIn 準(zhǔn)備 COPY 語(yǔ)句,指定表名和列名
  4. 批量寫入:遍歷數(shù)據(jù)并執(zhí)行 Exec,但此時(shí)數(shù)據(jù)還在客戶端緩沖區(qū)
  5. 最終執(zhí)行:調(diào)用 stmt.Exec() 真正將數(shù)據(jù)發(fā)送到服務(wù)器
  6. 事務(wù)提交:提交事務(wù),完成批量寫入

完整測(cè)試用例

// 設(shè)置測(cè)試數(shù)據(jù)庫(kù)
func setupTestDB() (*gorm.DB, error) {
    ctx := context.Background()
    postgres, err := infrastructure.DialPostgres(ctx, infrastructure.PostgresConfig{
        Host:     "host",
        Port:     5432,
        Username: "postgres",
        Password: "xxxxx",
        Database: "xxxxx",
    })
    if err != nil {
        return nil, err
    }

    return postgres, nil
}

func setupLogger() factory.LogFactory {
    logger, _ := factory.NewJsonFactory(factory.NewLevel("info"), factory.NewZapOption(factory.AddCallerSkip(0)))
    return logger
}

func TestReceiverRepo_BatchCreate(t *testing.T) {
    db, err := setupTestDB()
    require.NoError(t, err)
    defer db.Close()

    // 創(chuàng)建日志工廠
    logger := setupLogger()

    // 創(chuàng)建 repository 實(shí)例
    repo := NewReceiverRepository(db, logger)

    // 準(zhǔn)備測(cè)試數(shù)據(jù) - 20000 條記錄,使用負(fù)的 send_log_id 避免污染數(shù)據(jù)
    testData := make([]*define.WecomMsgReceiver, 0, 20000)
    now := time.Now()
    negativeSendLogID := int64(-100000) // 使用負(fù)的 send_log_id

    for i := 0; i < 20000; i++ {
        testData = append(testData, &define.WecomMsgReceiver{
            SendLogID: negativeSendLogID,
            UserID:    "test_user_" + fmt.Sprint(i),
            Status:    1,
            CreatedAt: now,
            UpdatedAt: now,
        })
    }

    ctx := context.Background()

    // 執(zhí)行批量插入
    rowsAffected, err := repo.BatchCreate(ctx, db, testData)

    // 驗(yàn)證結(jié)果
    assert.NoError(t, err)
    assert.Equal(t, int64(20000), rowsAffected)

    // 驗(yàn)證數(shù)據(jù)是否正確插入
    var count int64
    query := "SELECT COUNT(*) FROM wecom_msg_receiver WHERE send_log_id < 0 AND send_log_id >= ?"
    err = db.Raw(query, negativeSendLogID).Count(&count).Error
    assert.NoError(t, err)
    assert.Equal(t, int64(20000), count)

    // 清理測(cè)試數(shù)據(jù)
    deleteQuery := "DELETE FROM wecom_msg_receiver WHERE send_log_id < 0 AND send_log_id >= ?"
    result := db.Exec(deleteQuery, negativeSendLogID)
    assert.NoError(t, result.Error)
    assert.Equal(t, int64(20000), result.RowsAffected)

    // 驗(yàn)證清理是否成功
    err = db.Raw(query, negativeSendLogID).Count(&count).Error
    assert.NoError(t, err)
    assert.Equal(t, int64(0), count)
}

性能對(duì)比

在實(shí)際測(cè)試中,COPY 協(xié)議相比傳統(tǒng)分批插入有顯著性能提升:

方案20000 條數(shù)據(jù)耗時(shí)內(nèi)存占用網(wǎng)絡(luò)請(qǐng)求次數(shù)
傳統(tǒng)分批插入~15 秒多次
COPY 協(xié)議~2 秒1 次

注意事項(xiàng)

  1. 錯(cuò)誤處理:COPY 協(xié)議中某行數(shù)據(jù)錯(cuò)誤可能導(dǎo)致整個(gè)批量操作失敗
  2. 數(shù)據(jù)類型:確保 Go 數(shù)據(jù)類型與 PostgreSQL 列類型匹配
  3. 連接池:長(zhǎng)時(shí)間運(yùn)行的 COPY 操作會(huì)占用數(shù)據(jù)庫(kù)連接
  4. 超時(shí)設(shè)置:對(duì)于大數(shù)據(jù)量,需要適當(dāng)調(diào)整上下文超時(shí)時(shí)間

總結(jié)

通過(guò)使用 PostgreSQL 的 COPY 協(xié)議,我們成功解決了批量寫入時(shí)的參數(shù)數(shù)量限制問(wèn)題,同時(shí)大幅提升了性能。這種方法特別適合數(shù)據(jù)遷移、日志批量處理等需要高效寫入大量數(shù)據(jù)的場(chǎng)景。

COPY協(xié)議結(jié)合事務(wù)管理,既保證了數(shù)據(jù)一致性,又能提供了接近原生的寫入性能,是PostgreSQL批量數(shù)據(jù)操作的優(yōu)選方案。

以上就是PostgreSQL使用COPY協(xié)議高效批量數(shù)據(jù)寫入的實(shí)戰(zhàn)指南的詳細(xì)內(nèi)容,更多關(guān)于PostgreSQL COPY批量數(shù)據(jù)寫入的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • postgresql synchronous_commit參數(shù)的用法介紹

    postgresql synchronous_commit參數(shù)的用法介紹

    這篇文章主要介紹了postgresql synchronous_commit參數(shù)的用法介紹,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2020-12-12
  • PGSQL 實(shí)現(xiàn)把字符串轉(zhuǎn)換成double類型(to_number())

    PGSQL 實(shí)現(xiàn)把字符串轉(zhuǎn)換成double類型(to_number())

    這篇文章主要介紹了PGSQL 實(shí)現(xiàn)把字符串轉(zhuǎn)換成double類型(to_number()),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2020-12-12
  • PostgreSql中ALL與ANY區(qū)別小結(jié)

    PostgreSql中ALL與ANY區(qū)別小結(jié)

    ALL和ANY是PostgreSQL中用于與子查詢結(jié)果進(jìn)行比較的強(qiáng)大操作符,本文主要介紹了PostgreSql中ALL與ANY區(qū)別,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2025-11-11
  • PostgresSQL安裝教程及初始使用操作方法

    PostgresSQL安裝教程及初始使用操作方法

    這篇文章介紹了如何在Linux系統(tǒng)上安裝和配置PostgreSQL數(shù)據(jù)庫(kù),詳細(xì)介紹了PostgreSQL的數(shù)據(jù)類型,并解釋了如何使用序列來(lái)標(biāo)識(shí)字段的自增長(zhǎng),感興趣的朋友跟隨小編一起看看吧
    2026-01-01
  • postgresql 實(shí)現(xiàn)取出分組中最大的幾條數(shù)據(jù)

    postgresql 實(shí)現(xiàn)取出分組中最大的幾條數(shù)據(jù)

    這篇文章主要介紹了postgresql 實(shí)現(xiàn)取出分組中最大的幾條數(shù)據(jù),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2021-01-01
  • Postgres bytea類型 轉(zhuǎn)換及查看操作

    Postgres bytea類型 轉(zhuǎn)換及查看操作

    這篇文章主要介紹了Postgres bytea類型 轉(zhuǎn)換及查看操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2020-12-12
  • PostgreSQL日期時(shí)間字段類型選擇指南

    PostgreSQL日期時(shí)間字段類型選擇指南

    這段文章詳細(xì)介紹了在PostgreSQL中選擇合適的日期時(shí)間數(shù)據(jù)類型的方法,特別推薦使用timestampwithouttimezone類型來(lái)存儲(chǔ)精確到微秒的日期和時(shí)間,文章對(duì)比了多種日期時(shí)間數(shù)據(jù)類型的特點(diǎn)和適用場(chǎng)景,并并并強(qiáng)調(diào)了避免使用varchar等存儲(chǔ)日期時(shí)間的重要性
    2026-06-06
  • postgresql 實(shí)現(xiàn)啟動(dòng)、狀態(tài)查看、關(guān)閉

    postgresql 實(shí)現(xiàn)啟動(dòng)、狀態(tài)查看、關(guān)閉

    這篇文章主要介紹了postgresql 實(shí)現(xiàn)啟動(dòng)、狀態(tài)查看、關(guān)閉的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2021-01-01
  • postgreSQL中的內(nèi)連接和外連接實(shí)現(xiàn)操作

    postgreSQL中的內(nèi)連接和外連接實(shí)現(xiàn)操作

    這篇文章主要介紹了postgreSQL中的內(nèi)連接和外連接實(shí)現(xiàn)操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2021-01-01
  • postgresql 查詢字符串中是否包含某字符的操作

    postgresql 查詢字符串中是否包含某字符的操作

    這篇文章主要介紹了postgresql 查詢字符串中是否包含某字符的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2021-01-01

最新評(píng)論

大埔县| 苏州市| 武冈市| 汉寿县| 明光市| 荆州市| 赤壁市| 崇左市| 阿拉善盟| 突泉县| 碌曲县| 祥云县| 中方县| 张家口市| 阜新市| 大石桥市| 册亨县| 白沙| 潮州市| 广南县| 黄平县| 平原县| 霍山县| 峨山| 新田县| 通渭县| 大同市| 黄冈市| 两当县| 奉化市| 延安市| 华安县| 外汇| 平原县| 临高县| 新竹市| 宜州市| 兰州市| 阿克陶县| 阳春市| 云安县|