├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── config.yml │ └── feature_request.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ ├── go-build-test.yml │ └── golangci-lint.yml ├── .golangci.yaml ├── LICENSE ├── README-zh.md ├── README.md ├── example ├── dump │ ├── dump.sql │ └── main.go └── source │ ├── dump.sql │ └── main.go ├── go.mod ├── go.sum ├── images └── logo.png ├── mysqldump.go ├── source.go ├── source_test.go └── util.go /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 报告缺陷 Bug Report 3 | about: 报告缺陷以帮助我们改进 Report defects to help us improve 4 | --- 5 | 6 | ### 描述问题 Describe the problem 7 | 8 | 12 | 13 | ````````markdown 14 | 如果是解析渲染问题的话请在此处贴入 Markdown 原文 15 | If it is a problem of parsing and rendering, please post the original Markdown here 16 | ```````` 17 | 18 | ### 期待的结果 Expected result 19 | 20 | 24 | 25 | ### 截屏或录像 Screenshot or video 26 | 27 | 34 | 35 | ### 版本环境 Version environment 36 | 37 | * 版本 Version: 38 | * 操作系统 Operating system: 39 | * 浏览器(如果使用)Browser (if used): 40 | 41 | ### 其他信息 Other information 42 | 43 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Jarvan的博客 4 | url: https://jarvans.com 5 | about: 大厂程序员, 公众号 硬核的Jarvan 6 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 请求新功能 Request new features 3 | about: 提出你期待的功能特性 Come up with the features you expect 4 | --- 5 | 6 | ### 你在什么场景下需要该功能? In what scenarios do you need this function? 7 | 8 | 12 | 13 | ### 描述最优的解决方案 Describe the optimal solution 14 | 15 | 19 | 20 | ### 描述候选解决方案 Describe the candidate solution 21 | 22 | 26 | 27 | ### 其他信息 Other information 28 | 29 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.github/workflows/go-build-test.yml: -------------------------------------------------------------------------------- 1 | name: Go package 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v3 11 | 12 | - name: Set up Go 13 | uses: actions/setup-go@v4 14 | with: 15 | go-version: '1.15' 16 | 17 | - name: Build 18 | run: go build -v ./... 19 | 20 | - name: Test 21 | run: go test -json > TestResults-${{ matrix.go-version }}.json 22 | - name: Upload Go test results 23 | uses: actions/upload-artifact@v3 24 | with: 25 | name: Go-results-${{ matrix.go-version }} 26 | path: TestResults-${{ matrix.go-version }}.json 27 | retention-days: 5 -------------------------------------------------------------------------------- /.github/workflows/golangci-lint.yml: -------------------------------------------------------------------------------- 1 | name: golangci-lint 2 | on: 3 | push: 4 | branches: 5 | - master 6 | - main 7 | pull_request: 8 | 9 | permissions: 10 | contents: read 11 | # Optional: allow read access to pull request. Use with `only-new-issues` option. 12 | # pull-requests: read 13 | 14 | jobs: 15 | golangci: 16 | name: lint 17 | runs-on: ubuntu-latest 18 | steps: 19 | - uses: actions/checkout@v3 20 | - uses: actions/setup-go@v4 21 | with: 22 | go-version: '1.21' 23 | cache: false 24 | - name: golangci-lint 25 | uses: golangci/golangci-lint-action@v3 26 | with: 27 | # Require: The version of golangci-lint to use. 28 | # When `install-mode` is `binary` (default) the value can be v1.2 or v1.2.3 or `latest` to use the latest version. 29 | # When `install-mode` is `goinstall` the value can be v1.2.3, `latest`, or the hash of a commit. 30 | version: v1.54 31 | 32 | # Optional: working directory, useful for monorepos 33 | # working-directory: somedir 34 | 35 | # Optional: golangci-lint command line arguments. 36 | # 37 | # Note: By default, the `.golangci.yml` file should be at the root of the repository. 38 | # The location of the configuration file can be changed by using `--config=` 39 | # args: --timeout=30m --config=/my/path/.golangci.yml --issues-exit-code=0 40 | 41 | # Optional: show only new issues if it's a pull request. The default value is `false`. 42 | # only-new-issues: true 43 | 44 | # Optional: if set to true, then all caching functionality will be completely disabled, 45 | # takes precedence over all other caching options. 46 | # skip-cache: true 47 | 48 | # Optional: if set to true, then the action won't cache or restore ~/go/pkg. 49 | # skip-pkg-cache: true 50 | 51 | # Optional: if set to true, then the action won't cache or restore ~/.cache/go-build. 52 | # skip-build-cache: true 53 | 54 | # Optional: The mode to install golangci-lint. It can be 'binary' or 'goinstall'. 55 | # install-mode: "goinstall" -------------------------------------------------------------------------------- /.golangci.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2020 Lingfei Kong . All rights reserved. 2 | # Use of this source code is governed by a MIT style 3 | # license that can be found in the LICENSE file. 4 | 5 | # This file contains all available configuration options 6 | # with their default values. 7 | 8 | # options for analysis running 9 | run: 10 | # default concurrency is a available CPU number 11 | concurrency: 4 12 | 13 | # timeout for analysis, e.g. 30s, 5m, default is 1m 14 | timeout: 5m 15 | 16 | # exit code when at least one issue was found, default is 1 17 | issues-exit-code: 1 18 | 19 | # include test files or not, default is true 20 | tests: true 21 | 22 | # list of build tags, all linters use it. Default is empty list. 23 | build-tags: 24 | - mytag 25 | 26 | # which dirs to skip: issues from them won't be reported; 27 | # can use regexp here: generated.*, regexp is applied on full path; 28 | # default value is empty list, but default dirs are skipped independently 29 | # from this option's value (see skip-dirs-use-default). 30 | # "/" will be replaced by current OS file path separator to properly work 31 | # on Windows. 32 | skip-dirs: 33 | - test # 测试目录 34 | - tools # 工具目录 35 | 36 | # default is true. Enables skipping of directories: 37 | # vendor$, third_party$, testdata$, examples$, Godeps$, builtin$ 38 | skip-dirs-use-default: true 39 | 40 | # which files to skip: they will be analyzed, but issues from them 41 | # won't be reported. Default value is empty list, but there is 42 | # no need to include all autogenerated files, we confidently recognize 43 | # autogenerated files. If it's not please let us know. 44 | # "/" will be replaced by current OS file path separator to properly work 45 | # on Windows. 46 | skip-files: 47 | - ".*\\.my\\.go$" 48 | - _test.go 49 | 50 | # by default isn't set. If set we pass it to "go list -mod={option}". From "go help modules": 51 | # If invoked with -mod=readonly, the go command is disallowed from the implicit 52 | # automatic updating of go.mod described above. Instead, it fails when any changes 53 | # to go.mod are needed. This setting is most useful to check that go.mod does 54 | # not need updates, such as in a continuous integration and testing system. 55 | # If invoked with -mod=vendor, the go command assumes that the vendor 56 | # directory holds the correct copies of dependencies and ignores 57 | # the dependency descriptions in go.mod. 58 | #modules-download-mode: release|readonly|vendor 59 | 60 | # Allow multiple parallel golangci-lint instances running. 61 | # If false (default) - golangci-lint acquires file lock on start. 62 | allow-parallel-runners: true 63 | 64 | 65 | # output configuration options 66 | output: 67 | # colored-line-number|line-number|json|tab|checkstyle|code-climate, default is "colored-line-number" 68 | format: colored-line-number 69 | 70 | # print lines of code with issue, default is true 71 | print-issued-lines: true 72 | 73 | # print linter name in the end of issue text, default is true 74 | print-linter-name: true 75 | 76 | # make issues output unique by line, default is true 77 | uniq-by-line: true 78 | 79 | # add a prefix to the output file references; default is no prefix 80 | path-prefix: "" 81 | 82 | # sorts results by: filepath, line and column 83 | sort-results: true 84 | 85 | # all available settings of specific linters 86 | linters-settings: 87 | gocyclo: 88 | # Minimal code complexity to report. 89 | # Default: 30 (but we recommend 10-20) 90 | min-complexity: 20 91 | linters: 92 | # please, do not use `enable-all`: it's deprecated and will be removed soon. 93 | # inverted configuration with `enable-all` and `disable` is not scalable during updates of golangci-lint 94 | # enable-all: true 95 | disable-all: true 96 | enable: 97 | - typecheck # 类型检查 98 | - bodyclose # body是否关闭 99 | - durationcheck # duration相乘检查 100 | - errcheck # 错误检查 101 | - exportloopref # 循环指针检查 102 | # - gofmt # gofmt 103 | # - gofumpt # gofumpt 104 | # - goimports # 导包顺序检查 105 | # - gosec # go安全检查 106 | - gosimple # 简化代码检查 107 | - govet 108 | - ineffassign # 变量是否未被使用 109 | - makezero # make非0长度切片 110 | - nilerr 111 | #- prealloc # 切片预分配 112 | #- revive 冗余代码检查 113 | - staticcheck # 静态检查 114 | - unparam # 无用的参数 115 | - unused # 变量是否使用 116 | - errchkjson # json err是否处理 117 | - gocyclo # 圈复杂度检查 118 | fast: false 119 | 120 | issues: 121 | 122 | # Show only new issues created after git revision `REV` 123 | # new-from-rev: REV 124 | 125 | # Show only new issues created in git patch with set file path. 126 | #new-from-patch: path/to/patch/file 127 | 128 | # Fix found issues (if it's supported by the linter) 129 | fix: false 130 | 131 | 132 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023-present Dengjiawen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README-zh.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |
4 |

5 | 6 | 7 | 8 |

9 |

10 | 11 | ## mysqldump 12 | 13 | [English](./README.md) 14 | 15 | golang 实现的零依赖、支持所有类型、高性能、并发 mysqldump 工具。 16 | 17 | 18 | ## Features 19 | 20 | * 自定义 Writer: 如本地文件、多文件储存、远程服务器、云存储等。(默认控制台输出)。 21 | * 支持所有 MYSQL 数据类型. 22 | * 支持 INSERT Merge, 大幅提升数据恢复性能 23 | 24 | ## QuickStart 25 | 26 | ### Create Table and Insert Test Data 27 | 28 | ```sql 29 | DROP TABLE IF EXISTS `test`; 30 | 31 | CREATE TABLE `test` ( 32 | `id` bigint unsigned NOT NULL AUTO_INCREMENT, 33 | `char_col` char(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL, 34 | `varchar_col` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL, 35 | `binary_col` binary(10) DEFAULT NULL, 36 | `varbinary_col` varbinary(255) DEFAULT NULL, 37 | `tinyblob_col` tinyblob, 38 | `tinytext_col` tinytext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci, 39 | `text_col` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci, 40 | `blob_col` blob, 41 | `mediumtext_col` mediumtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci, 42 | `mediumblob_col` mediumblob, 43 | `longtext_col` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci, 44 | `longblob_col` longblob, 45 | `enum_col` enum('value1','value2','value3') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL, 46 | `set_col` set('value1','value2','value3') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL, 47 | `bit_col` bit(8) DEFAULT NULL, 48 | `tinyint_col` tinyint NOT NULL DEFAULT '0', 49 | `bool_col` tinyint(1) NOT NULL DEFAULT '0', 50 | `boolean_col` tinyint(1) NOT NULL DEFAULT '0', 51 | `smallint_col` smallint NOT NULL DEFAULT '0', 52 | `mediumint_col` mediumint NOT NULL DEFAULT '0', 53 | `int_col` int NOT NULL DEFAULT '0', 54 | `integer_col` int NOT NULL DEFAULT '0', 55 | `bigint_col` bigint NOT NULL DEFAULT '0', 56 | `float_col` float(8,2) NOT NULL DEFAULT '0.00', 57 | `double_col` double(8,2) NOT NULL DEFAULT '0.00', 58 | `decimal_col` decimal(10,2) NOT NULL DEFAULT '0.00', 59 | `dec_col` decimal(10,2) NOT NULL DEFAULT '0.00', 60 | `date_col` date DEFAULT NULL, 61 | `datetime_col` datetime DEFAULT NULL, 62 | `timestamp_col` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, 63 | `time_col` time DEFAULT NULL, 64 | `year_col` year DEFAULT NULL, 65 | PRIMARY KEY (`id`) 66 | ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; 67 | 68 | INSERT INTO `test` VALUES (1,'abc','def',0x61626300000000000000,0x646566,0x74696E79626C6F62,'Hello','World',0x776F726C64,'Medium Text',0x4D656469756D426C6F62,'Long Text',0x4C6F6E67426C6F62,'value2','value1,value3',0x66,-128,1,0,-32768,-8388608,-2147483648,-2147483648,-9223372036854775808,1234.56,1234.56,1234.56,1234.56,'2023-03-17','2023-03-17 10:00:00','2023-03-17 14:04:46','10:00:00',2023); 69 | 70 | ``` 71 | 72 | ### Dump SQL 73 | 74 | ```go 75 | import ( 76 | "os" 77 | 78 | "github.com/jarvanstack/mysqldump" 79 | ) 80 | 81 | func main() { 82 | 83 | dsn := "root:rootpasswd@tcp(localhost:3306)/dbname?charset=utf8mb4&parseTime=true&loc=Asia%2FShanghai" 84 | 85 | f, _ := os.Create("dump.sql") 86 | 87 | _ = mysqldump.Dump( 88 | dsn, // DSN 89 | mysqldump.WithDropTable(), // Option: Delete table before create (Default: Not delete table) 90 | mysqldump.WithData(), // Option: Dump Data (Default: Only dump table schema) 91 | mysqldump.WithTables("test"), // Option: Dump Tables (Default: All tables) 92 | mysqldump.WithWriter(f), // Option: Writer (Default: os.Stdout) 93 | ) 94 | } 95 | 96 | ``` 97 | 98 | ### Output File dump.sql 99 | 100 | ```sql 101 | -- ---------------------------- 102 | -- MySQL Database Dump 103 | -- Start Time: 2023-04-21 14:16:56 104 | -- ---------------------------- 105 | 106 | 107 | USE `dc3`; 108 | DROP TABLE IF EXISTS `test`; 109 | -- ---------------------------- 110 | -- Table structure for test 111 | -- ---------------------------- 112 | CREATE TABLE IF NOT EXISTS `test` ( 113 | `id` bigint unsigned NOT NULL AUTO_INCREMENT, 114 | `char_col` char(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL, 115 | `varchar_col` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL, 116 | `binary_col` binary(10) DEFAULT NULL, 117 | `varbinary_col` varbinary(255) DEFAULT NULL, 118 | `tinyblob_col` tinyblob, 119 | `tinytext_col` tinytext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci, 120 | `text_col` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci, 121 | `blob_col` blob, 122 | `mediumtext_col` mediumtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci, 123 | `mediumblob_col` mediumblob, 124 | `longtext_col` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci, 125 | `longblob_col` longblob, 126 | `enum_col` enum('value1','value2','value3') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL, 127 | `set_col` set('value1','value2','value3') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL, 128 | `bit_col` bit(8) DEFAULT NULL, 129 | `tinyint_col` tinyint NOT NULL DEFAULT '0', 130 | `bool_col` tinyint(1) NOT NULL DEFAULT '0', 131 | `boolean_col` tinyint(1) NOT NULL DEFAULT '0', 132 | `smallint_col` smallint NOT NULL DEFAULT '0', 133 | `mediumint_col` mediumint NOT NULL DEFAULT '0', 134 | `int_col` int NOT NULL DEFAULT '0', 135 | `integer_col` int NOT NULL DEFAULT '0', 136 | `bigint_col` bigint NOT NULL DEFAULT '0', 137 | `float_col` float(8,2) NOT NULL DEFAULT '0.00', 138 | `double_col` double(8,2) NOT NULL DEFAULT '0.00', 139 | `decimal_col` decimal(10,2) NOT NULL DEFAULT '0.00', 140 | `dec_col` decimal(10,2) NOT NULL DEFAULT '0.00', 141 | `date_col` date DEFAULT NULL, 142 | `datetime_col` datetime DEFAULT NULL, 143 | `timestamp_col` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, 144 | `time_col` time DEFAULT NULL, 145 | `year_col` year DEFAULT NULL, 146 | PRIMARY KEY (`id`) 147 | ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; 148 | 149 | 150 | 151 | -- ---------------------------- 152 | -- Records of test 153 | -- ---------------------------- 154 | INSERT INTO `test` VALUES (1,'abc','def',0x61626300000000000000,0x646566,0x74696E79626C6F62,'Hello','World',0x776F726C64,'Medium Text',0x4D656469756D426C6F62,'Long Text',0x4C6F6E67426C6F62,'value2','value1,value3',0x66,-128,1,0,-32768,-8388608,-2147483648,-2147483648,-9223372036854775808,1234.56,1234.56,1234.56,1234.56,'2023-03-17','2023-03-17 10:00:00','2023-03-17 14:04:46','10:00:00',2023); 155 | 156 | 157 | -- ---------------------------- 158 | -- Dumped by mysqldump2 159 | -- Cost Time: 5.81592ms 160 | -- ---------------------------- 161 | 162 | ``` 163 | 164 | ### Source SQL 165 | 166 | ```go 167 | import ( 168 | "os" 169 | 170 | "github.com/jarvanstack/mysqldump" 171 | ) 172 | 173 | func main() { 174 | 175 | dsn := "root:rootpasswd@tcp(localhost:3306)/dbname?charset=utf8mb4&parseTime=true&loc=Asia%2FShanghai" 176 | f, _ := os.Open("dump.sql") 177 | 178 | _ = mysqldump.Source( 179 | dsn, 180 | f, 181 | mysqldump.WithMergeInsert(1000), // Option: Merge insert 1000 (Default: Not merge insert) 182 | mysqldump.WithDebug(), // Option: Print execute sql (Default: Not print execute sql) 183 | ) 184 | } 185 | ``` 186 | 187 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |
4 |

5 | 6 | 7 | 8 |

9 |

10 | 11 | ## mysqldump 12 | 13 | [简体中文](README-zh.md) 14 | 15 | A zero-dependency,all data types are supported, high-performance, concurrent mysqldump tool implemented in golang. 16 | 17 | ## Features 18 | 19 | * Supports custom Writer: data can be written to any Writer, such as local files, multiple file storage, remote servers, cloud storage, etc. (default console output). 20 | * Supports all MySQL data types QuickStart. 21 | * Support Merge Insert Option in Source Greatly improve data recovery performance 22 | 23 | 24 | ## QuickStart 25 | 26 | ### Create Table and Insert Test Data 27 | 28 | ```sql 29 | DROP TABLE IF EXISTS `test`; 30 | 31 | CREATE TABLE `test` ( 32 | `id` bigint unsigned NOT NULL AUTO_INCREMENT, 33 | `char_col` char(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL, 34 | `varchar_col` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL, 35 | `binary_col` binary(10) DEFAULT NULL, 36 | `varbinary_col` varbinary(255) DEFAULT NULL, 37 | `tinyblob_col` tinyblob, 38 | `tinytext_col` tinytext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci, 39 | `text_col` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci, 40 | `blob_col` blob, 41 | `mediumtext_col` mediumtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci, 42 | `mediumblob_col` mediumblob, 43 | `longtext_col` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci, 44 | `longblob_col` longblob, 45 | `enum_col` enum('value1','value2','value3') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL, 46 | `set_col` set('value1','value2','value3') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL, 47 | `bit_col` bit(8) DEFAULT NULL, 48 | `tinyint_col` tinyint NOT NULL DEFAULT '0', 49 | `bool_col` tinyint(1) NOT NULL DEFAULT '0', 50 | `boolean_col` tinyint(1) NOT NULL DEFAULT '0', 51 | `smallint_col` smallint NOT NULL DEFAULT '0', 52 | `mediumint_col` mediumint NOT NULL DEFAULT '0', 53 | `int_col` int NOT NULL DEFAULT '0', 54 | `integer_col` int NOT NULL DEFAULT '0', 55 | `bigint_col` bigint NOT NULL DEFAULT '0', 56 | `float_col` float(8,2) NOT NULL DEFAULT '0.00', 57 | `double_col` double(8,2) NOT NULL DEFAULT '0.00', 58 | `decimal_col` decimal(10,2) NOT NULL DEFAULT '0.00', 59 | `dec_col` decimal(10,2) NOT NULL DEFAULT '0.00', 60 | `date_col` date DEFAULT NULL, 61 | `datetime_col` datetime DEFAULT NULL, 62 | `timestamp_col` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, 63 | `time_col` time DEFAULT NULL, 64 | `year_col` year DEFAULT NULL, 65 | PRIMARY KEY (`id`) 66 | ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; 67 | 68 | INSERT INTO `test` VALUES (1,'abc','def',0x61626300000000000000,0x646566,0x74696E79626C6F62,'Hello','World',0x776F726C64,'Medium Text',0x4D656469756D426C6F62,'Long Text',0x4C6F6E67426C6F62,'value2','value1,value3',0x66,-128,1,0,-32768,-8388608,-2147483648,-2147483648,-9223372036854775808,1234.56,1234.56,1234.56,1234.56,'2023-03-17','2023-03-17 10:00:00','2023-03-17 14:04:46','10:00:00',2023); 69 | 70 | ``` 71 | 72 | ### Dump SQL 73 | 74 | ```go 75 | import ( 76 | "os" 77 | 78 | "github.com/jarvanstack/mysqldump" 79 | ) 80 | 81 | func main() { 82 | 83 | dsn := "root:rootpasswd@tcp(localhost:3306)/dbname?charset=utf8mb4&parseTime=true&loc=Asia%2FShanghai" 84 | 85 | f, _ := os.Create("dump.sql") 86 | 87 | _ = mysqldump.Dump( 88 | dsn, // DSN 89 | mysqldump.WithDropTable(), // Option: Delete table before create (Default: Not delete table) 90 | mysqldump.WithData(), // Option: Dump Data (Default: Only dump table schema) 91 | mysqldump.WithTables("test"), // Option: Dump Tables (Default: All tables) 92 | mysqldump.WithWriter(f), // Option: Writer (Default: os.Stdout) 93 | ) 94 | } 95 | ``` 96 | 97 | ### Output File dump.sql 98 | 99 | ```sql 100 | -- ---------------------------- 101 | -- MySQL Database Dump 102 | -- Start Time: 2023-04-21 14:16:56 103 | -- ---------------------------- 104 | 105 | 106 | USE `dc3`; 107 | DROP TABLE IF EXISTS `test`; 108 | -- ---------------------------- 109 | -- Table structure for test 110 | -- ---------------------------- 111 | CREATE TABLE IF NOT EXISTS `test` ( 112 | `id` bigint unsigned NOT NULL AUTO_INCREMENT, 113 | `char_col` char(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL, 114 | `varchar_col` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL, 115 | `binary_col` binary(10) DEFAULT NULL, 116 | `varbinary_col` varbinary(255) DEFAULT NULL, 117 | `tinyblob_col` tinyblob, 118 | `tinytext_col` tinytext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci, 119 | `text_col` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci, 120 | `blob_col` blob, 121 | `mediumtext_col` mediumtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci, 122 | `mediumblob_col` mediumblob, 123 | `longtext_col` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci, 124 | `longblob_col` longblob, 125 | `enum_col` enum('value1','value2','value3') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL, 126 | `set_col` set('value1','value2','value3') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL, 127 | `bit_col` bit(8) DEFAULT NULL, 128 | `tinyint_col` tinyint NOT NULL DEFAULT '0', 129 | `bool_col` tinyint(1) NOT NULL DEFAULT '0', 130 | `boolean_col` tinyint(1) NOT NULL DEFAULT '0', 131 | `smallint_col` smallint NOT NULL DEFAULT '0', 132 | `mediumint_col` mediumint NOT NULL DEFAULT '0', 133 | `int_col` int NOT NULL DEFAULT '0', 134 | `integer_col` int NOT NULL DEFAULT '0', 135 | `bigint_col` bigint NOT NULL DEFAULT '0', 136 | `float_col` float(8,2) NOT NULL DEFAULT '0.00', 137 | `double_col` double(8,2) NOT NULL DEFAULT '0.00', 138 | `decimal_col` decimal(10,2) NOT NULL DEFAULT '0.00', 139 | `dec_col` decimal(10,2) NOT NULL DEFAULT '0.00', 140 | `date_col` date DEFAULT NULL, 141 | `datetime_col` datetime DEFAULT NULL, 142 | `timestamp_col` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, 143 | `time_col` time DEFAULT NULL, 144 | `year_col` year DEFAULT NULL, 145 | PRIMARY KEY (`id`) 146 | ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; 147 | 148 | 149 | 150 | -- ---------------------------- 151 | -- Records of test 152 | -- ---------------------------- 153 | INSERT INTO `test` VALUES (1,'abc','def',0x61626300000000000000,0x646566,0x74696E79626C6F62,'Hello','World',0x776F726C64,'Medium Text',0x4D656469756D426C6F62,'Long Text',0x4C6F6E67426C6F62,'value2','value1,value3',0x66,-128,1,0,-32768,-8388608,-2147483648,-2147483648,-9223372036854775808,1234.56,1234.56,1234.56,1234.56,'2023-03-17','2023-03-17 10:00:00','2023-03-17 14:04:46','10:00:00',2023); 154 | 155 | 156 | -- ---------------------------- 157 | -- Dumped by mysqldump2 158 | -- Cost Time: 5.81592ms 159 | -- ---------------------------- 160 | 161 | ``` 162 | 163 | ### Source SQL 164 | 165 | ```go 166 | import ( 167 | "os" 168 | 169 | "github.com/jarvanstack/mysqldump" 170 | ) 171 | 172 | func main() { 173 | 174 | dsn := "root:rootpasswd@tcp(localhost:3306)/dbname?charset=utf8mb4&parseTime=true&loc=Asia%2FShanghai" 175 | f, _ := os.Open("dump.sql") 176 | 177 | _ = mysqldump.Source( 178 | dsn, 179 | f, 180 | mysqldump.WithMergeInsert(1000), // Option: Merge insert 1000 (Default: Not merge insert) 181 | mysqldump.WithDebug(), // Option: Print execute sql (Default: Not print execute sql) 182 | ) 183 | } 184 | ``` 185 | 186 | -------------------------------------------------------------------------------- /example/dump/dump.sql: -------------------------------------------------------------------------------- 1 | -- ---------------------------- 2 | -- MySQL Database Dump 3 | -- Start Time: 2023-04-21 14:16:56 4 | -- ---------------------------- 5 | 6 | 7 | USE `dc3`; 8 | DROP TABLE IF EXISTS `test`; 9 | -- ---------------------------- 10 | -- Table structure for test 11 | -- ---------------------------- 12 | CREATE TABLE IF NOT EXISTS `test` ( 13 | `id` bigint unsigned NOT NULL AUTO_INCREMENT, 14 | `char_col` char(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL, 15 | `varchar_col` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL, 16 | `binary_col` binary(10) DEFAULT NULL, 17 | `varbinary_col` varbinary(255) DEFAULT NULL, 18 | `tinyblob_col` tinyblob, 19 | `tinytext_col` tinytext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci, 20 | `text_col` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci, 21 | `blob_col` blob, 22 | `mediumtext_col` mediumtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci, 23 | `mediumblob_col` mediumblob, 24 | `longtext_col` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci, 25 | `longblob_col` longblob, 26 | `enum_col` enum('value1','value2','value3') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL, 27 | `set_col` set('value1','value2','value3') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL, 28 | `bit_col` bit(8) DEFAULT NULL, 29 | `tinyint_col` tinyint NOT NULL DEFAULT '0', 30 | `bool_col` tinyint(1) NOT NULL DEFAULT '0', 31 | `boolean_col` tinyint(1) NOT NULL DEFAULT '0', 32 | `smallint_col` smallint NOT NULL DEFAULT '0', 33 | `mediumint_col` mediumint NOT NULL DEFAULT '0', 34 | `int_col` int NOT NULL DEFAULT '0', 35 | `integer_col` int NOT NULL DEFAULT '0', 36 | `bigint_col` bigint NOT NULL DEFAULT '0', 37 | `float_col` float(8,2) NOT NULL DEFAULT '0.00', 38 | `double_col` double(8,2) NOT NULL DEFAULT '0.00', 39 | `decimal_col` decimal(10,2) NOT NULL DEFAULT '0.00', 40 | `dec_col` decimal(10,2) NOT NULL DEFAULT '0.00', 41 | `date_col` date DEFAULT NULL, 42 | `datetime_col` datetime DEFAULT NULL, 43 | `timestamp_col` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, 44 | `time_col` time DEFAULT NULL, 45 | `year_col` year DEFAULT NULL, 46 | PRIMARY KEY (`id`) 47 | ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; 48 | 49 | 50 | 51 | -- ---------------------------- 52 | -- Records of test 53 | -- ---------------------------- 54 | INSERT INTO `test` VALUES (1,'abc','def',0x61626300000000000000,0x646566,0x74696E79626C6F62,'Hello','World',0x776F726C64,'Medium Text',0x4D656469756D426C6F62,'Long Text',0x4C6F6E67426C6F62,'value2','value1,value3',0x66,-128,1,0,-32768,-8388608,-2147483648,-2147483648,-9223372036854775808,1234.56,1234.56,1234.56,1234.56,'2023-03-17','2023-03-17 10:00:00','2023-03-17 14:04:46','10:00:00',2023); 55 | 56 | 57 | -- ---------------------------- 58 | -- Dumped by mysqldump2 59 | -- Cost Time: 5.81592ms 60 | -- ---------------------------- 61 | -------------------------------------------------------------------------------- /example/dump/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/jarvanstack/mysqldump" 7 | ) 8 | 9 | func main() { 10 | 11 | dsn := "root:rootpasswd@tcp(localhost:3306)/dbname?charset=utf8mb4&parseTime=true&loc=Asia%2FShanghai" 12 | 13 | f, _ := os.Create("dump.sql") 14 | 15 | _ = mysqldump.Dump( 16 | dsn, // DSN 17 | mysqldump.WithDropTable(), // Option: Delete table before create (Default: Not delete table) 18 | mysqldump.WithData(), // Option: Dump Data (Default: Only dump table schema) 19 | mysqldump.WithTables("test"), // Option: Dump Tables (Default: All tables) 20 | mysqldump.WithWriter(f), // Option: Writer (Default: os.Stdout) 21 | ) 22 | } 23 | -------------------------------------------------------------------------------- /example/source/dump.sql: -------------------------------------------------------------------------------- 1 | -- ---------------------------- 2 | -- MySQL Database Dump 3 | -- Start Time: 2023-04-21 14:04:27 4 | -- ---------------------------- 5 | 6 | 7 | USE `dc3`; 8 | DROP TABLE IF EXISTS `test`; 9 | -- ---------------------------- 10 | -- Table structure for test 11 | -- ---------------------------- 12 | CREATE TABLE IF NOT EXISTS `test` ( 13 | `id` bigint unsigned NOT NULL AUTO_INCREMENT, 14 | `char_col` char(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL, 15 | `varchar_col` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL, 16 | `binary_col` binary(10) DEFAULT NULL, 17 | `varbinary_col` varbinary(255) DEFAULT NULL, 18 | `tinyblob_col` tinyblob, 19 | `tinytext_col` tinytext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci, 20 | `text_col` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci, 21 | `blob_col` blob, 22 | `mediumtext_col` mediumtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci, 23 | `mediumblob_col` mediumblob, 24 | `longtext_col` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci, 25 | `longblob_col` longblob, 26 | `enum_col` enum('value1','value2','value3') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL, 27 | `set_col` set('value1','value2','value3') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL, 28 | `bit_col` bit(8) DEFAULT NULL, 29 | `tinyint_col` tinyint NOT NULL DEFAULT '0', 30 | `bool_col` tinyint(1) NOT NULL DEFAULT '0', 31 | `boolean_col` tinyint(1) NOT NULL DEFAULT '0', 32 | `smallint_col` smallint NOT NULL DEFAULT '0', 33 | `mediumint_col` mediumint NOT NULL DEFAULT '0', 34 | `int_col` int NOT NULL DEFAULT '0', 35 | `integer_col` int NOT NULL DEFAULT '0', 36 | `bigint_col` bigint NOT NULL DEFAULT '0', 37 | `float_col` float(8,2) NOT NULL DEFAULT '0.00', 38 | `double_col` double(8,2) NOT NULL DEFAULT '0.00', 39 | `decimal_col` decimal(10,2) NOT NULL DEFAULT '0.00', 40 | `dec_col` decimal(10,2) NOT NULL DEFAULT '0.00', 41 | `date_col` date DEFAULT NULL, 42 | `datetime_col` datetime DEFAULT NULL, 43 | `timestamp_col` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, 44 | `time_col` time DEFAULT NULL, 45 | `year_col` year DEFAULT NULL, 46 | PRIMARY KEY (`id`) 47 | ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; 48 | 49 | 50 | 51 | -- ---------------------------- 52 | -- Records of test 53 | -- ---------------------------- 54 | INSERT INTO `test` VALUES (1,'abc','def',0x61626300000000000000,0x646566,0x74696E79626C6F62,'Hello','World',0x776F726C64,'Medium Text',0x4D656469756D426C6F62,'Long Text',0x4C6F6E67426C6F62,'value2','value1,value3',0x66,-128,1,0,-32768,-8388608,-2147483648,-2147483648,-9223372036854775808,1234.56,1234.56,1234.56,1234.56,'2023-03-17','2023-03-17 10:00:00','2023-03-17 14:04:46','10:00:00',2023); 55 | 56 | 57 | -- ---------------------------- 58 | -- Dumped by mysqldump2 59 | -- Cost Time: 10.353526ms 60 | -- ---------------------------- 61 | -------------------------------------------------------------------------------- /example/source/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/jarvanstack/mysqldump" 7 | ) 8 | 9 | func main() { 10 | 11 | dns := "root:rootpasswd@tcp(localhost:3306)/dbname?charset=utf8mb4&parseTime=true&loc=Asia%2FShanghai" 12 | f, _ := os.Open("dump.sql") 13 | 14 | _ = mysqldump.Source( 15 | dns, 16 | f, 17 | mysqldump.WithMergeInsert(1000), // Option: Merge insert 1000 (Default: Not merge insert) 18 | mysqldump.WithDebug(), // Option: Print execute sql (Default: Not print execute sql) 19 | ) 20 | } 21 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/jarvanstack/mysqldump 2 | 3 | go 1.18 4 | 5 | require github.com/go-sql-driver/mysql v1.7.0 6 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc= 2 | github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= 3 | -------------------------------------------------------------------------------- /images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jarvanstack/mysqldump/8489f418ffe577e3a517ca17e9616879e41f65fa/images/logo.png -------------------------------------------------------------------------------- /mysqldump.go: -------------------------------------------------------------------------------- 1 | package mysqldump 2 | 3 | import ( 4 | "bufio" 5 | "database/sql" 6 | "fmt" 7 | "io" 8 | "log" 9 | "os" 10 | "strings" 11 | "time" 12 | 13 | _ "github.com/go-sql-driver/mysql" 14 | ) 15 | 16 | func init() { 17 | // 打印 日志 行数 18 | log.SetFlags(log.Lshortfile | log.LstdFlags) 19 | } 20 | 21 | type dumpOption struct { 22 | // 导出表数据 23 | isData bool 24 | 25 | // 导出指定表, 与 isAllTables 互斥, isAllTables 优先级高 26 | tables []string 27 | // 导出全部表 28 | isAllTable bool 29 | // 是否删除表 30 | isDropTable bool 31 | 32 | // writer 默认为 os.Stdout 33 | writer io.Writer 34 | } 35 | 36 | type DumpOption func(*dumpOption) 37 | 38 | // 删除表 39 | func WithDropTable() DumpOption { 40 | return func(option *dumpOption) { 41 | option.isDropTable = true 42 | } 43 | } 44 | 45 | // 导出表数据 46 | func WithData() DumpOption { 47 | return func(option *dumpOption) { 48 | option.isData = true 49 | } 50 | } 51 | 52 | // 导出指定表, 与 WithAllTables 互斥, WithAllTables 优先级高 53 | func WithTables(tables ...string) DumpOption { 54 | return func(option *dumpOption) { 55 | option.tables = tables 56 | } 57 | } 58 | 59 | // 导出全部表 60 | func WithAllTable() DumpOption { 61 | return func(option *dumpOption) { 62 | option.isAllTable = true 63 | } 64 | } 65 | 66 | // 导出到指定 writer 67 | func WithWriter(writer io.Writer) DumpOption { 68 | return func(option *dumpOption) { 69 | option.writer = writer 70 | } 71 | } 72 | 73 | func Dump(dsn string, opts ...DumpOption) error { 74 | // 打印开始 75 | start := time.Now() 76 | log.Printf("[info] [dump] start at %s\n", start.Format("2006-01-02 15:04:05")) 77 | // 打印结束 78 | defer func() { 79 | end := time.Now() 80 | log.Printf("[info] [dump] end at %s, cost %s\n", end.Format("2006-01-02 15:04:05"), end.Sub(start)) 81 | }() 82 | 83 | var err error 84 | 85 | var o dumpOption 86 | 87 | for _, opt := range opts { 88 | opt(&o) 89 | } 90 | 91 | if len(o.tables) == 0 { 92 | // 默认包含全部表 93 | o.isAllTable = true 94 | } 95 | 96 | if o.writer == nil { 97 | // 默认输出到 os.Stdout 98 | o.writer = os.Stdout 99 | } 100 | 101 | buf := bufio.NewWriter(o.writer) 102 | defer buf.Flush() 103 | 104 | // 打印 Header 105 | _, _ = buf.WriteString("-- ----------------------------\n") 106 | _, _ = buf.WriteString("-- MySQL Database Dump\n") 107 | _, _ = buf.WriteString("-- Start Time: " + start.Format("2006-01-02 15:04:05") + "\n") 108 | _, _ = buf.WriteString("-- ----------------------------\n") 109 | _, _ = buf.WriteString("\n\n") 110 | 111 | // 连接数据库 112 | db, err := sql.Open("mysql", dsn) 113 | if err != nil { 114 | log.Printf("[error] %v \n", err) 115 | return err 116 | } 117 | defer db.Close() 118 | 119 | // 1. 获取数据库 120 | dbName, err := GetDBNameFromDSN(dsn) 121 | if err != nil { 122 | log.Printf("[error] %v \n", err) 123 | return err 124 | } 125 | _, err = db.Exec(fmt.Sprintf("USE `%s`", dbName)) 126 | if err != nil { 127 | log.Printf("[error] %v \n", err) 128 | return err 129 | } 130 | 131 | // 2. 获取表 132 | var tables []string 133 | if o.isAllTable { 134 | tmp, err := getAllTables(db) 135 | if err != nil { 136 | log.Printf("[error] %v \n", err) 137 | return err 138 | } 139 | tables = tmp 140 | } else { 141 | tables = o.tables 142 | } 143 | 144 | // 3. 导出表 145 | for _, table := range tables { 146 | // 删除表 147 | if o.isDropTable { 148 | _, _ = buf.WriteString(fmt.Sprintf("DROP TABLE IF EXISTS `%s`;\n", table)) 149 | } 150 | 151 | // 导出表结构 152 | err = writeTableStruct(db, table, buf) 153 | if err != nil { 154 | log.Printf("[error] %v \n", err) 155 | return err 156 | } 157 | 158 | // 导出表数据 159 | if o.isData { 160 | err = writeTableData(db, table, buf) 161 | if err != nil { 162 | log.Printf("[error] %v \n", err) 163 | return err 164 | } 165 | } 166 | } 167 | 168 | // 导出每个表的结构和数据 169 | 170 | _, _ = buf.WriteString("-- ----------------------------\n") 171 | _, _ = buf.WriteString("-- Dumped by mysqldump\n") 172 | _, _ = buf.WriteString("-- Cost Time: " + time.Since(start).String() + "\n") 173 | _, _ = buf.WriteString("-- ----------------------------\n") 174 | buf.Flush() 175 | 176 | return nil 177 | } 178 | 179 | func getCreateTableSQL(db *sql.DB, table string) (string, error) { 180 | var createTableSQL string 181 | err := db.QueryRow(fmt.Sprintf("SHOW CREATE TABLE `%s`", table)).Scan(&table, &createTableSQL) 182 | if err != nil { 183 | return "", err 184 | } 185 | // IF NOT EXISTS 186 | createTableSQL = strings.Replace(createTableSQL, "CREATE TABLE", "CREATE TABLE IF NOT EXISTS", 1) 187 | return createTableSQL, nil 188 | } 189 | 190 | func getAllTables(db *sql.DB) ([]string, error) { 191 | var tables []string 192 | rows, err := db.Query("SHOW TABLES") 193 | if err != nil { 194 | return nil, err 195 | } 196 | defer rows.Close() 197 | 198 | for rows.Next() { 199 | var table string 200 | err = rows.Scan(&table) 201 | if err != nil { 202 | return nil, err 203 | } 204 | tables = append(tables, table) 205 | } 206 | return tables, nil 207 | } 208 | 209 | func writeTableStruct(db *sql.DB, table string, buf *bufio.Writer) error { 210 | // 导出表结构 211 | _, _ = buf.WriteString("-- ----------------------------\n") 212 | _, _ = buf.WriteString(fmt.Sprintf("-- Table structure for %s\n", table)) 213 | _, _ = buf.WriteString("-- ----------------------------\n") 214 | 215 | createTableSQL, err := getCreateTableSQL(db, table) 216 | if err != nil { 217 | log.Printf("[error] %v \n", err) 218 | return err 219 | } 220 | _, _ = buf.WriteString(createTableSQL) 221 | _, _ = buf.WriteString(";") 222 | 223 | _, _ = buf.WriteString("\n\n") 224 | _, _ = buf.WriteString("\n\n") 225 | return nil 226 | } 227 | 228 | // 禁止 golangci-lint 检查 229 | // nolint: gocyclo 230 | func writeTableData(db *sql.DB, table string, buf *bufio.Writer) error { 231 | 232 | // 导出表数据 233 | _, _ = buf.WriteString("-- ----------------------------\n") 234 | _, _ = buf.WriteString(fmt.Sprintf("-- Records of %s\n", table)) 235 | _, _ = buf.WriteString("-- ----------------------------\n") 236 | 237 | lineRows, err := db.Query(fmt.Sprintf("SELECT * FROM `%s`", table)) 238 | if err != nil { 239 | log.Printf("[error] %v \n", err) 240 | return err 241 | } 242 | defer lineRows.Close() 243 | 244 | var columns []string 245 | columns, err = lineRows.Columns() 246 | if err != nil { 247 | log.Printf("[error] %v \n", err) 248 | return err 249 | } 250 | columnTypes, err := lineRows.ColumnTypes() 251 | if err != nil { 252 | log.Printf("[error] %v \n", err) 253 | return err 254 | } 255 | 256 | var values [][]interface{} 257 | for lineRows.Next() { 258 | row := make([]interface{}, len(columns)) 259 | rowPointers := make([]interface{}, len(columns)) 260 | for i := range columns { 261 | rowPointers[i] = &row[i] 262 | } 263 | err = lineRows.Scan(rowPointers...) 264 | if err != nil { 265 | log.Printf("[error] %v \n", err) 266 | return err 267 | } 268 | values = append(values, row) 269 | } 270 | 271 | for _, row := range values { 272 | ssql := "INSERT INTO `" + table + "` VALUES (" 273 | 274 | for i, col := range row { 275 | if col == nil { 276 | ssql += "NULL" 277 | } else { 278 | Type := columnTypes[i].DatabaseTypeName() 279 | // 去除 UNSIGNED 和空格 280 | Type = strings.Replace(Type, "UNSIGNED", "", -1) 281 | Type = strings.Replace(Type, " ", "", -1) 282 | switch Type { 283 | case "TINYINT", "SMALLINT", "MEDIUMINT", "INT", "INTEGER", "BIGINT": 284 | if bs, ok := col.([]byte); ok { 285 | ssql += string(bs) 286 | } else { 287 | ssql += fmt.Sprintf("%d", col) 288 | } 289 | case "FLOAT", "DOUBLE": 290 | if bs, ok := col.([]byte); ok { 291 | ssql += string(bs) 292 | } else { 293 | ssql += fmt.Sprintf("%f", col) 294 | } 295 | case "DECIMAL", "DEC": 296 | ssql += fmt.Sprintf("%s", col) 297 | 298 | case "DATE": 299 | t, ok := col.(time.Time) 300 | if !ok { 301 | log.Println("DATE 类型转换错误") 302 | return err 303 | } 304 | ssql += fmt.Sprintf("'%s'", t.Format("2006-01-02")) 305 | case "DATETIME": 306 | t, ok := col.(time.Time) 307 | if !ok { 308 | log.Println("DATETIME 类型转换错误") 309 | return err 310 | } 311 | ssql += fmt.Sprintf("'%s'", t.Format("2006-01-02 15:04:05")) 312 | case "TIMESTAMP": 313 | t, ok := col.(time.Time) 314 | if !ok { 315 | log.Println("TIMESTAMP 类型转换错误") 316 | return err 317 | } 318 | ssql += fmt.Sprintf("'%s'", t.Format("2006-01-02 15:04:05")) 319 | case "TIME": 320 | t, ok := col.([]byte) 321 | if !ok { 322 | log.Println("TIME 类型转换错误") 323 | return err 324 | } 325 | ssql += fmt.Sprintf("'%s'", string(t)) 326 | case "YEAR": 327 | t, ok := col.([]byte) 328 | if !ok { 329 | log.Println("YEAR 类型转换错误") 330 | return err 331 | } 332 | ssql += string(t) 333 | case "CHAR", "VARCHAR", "TINYTEXT", "TEXT", "MEDIUMTEXT", "LONGTEXT": 334 | ssql += fmt.Sprintf("'%s'", strings.Replace(fmt.Sprintf("%s", col), "'", "''", -1)) 335 | case "BIT", "BINARY", "VARBINARY", "TINYBLOB", "BLOB", "MEDIUMBLOB", "LONGBLOB": 336 | ssql += fmt.Sprintf("0x%X", col) 337 | case "ENUM", "SET": 338 | ssql += fmt.Sprintf("'%s'", col) 339 | case "BOOL", "BOOLEAN": 340 | if col.(bool) { 341 | ssql += "true" 342 | } else { 343 | ssql += "false" 344 | } 345 | case "JSON": 346 | ssql += fmt.Sprintf("'%s'", col) 347 | default: 348 | // unsupported type 349 | log.Printf("unsupported type: %s", Type) 350 | return fmt.Errorf("unsupported type: %s", Type) 351 | } 352 | } 353 | if i < len(row)-1 { 354 | ssql += "," 355 | } 356 | } 357 | ssql += ");\n" 358 | _, _ = buf.WriteString(ssql) 359 | } 360 | 361 | _, _ = buf.WriteString("\n\n") 362 | return nil 363 | } 364 | -------------------------------------------------------------------------------- /source.go: -------------------------------------------------------------------------------- 1 | package mysqldump 2 | 3 | import ( 4 | "bufio" 5 | "database/sql" 6 | "errors" 7 | "fmt" 8 | "io" 9 | "log" 10 | "strings" 11 | "time" 12 | ) 13 | 14 | type sourceOption struct { 15 | dryRun bool 16 | mergeInsert int 17 | debug bool 18 | } 19 | type SourceOption func(*sourceOption) 20 | 21 | func WithDryRun() SourceOption { 22 | return func(o *sourceOption) { 23 | o.dryRun = true 24 | } 25 | } 26 | 27 | func WithMergeInsert(size int) SourceOption { 28 | return func(o *sourceOption) { 29 | o.mergeInsert = size 30 | } 31 | } 32 | 33 | // WithDebug 打印执行的 SQL 34 | func WithDebug() SourceOption { 35 | return func(o *sourceOption) { 36 | o.debug = true 37 | } 38 | } 39 | 40 | type dbWrapper struct { 41 | DB *sql.DB 42 | debug bool 43 | dryRun bool 44 | } 45 | 46 | func newDBWrapper(db *sql.DB, dryRun, debug bool) *dbWrapper { 47 | 48 | return &dbWrapper{ 49 | DB: db, 50 | dryRun: dryRun, 51 | debug: debug, 52 | } 53 | } 54 | 55 | func (db *dbWrapper) Exec(query string, args ...interface{}) (sql.Result, error) { 56 | if db.debug { 57 | log.Printf("[debug] [query]\n%s\n", query) 58 | } 59 | 60 | if db.dryRun { 61 | return nil, nil 62 | } 63 | return db.DB.Exec(query, args...) 64 | } 65 | 66 | // Source 加载 67 | // 禁止 golangci-lint 检查 68 | // nolint: gocyclo 69 | func Source(dsn string, reader io.Reader, opts ...SourceOption) error { 70 | // 打印开始 71 | start := time.Now() 72 | log.Printf("[info] [source] start at %s\n", start.Format("2006-01-02 15:04:05")) 73 | // 打印结束 74 | defer func() { 75 | end := time.Now() 76 | log.Printf("[info] [source] end at %s, cost %s\n", end.Format("2006-01-02 15:04:05"), end.Sub(start)) 77 | }() 78 | 79 | var err error 80 | var db *sql.DB 81 | var o sourceOption 82 | for _, opt := range opts { 83 | opt(&o) 84 | } 85 | 86 | dbName, err := GetDBNameFromDSN(dsn) 87 | if err != nil { 88 | log.Printf("[error] %v\n", err) 89 | return err 90 | } 91 | 92 | // Open database 93 | db, err = sql.Open("mysql", dsn) 94 | if err != nil { 95 | log.Printf("[error] %v\n", err) 96 | return err 97 | } 98 | defer db.Close() 99 | 100 | // DB Wrapper 101 | dbWrapper := newDBWrapper(db, o.dryRun, o.debug) 102 | 103 | // Use database 104 | _, err = dbWrapper.Exec(fmt.Sprintf("USE %s;", dbName)) 105 | if err != nil { 106 | log.Printf("[error] %v\n", err) 107 | return err 108 | } 109 | 110 | // 设置超时时间1小时 111 | db.SetConnMaxLifetime(3600) 112 | 113 | // 一句一句执行 114 | r := bufio.NewReader(reader) 115 | // 关闭事务 116 | _, err = dbWrapper.Exec("SET autocommit=0;") 117 | if err != nil { 118 | log.Printf("[error] %v\n", err) 119 | return err 120 | } 121 | 122 | for { 123 | line, err := r.ReadString(';') 124 | if err != nil { 125 | if err == io.EOF { 126 | break 127 | } 128 | log.Printf("[error] %v\n", err) 129 | return err 130 | } 131 | 132 | ssql := string(line) 133 | 134 | // 删除末尾的换行符 135 | ssql = trim(ssql) 136 | if err != nil { 137 | log.Printf("[error] [trim] %v\n", err) 138 | return err 139 | } 140 | 141 | // 如果 INSERT 开始, 并且 mergeInsert 为 true, 则合并 INSERT 142 | if o.mergeInsert > 1 && strings.HasPrefix(ssql, "INSERT INTO") { 143 | var insertSQLs []string 144 | insertSQLs = append(insertSQLs, ssql) 145 | for i := 0; i < o.mergeInsert-1; i++ { 146 | line, err := r.ReadString(';') 147 | if err != nil { 148 | if err == io.EOF { 149 | break 150 | } 151 | log.Printf("[error] %v\n", err) 152 | return err 153 | } 154 | 155 | ssql2 := string(line) 156 | ssql2 = trim(ssql2) 157 | if err != nil { 158 | log.Printf("[error] [trim] %v\n", err) 159 | return err 160 | } 161 | if strings.HasPrefix(ssql2, "INSERT INTO") { 162 | insertSQLs = append(insertSQLs, ssql2) 163 | continue 164 | } 165 | 166 | break 167 | } 168 | // 合并 INSERT 169 | ssql, err = mergeInsert(insertSQLs) 170 | if err != nil { 171 | log.Printf("[error] [mergeInsert] %v\n", err) 172 | return err 173 | } 174 | } 175 | 176 | _, err = dbWrapper.Exec(ssql) 177 | if err != nil { 178 | log.Printf("[error] %v\n", err) 179 | return err 180 | } 181 | } 182 | 183 | // 提交事务 184 | _, err = dbWrapper.Exec("COMMIT;") 185 | if err != nil { 186 | log.Printf("[error] %v\n", err) 187 | return err 188 | } 189 | 190 | // 开启事务 191 | _, err = dbWrapper.Exec("SET autocommit=1;") 192 | if err != nil { 193 | log.Printf("[error] %v\n", err) 194 | return err 195 | } 196 | 197 | return nil 198 | } 199 | 200 | /* 201 | 将多个 INSERT 合并为一个 202 | 输入: 203 | INSERT INTO `test` VALUES (1, 'a'); 204 | INSERT INTO `test` VALUES (2, 'b'); 205 | 输出 206 | INSERT INTO `test` VALUES (1, 'a'), (2, 'b'); 207 | */ 208 | func mergeInsert(insertSQLs []string) (string, error) { 209 | if len(insertSQLs) == 0 { 210 | return "", errors.New("no input provided") 211 | } 212 | builder := strings.Builder{} 213 | sql1 := insertSQLs[0] 214 | sql1 = strings.TrimSuffix(sql1, ";") 215 | builder.WriteString(sql1) 216 | for i, insertSQL := range insertSQLs[1:] { 217 | if i < len(insertSQLs)-1 { 218 | builder.WriteString(",") 219 | } 220 | 221 | valuesIdx := strings.Index(insertSQL, "VALUES") 222 | if valuesIdx == -1 { 223 | return "", errors.New("invalid SQL: missing VALUES keyword") 224 | } 225 | sqln := insertSQL[valuesIdx:] 226 | sqln = strings.TrimPrefix(sqln, "VALUES") 227 | sqln = strings.TrimSuffix(sqln, ";") 228 | builder.WriteString(sqln) 229 | 230 | } 231 | builder.WriteString(";") 232 | 233 | return builder.String(), nil 234 | } 235 | 236 | // 删除空白符换行符和注释 237 | func trim(s string) string { 238 | s = strings.TrimLeft(s, "\n") 239 | s = strings.TrimSpace(s) 240 | return s 241 | } 242 | -------------------------------------------------------------------------------- /source_test.go: -------------------------------------------------------------------------------- 1 | package mysqldump 2 | 3 | import "testing" 4 | 5 | func Test_mergeInsert(t *testing.T) { 6 | type args struct { 7 | insertSQLs []string 8 | } 9 | tests := []struct { 10 | name string 11 | args args 12 | want string 13 | wantErr bool 14 | }{ 15 | { 16 | args: args{ 17 | insertSQLs: []string{ 18 | "INSERT INTO `test` VALUES (1, 'a');", 19 | "INSERT INTO `test` VALUES (2, 'b');", 20 | }, 21 | }, 22 | want: "INSERT INTO `test` VALUES (1, 'a'), (2, 'b');", 23 | wantErr: false, 24 | }, 25 | } 26 | for _, tt := range tests { 27 | t.Run(tt.name, func(t *testing.T) { 28 | got, err := mergeInsert(tt.args.insertSQLs) 29 | if (err != nil) != tt.wantErr { 30 | t.Errorf("mergeInsert() error = %v, wantErr %v", err, tt.wantErr) 31 | return 32 | } 33 | if got != tt.want { 34 | t.Errorf("mergeInsert() = %v, want %v", got, tt.want) 35 | } 36 | }) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /util.go: -------------------------------------------------------------------------------- 1 | package mysqldump 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | //从dsn中提取出数据库名称,并将其作为结果返回。 9 | //如果无法解析出数据库名称,将返回一个错误。 10 | 11 | func GetDBNameFromDSN(dsn string) (string, error) { 12 | ss1 := strings.Split(dsn, "/") 13 | if len(ss1) == 2 { 14 | ss2 := strings.Split(ss1[1], "?") 15 | if len(ss2) == 2 { 16 | return ss2[0], nil 17 | } 18 | } 19 | 20 | return "", fmt.Errorf("dsn error: %s", dsn) 21 | } 22 | --------------------------------------------------------------------------------