登录
首页 >  Golang >  Go问答

go 语言中mysql操作200万数据时应该如何写?

来源:SegmentFault

时间:2023-01-10 17:15:41 106浏览 收藏

亲爱的编程学习爱好者,如果你点开了这篇文章,说明你对《go 语言中mysql操作200万数据时应该如何写?》很感兴趣。本篇文章就来给大家详细解析一下,主要介绍一下MySQL、go,希望所有认真读完的童鞋们,都有实质性的提高。

问题内容

在写一个将 discuzx 的 post 数据的 bbcode 转换成 html 的功能。
但是转换过程中,越到后面,越卡了。
本来想学学并发的,无奈不会啊。。。太菜了。
注释掉的是想要弄的。。。

求个解决方案。代码如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
package main
 
import (
    "database/sql"
    "fmt"
    "github.com/frustra/bbcode"
    _ "github.com/go-sql-driver/mysql"
    //"time"
    //"runtime"
    "strconv"
)
 
const (
    XnFromPost = "bbs_post"
    XnTopost   = "bbs_post"
    DxFromPost = "pre_forum_post"
)
 
type Hostinfo struct {
    DBUser,
    DBPassword,
    DBname,
    DBHost,
    DBPort,
    DBChar string
}
 
type post struct {
    pid     int
    message string
}
 
var OldDB, NewDB *sql.DB
 
/**
  设置数据库,并连接数据库
*/
func SetDB() (olddata, newdata *sql.DB) {
    var localhost, remotehost Hostinfo
 
    localhost = Hostinfo{
        "root",
        "123456",
        "xiuno",
        "",
        "3306",
        "utf8",
    }
 
    remotehost = Hostinfo{
        "root",
        "123456",
        "gxvtc",
        "",
        "3306",
        "utf8",
    }
 
    olddata, _ = connMysql(&localhost)
    newdata, _ = connMysql(&remotehost)
 
    return olddata, newdata
}
 
/**
连接数据库
*/
func connMysql(host *Hostinfo) (*sql.DB, error) {
    //db, err := sql.Open("mysql", "root:123456@/"+dbs+"?charset=utf8")
    if host.DBHost != "" {
        host.DBHost = "tcp(" + host.DBHost + ":" + host.DBPort + ")"
        fmt.Println(host.DBHost)
    }
 
    db, err := sql.Open("mysql", host.DBUser+":"+host.DBPassword+"@"+host.DBHost+"/"+host.DBname+"?charset="+host.DBChar)
    return db, err
}
 
func main() {
    OldDB, NewDB = SetDB()
    defer OldDB.Close()
    defer NewDB.Close()
    updatePost()
}
 
func updatePost() {
    const total = 100
 
    //selectPost := "SELECT pid,message FROM " + XnFromPost + " ORDER BY pid ASC LIMIT 10"
    selectPost := "SELECT pid,message FROM " + DxFromPost + " ORDER BY pid ASC "
    /*
        Data, err := OldDB.Query(selectPost)
        if err != nil {
            fmt.Println(err.Error())
        }*/
    fmt.Println(selectPost)
 
    updatePost := "UPDATE " + XnTopost + " SET message = ? WHERE pid = ? limit 1"
    fmt.Println(updatePost)
    stmt, err := OldDB.Prepare(updatePost)
    //insertPost := "INSERT INTO " + XnTopost + " (message,pid) VALUES (?,?)"
    //fmt.Println(insertPost)
    //stmt, err := OldDB.Prepare(insertPost)
 
    if err != nil {
        fmt.Println(err.Error())
    }
 
    //mypost := make(map[int]post)
 
    i := 0
    for {
        tmpNum := i * total
 
        i++
        tmpSQL := selectPost + " LIMIT " + strconv.Itoa(tmpNum) + "," + strconv.Itoa(total)
        fmt.Println(tmpSQL)
        Data, err := NewDB.Query(tmpSQL)
        //Data, err := NewDB.Query(tmpSQL)
        if err != nil {
            fmt.Println(err.Error())
        }
 
        tag := false
        //使用并发的方式
        for Data.Next() {
            tag = true
            var pid int
            var msg string
            Data.Scan(&pid, &msg)
            fmt.Println(pid)
 
            //bbcode转码html
            compiler := bbcode.NewCompiler(true, true)
            msg = compiler.Compile(msg)
            //mypost[pid] = post{pid, msg}
            _, err = stmt.Exec(msg, pid)
            if err != nil {
                fmt.Println("pid: ", pid, err.Error())
            }
        }
 
        if tag == false {
            fmt.Println("没有数据了...")
            break
        }
 
    }
 
    /*
       //直接查找并更新
           for Data.Next() {
               var pid int
               var msg string
               Data.Scan(&pid, &msg)
 
               //bbcode转码html
               compiler := bbcode.NewCompiler(true, true)
               msg = compiler.Compile(msg)
 
               mypost[pid] = post{pid, msg}
               //fmt.Println(mypost)
               fmt.Println(pid)
 
               _, err = stmt.Exec(msg, pid)
               if err != nil {
                   fmt.Println("pid: ", pid, err.Error())
               }
           }
    */
 
    /*
            //使用并发的方式
            for Data.Next() {
                var pid int
                var msg string
                Data.Scan(&pid, &msg)
 
                //bbcode转码html
                compiler := bbcode.NewCompiler(true, true)
                msg = compiler.Compile(msg)
                mypost[pid] = post{pid, msg}
            }
 
        runtime.GOMAXPROCS(runtime.NumCPU())
        c := make(chan post)
        for _, v := range mypost {
            go ShowMsg(c, v)
            /*
                go func() {
                    fmt.Println(v.pid)
                    c

正确答案

由于我实在没有耐心看完你的代码,而且很多东西都还要你自己去实践, 这里我简单说一下我的方案, 希望能给你指明方向;

1
当然我也可能有理解错误,如果谁发现错误,请及时告知
.

先说明几个比较重要的概念和前提知识:

  • golang的协程对应的是实际操作系统线程, routine之间是独立的

  • 使用go关键字调用了一个函数后, 只是新建了另外一个新的线程

  • 指定了cpu number之后go才真正使用多核cpu, goroutine才真正被并行调度;

  • 使用无缓冲channel就像是握手,必须同时有写有读才能继续执行

  • channel使用完毕之后需要进行关闭, 否则会有内存泄露

  • channel可以试用range进行读取

好了, 上面比较枯燥, 下面是我按照我的代码开始给你讲解清楚你需要注意的问题:

1
2
3
4
5
/* 正确创建合适数量的goroutine */
 
#建议创建routine的方式类似这样, 根据CPU数量创建合适的线程
runtime.GOMAXPROCS(runtime.NumCPU())
for i:=0; i

好, 创建了合适的线程之后,我们开始分析这些线程的执行, 上面for循环的代码所在的线程(routine)我们可以称之为main routine, 因为它是第一个routine; go后面调用的匿名函数则并发执行了内部逻辑,

1
main routine现在不知道哪个routine先执行完毕, 也就更不知道它们合适全部执行完毕
, 作为一个合格的程序员,必须考虑清楚他们之间的先后顺序;

1
2
3
4
5
import "sync"
/* 正确管理routine的生命周期, 防止死锁 */
var barrier sync.WaitGroup /* 使用WaitGroup管理自己启动的多个线程 */
 
for i:=0; i

OK了, 现在routine全部在掌握之中, 下面就可以考虑如何让它们替我工作了!而我们要做的就是利用channel传递数据进去, 让routine通过routine接收数据并并发处理.

1
2
3
4
5
6
7
#假想的任务: 多线程执行1100得加法!
var barrier sync.WaitGroup /* 使用WaitGroup管理自己启动的多个线程 */
cin  := make(chan int)
cout := make(chan int)
#因为cin无缓冲的(有缓冲也不可能容纳全部100个数据), 所以增加新的routine往其中写数据, 保证main routine不卡死, 可以调用起来后面的for循环, 消耗数据.
go func() {
    for i:=1; i

注: 以上代码并未严格测试, 只是为了说明我的方案, 呵呵好累. 给个辛苦分..

理论要掌握,实操不能落!以上关于《go 语言中mysql操作200万数据时应该如何写?》的详细介绍,大家都掌握了吧!如果想要继续提升自己的能力,那么就来关注golang学习网公众号吧!

声明:本文转载于:SegmentFault 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>