面试题答案
一键面试开启事务
在Go语言中使用database/sql
包开启事务的示例代码如下:
package main
import (
"database/sql"
"fmt"
_ "github.com/go-sql-driver/mysql"
)
func main() {
db, err := sql.Open("mysql", "user:password@tcp(127.0.0.1:3306)/test")
if err != nil {
panic(err.Error())
}
defer db.Close()
// 开启事务
tx, err := db.Begin()
if err != nil {
fmt.Println("开启事务失败:", err)
return
}
// 执行事务中的SQL操作
_, err = tx.Exec("INSERT INTO your_table (column1, column2) VALUES (?,?)", "value1", "value2")
if err != nil {
// 回滚事务
tx.Rollback()
fmt.Println("执行SQL失败,事务回滚:", err)
return
}
// 提交事务
err = tx.Commit()
if err != nil {
fmt.Println("提交事务失败:", err)
return
}
fmt.Println("事务执行成功")
}
并发请求下保证事务一致性和数据完整性
- 使用锁机制:
- 在数据库层面,可以使用行级锁或表级锁。例如,在MySQL中,可以使用
SELECT... FOR UPDATE
语句来获取行级锁。 - 示例代码:
- 在数据库层面,可以使用行级锁或表级锁。例如,在MySQL中,可以使用
tx, err := db.Begin()
if err != nil {
fmt.Println("开启事务失败:", err)
return
}
// 获取行级锁
rows, err := tx.Query("SELECT column1 FROM your_table WHERE id =? FOR UPDATE", 1)
if err != nil {
tx.Rollback()
fmt.Println("获取行锁失败,事务回滚:", err)
return
}
defer rows.Close()
// 后续执行相关操作
- 乐观锁:
- 乐观锁假设并发操作不会经常发生冲突。通常在表中添加一个版本号字段(如
version
)。 - 在更新数据时,检查版本号是否匹配,匹配则更新并增加版本号,不匹配则重试操作。
- 示例代码:
- 乐观锁假设并发操作不会经常发生冲突。通常在表中添加一个版本号字段(如
tx, err := db.Begin()
if err != nil {
fmt.Println("开启事务失败:", err)
return
}
// 获取当前版本号
var version int
err = tx.QueryRow("SELECT version FROM your_table WHERE id =?", 1).Scan(&version)
if err != nil {
tx.Rollback()
fmt.Println("获取版本号失败,事务回滚:", err)
return
}
// 更新数据,并检查版本号
result, err := tx.Exec("UPDATE your_table SET column1 =?, version = version + 1 WHERE id =? AND version =?", "new_value", 1, version)
if err != nil {
tx.Rollback()
fmt.Println("更新数据失败,事务回滚:", err)
return
}
rowsAffected, err := result.RowsAffected()
if err != nil {
tx.Rollback()
fmt.Println("获取影响行数失败,事务回滚:", err)
return
}
if rowsAffected == 0 {
// 版本号不匹配,重试
// 这里只是简单示意,实际应用中可能需要更复杂的重试逻辑
fmt.Println("版本号不匹配,重试操作")
tx.Rollback()
return
}
err = tx.Commit()
if err != nil {
fmt.Println("提交事务失败:", err)
return
}
可能遇到的并发问题及解决办法
- 脏读:
- 问题描述:一个事务读取了另一个未提交事务修改的数据。
- 解决办法:通过设置数据库的隔离级别为
READ COMMITTED
或更高(如REPEATABLE READ
、SERIALIZABLE
)。在Go中,可以通过在连接数据库时设置相关参数来指定隔离级别。例如,在MySQL中,在连接字符串中添加tx_isolation=READ-COMMITTED
。
- 不可重复读:
- 问题描述:在同一个事务内,多次读取同一数据,得到不同的结果,因为另一个事务在期间修改并提交了该数据。
- 解决办法:设置数据库隔离级别为
REPEATABLE READ
或SERIALIZABLE
。
- 幻读:
- 问题描述:在一个事务内,多次执行相同的查询,每次得到的结果集不同,因为另一个事务在期间插入或删除了符合查询条件的数据。
- 解决办法:设置数据库隔离级别为
SERIALIZABLE
,或者在查询时使用范围锁(如SELECT... FOR UPDATE
语句在合适的条件下使用,能锁住相关范围的数据)。