X - Blog

XGD16的个人博客

MySQL 的事务隔离

1. 隔离级别介绍

1.读未提交(READ UNCOMMITTED)

  • 最低的隔离级别
  • 事务可以读取其他事务未提交的修改(”脏读”)
  • 性能最好,但数据一致性最差

2.读已提交(READ COMMITTED)

  • 事务只能读取其他事务已提交的修改
  • 解决了脏读问题,但可能出现不可重复读
  • Oracle、SQL Server等数据库的默认级别

3.可重复读(REPEATABLE READ)

  • MySQL的默认隔离级别
  • 确保在同一事务中多次读取同样数据结果一致
  • 解决了不可重复读问题,但可能出现幻读
  • MySQL通过多版本并发控制(MVCC)和间隙锁(Gap Lock)解决了幻读问题

4.串行化(SERIALIZABLE)

  • 最高的隔离级别
  • 所有事务串行执行,完全隔离
  • 解决了所有并发问题,但性能最差

2. 主要解决的并发问题

1. 脏读(Dirty Read)

  • 一个事务读取了另一个未提交事务修改过的数据
  • 示例场景
    1
    2
    3
    4
    5
    6
    7
    -- 事务A
    BEGIN;
    UPDATE users SET balance = balance - 100 WHERE id = 1;
    -- 此时balance已修改但未提交

    -- 事务B(READ UNCOMMITTED级别)
    SELECT balance FROM users WHERE id = 1; -- 读取到未提交的修改

2. 不可重复读(Non-repeatable Read)

  • 同一事务内,多次读取同一数据返回不同结果(数据被其他事务修改并提交)
  • 示例场景
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    -- 事务A
    BEGIN;
    SELECT balance FROM users WHERE id = 1; -- 第一次读取:1000

    -- 事务B
    BEGIN;
    UPDATE users SET balance = 900 WHERE id = 1;
    COMMIT;

    -- 事务A
    SELECT balance FROM users WHERE id = 1; -- 第二次读取:900(不一致)

3. 幻读(Phantom Read)

  • 同一事务内,多次查询返回不同的行集(其他事务新增或删除了数据)
  • 示例场景
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    -- 事务A
    BEGIN;
    SELECT COUNT(*) FROM users WHERE age > 20; -- 第一次查询:10条记录

    -- 事务B
    BEGIN;
    INSERT INTO users (name, age) VALUES ('张三', 25);
    COMMIT;

    -- 事务A
    SELECT COUNT(*) FROM users WHERE age > 20; -- 第二次查询:11条记录(出现幻读)

3. 隔离级别与并发问题对照表

隔离级别 脏读 不可重复读 幻读 性能 数据一致性
READ UNCOMMITTED ❌ 可能发生 ❌ 可能发生 ❌ 可能发生 ⭐⭐⭐⭐⭐ 最好 ⭐ 最差
READ COMMITTED ✅ 已解决 ❌ 可能发生 ❌ 可能发生 ⭐⭐⭐⭐ 较好 ⭐⭐ 较差
REPEATABLE READ ✅ 已解决 ✅ 已解决 ✅ 已解决* ⭐⭐⭐ 中等 ⭐⭐⭐⭐ 较好
SERIALIZABLE ✅ 已解决 ✅ 已解决 ✅ 已解决 ⭐ 最差 ⭐⭐⭐⭐⭐ 最好

*注:MySQL的REPEATABLE READ级别通过MVCC和间隙锁机制解决了幻读问题,这是MySQL特有的实现。

4. 各数据库默认隔离级别对比

数据库 默认隔离级别 说明
MySQL REPEATABLE READ 通过MVCC解决幻读
Oracle READ COMMITTED 支持快照隔离
SQL Server READ COMMITTED 支持快照隔离
PostgreSQL READ COMMITTED 支持快照隔离

5. 实际应用建议

选择隔离级别的考虑因素:

  1. 数据一致性要求

    • 金融交易:建议使用SERIALIZABLE或REPEATABLE READ
    • 一般业务:READ COMMITTED通常足够
  2. 性能要求

    • 高并发场景:考虑使用READ COMMITTED
    • 读多写少:REPEATABLE READ性能影响较小
  3. 业务特点

    • 报表查询:REPEATABLE READ确保数据一致性
    • 实时数据:READ COMMITTED提供更好的并发性

MySQL最佳实践:

1
2
3
4
5
6
7
8
9
10
11
-- 查看当前隔离级别
SELECT @@transaction_isolation;

-- 设置会话隔离级别
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;

-- 在事务中设置隔离级别
START TRANSACTION;
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
-- 执行事务操作
COMMIT;

6. 总结

MySQL的事务隔离机制通过不同的隔离级别来平衡数据一致性和性能。选择合适的隔离级别需要根据具体的业务场景、数据一致性要求和性能需求来决定。在大多数情况下,MySQL的默认REPEATABLE READ级别已经能够很好地处理并发问题。

Go 语言中的接口(interface)是一种抽象类型,它定义了一组方法的集合。任何类型只要实现了这些方法,就被认为实现了这个接口。这种设计提供了极大的灵活性和可扩展性。

1. 声明一个 interface

1
2
3
4
5
type UserInterface interface {
SetInfo(username string, age int)
GetUsername() (username string)
GetAge() (age int)
}

在这个例子中,我们定义了一个 UserInterface 接口,它要求实现三个方法:

  • SetInfo: 设置用户信息
  • GetUsername: 获取用户名
  • GetAge: 获取年龄

2. 创建一个实现了 UserInterface 的结构体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
type User struct {
Username string
Age int
}

func (t *User) SetInfo(username string, age int) {
t.Username = username
t.Age = age
}

func (t *User) GetUsername() (username string) {
return t.Username
}

func (t *User) GetAge() (age int) {
return t.Age
}

这里的 User 结构体通过实现所有 UserInterface 要求的方法,自动成为了 UserInterface 的实现者。注意:

  • Go 的接口实现是隐式的,不需要显式声明
  • 方法接收者使用指针 *User 可以修改结构体的值

3. 如何使用

1
2
3
4
5
6
7
8
9
// 假设一个函数需要 UserInterface 类型的参数
func GetUser(user UserInterface) {
user.SetInfo("Tom", 25) // 设置用户信息
fmt.Println(user.GetUsername()) // 输出: Tom
fmt.Println(user.GetAge()) // 输出: 25
}

// 调用示例
GetUser(new(User)) // new(User) 创建一个 User 的指针

4. 实际应用场景

  1. 数据库操作接口
1
2
3
4
5
6
7
8
9
type Storage interface {
Save(data interface{}) error
Get(id string) (interface{}, error)
Delete(id string) error
}

// 可以有多个实现
type MySQLStorage struct { ... }
type MongoDBStorage struct { ... }
  1. 日志接口
1
2
3
4
5
type Logger interface {
Info(message string)
Error(message string)
Debug(message string)
}
  1. HTTP 客户端接口
1
2
3
4
type HTTPClient interface {
Get(url string) (*Response, error)
Post(url string, body interface{}) (*Response, error)
}

5. 接口的优势

  1. 解耦合: 接口将实现从定义中分离,使代码更加灵活
  2. 易于测试: 可以轻松创建 mock 实现用于测试
  3. 多态性: 一个接口可以有多个不同的实现
  4. 可扩展性: 新的实现可以随时添加,而无需修改现有代码

6. 总结

在 Go 中,interface 是实现多态和抽象的关键机制。只要一个类型实现了接口中定义的所有方法,它就自动满足了该接口的要求。这种设计既简单又灵活,是 Go 语言最强大的特性之一。

使用接口的最佳实践:

  • 接口应该小而精确,只包含必要的方法
  • 在需要抽象和多态的地方使用接口
  • 优先使用接口作为函数参数类型,而不是具体类型

编写 request.ts

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
import type {
AxiosError,
AxiosRequestConfig,
AxiosResponse,
InternalAxiosRequestConfig,
} from "axios";
import axios from "axios";

// 创建axios实例
const request = axios.create({
baseURL: "http://{需要请求的服务器地址}",
timeout: 60 * 1000,
params: {},
});

request.interceptors.request.use(
function (config: InternalAxiosRequestConfig) {
// 在发送请求之前做些什么
return config;
},
function (error: any) {
// 对请求错误做些什么
console.log(error);
return Promise.reject(error);
}
);

// 添加响应拦截器
request.interceptors.response.use(
function (response: AxiosResponse) {
// 成功返回时处理数据
return response;
},
function (error: AxiosError<{ code: number; msg: string; time: number }>) {
// 出错时调用
return Promise.reject(error);
}
);

export interface Response<T = any> {
code: number;
data: T;
msg: string;
time: number;
}

const req = async <T>(config: AxiosRequestConfig) => {
const res = await request(config);
return res.data as T;
};
export default req;

编写接口函数

示例 api/hello.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import type { Response } from "@/lib/request";
import req from "@/lib/request";

export type HelloApiResp = {
msg: string;
};

export const sayHello = (params: { name: string } = { name: "world" }) => {
return req<Response<HelloApiResp>>({
url: "/api/hello",
method: "GET",
params,
});
};

使用示例

import { sayHello } from "@/api/demo";

sayHello({ name: "x" }).then((res) => {
  console.log(res.msg);
});

UniTranslate:打造统一的翻译服务生态

在当今全球化的互联网时代,多语言支持已经成为许多应用和服务的基本需求。然而,不同翻译服务提供商的 API 接口各不相同,集成和管理多个翻译服务往往会带来额外的开发和维护成本。UniTranslate 应运而生,它是一个基于 Go 语言开发的统一翻译管理平台,旨在解决这些痛点。

核心特性

  1. 多平台支持

    • 支持主流翻译服务:百度、有道、谷歌、Deepl、腾讯、ChatGPT、火山、讯飞、PaPaGo
    • 支持免费的 Google 翻译平台
    • 统一的 API 调用规范,降低开发成本
  2. 智能调度机制

    • 支持设置翻译 API 的优先级
    • 同一提供商可配置多个接入点,支持不同等级设置
    • 自动故障转移:当某个 API 调用失败时自动切换到下一个可用服务
  3. 高效缓存策略

    • 支持 Redis 和内存缓存模式
    • 智能缓存已翻译内容,减少重复调用
    • 可配置的缓存策略,满足不同场景需求
  4. 完善的管理功能

    • Web 控制台支持,提供可视化管理界面
    • 灵活的配置系统
    • 支持终端交互式翻译
    • 详细的 API 文档

部署简便,开箱即用

UniTranslate 提供了 Docker 支持,只需几个简单的命令即可完成部署:

Docker 部署

未来展望

UniTranslate 团队持续致力于提升产品功能,计划中的特性包括:

  • MySQL 持久化存储
  • 更安全的身份验证机制
  • 支持更多国家语言
  • SQLite 支持
  • 更多客户端功能

为什么选择 UniTranslate?

  1. 统一接入点:通过单一的 API 接口访问多个翻译服务,简化开发流程。

  2. 高可用性:智能的故障转移机制确保服务的稳定性。

  3. 成本优化:缓存机制有效减少 API 调用次数,降低运营成本。

  4. 灵活配置:支持多种部署方式和配置选项,适应不同的使用场景。

  5. 开源免费:基于开源协议,社区驱动的持续更新和改进。

快速开始

访问 UniTranslate 官方文档 获取详细的使用指南和 API 文档。项目托管在 GitHub,欢迎社区贡献和反馈。

1. 为什么要使用 RabbitMQ 🤔

异步通信:允许系统中的不同部分通过消息传递进行通信,而无需实时直接交互。这种异步通信可以提高系统的可伸缩性和灵活性。

解耦系统组件:消息代理系统可以在系统内部或跨系统之间充当中介,从而实现系统组件的解耦。这意味着每个组件可以独立地工作,而无需直接了解其他组件的实现细节。

消息队列:RabbitMQ 等消息代理系统利用消息队列的方式来存储和传递消息。这可以帮助处理系统中的大量请求或数据,确保消息在需要时得到处理,而不会因为某个组件繁忙而丢失。

异步任务处理:允许将耗时的任务从主应用程序中分离出来并异步处理。通过将这些任务发送到队列中,可以提高应用程序的性能和响应性。

消息持久化:消息代理系统通常支持消息持久化,即使在代理或消费者宕机后,消息也不会丢失。这有助于确保数据的安全性和可靠性。

负载均衡:使用消息队列可以实现负载均衡,多个消费者可以同时从队列中获取消息并处理,提高系统的吞吐量和效率。

处理失败和重试机制:消息代理系统通常具备处理失败消息和重试机制的能力,可以有效处理由于错误而导致的消息处理失败的情况。

2. 关于使用

简单封装
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
<?php

namespace App\Logic;

use PhpAmqpLib\Channel\AbstractChannel;
use PhpAmqpLib\Channel\AMQPChannel;
use PhpAmqpLib\Connection\AMQPStreamConnection;
use PhpAmqpLib\Message\AMQPMessage;

class RabbitMqClient
{
private AMQPStreamConnection $conn;
private AbstractChannel|AMQPChannel $chan;
private string $queueName;

public function __construct()
{
$this->conn = new AMQPStreamConnection('127.0.0.1', 5672, 'admin', 'admin');
$this->chan = $this->conn->channel();
}

public function setQueue(string $queueName): self
{
$this->chan->queue_declare($this->queueName = $queueName, auto_delete: false);
return $this;
}

/**
* 发送
*
* @param mixed $data 需要发送的数据
* @return void
*/
public function send(mixed $data): void
{
$this->chan->basic_publish(new AMQPMessage(json_encode($data, JSON_UNESCAPED_UNICODE)), '', $this->queueName);
}

/**
* 接收消息
*
* @param callable $callback
* @return void
*/
public function recv(callable $callback): void
{
$this->chan->basic_consume($this->queueName, '', false, false, false, false, $callback);
while (count($this->chan->callbacks)) {
$this->chan->wait();
}
}

/**
* 获取队列名称
*
* @return string
*/
public function getQueueName(): string
{
return $this->queueName;
}

/**
* 获取channel
*
* @return AbstractChannel|AMQPChannel
*/
public function getChan(): AbstractChannel|AMQPChannel
{
return $this->chan;
}

/**
* 关闭连接
*
* @return void
* @throws null
*/
public function close (): void
{
$this->conn->close();
$this->chan->close();
}
}

发送端
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
<?php

namespace App\Console\Commands\RabbitMQ;

use App\Logic\RabbitMqClient;
use Illuminate\Console\Command;

class RabbitMqSend extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'rabbit-mq:send';

/**
* The console command description.
*
* @var string
*/
protected $description = 'Command description';

/**
* Execute the console command.
*/
public function handle(): void
{
$mq = new RabbitMqClient();

$mq->setQueue('test');
$mq->send(['a' => 1]);
}
}

接收端
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
<?php

namespace App\Console\Commands\RabbitMQ;

use App\Logic\RabbitMqClient;
use Illuminate\Console\Command;
use PhpAmqpLib\Message\AMQPMessage;

class RabbitMqRecv extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'rabbit-mq:recv';

/**
* The console command description.
*
* @var string
*/
protected $description = 'Command description';

/**
* Execute the console command.
*/
public function handle(): void
{
$mq = new RabbitMqClient();

$mq->setQueue('test');

while (true) {
$mq->recv(function (AMQPMessage $data) use ($mq) {
$this->queueHandler($mq, json_decode($data->getBody()), true);
// ack 确认消息已处理 避免进入重新发布 (没有ack的消息会在当前连接客户端断开重启后重新接收到此消息)
$mq->getChan()->basic_ack($data->getDeliveryTag());
});
}
}

private function queueHandler(RabbitMqClient $mq, mixed $data): void
{
// 输出 decode 后接收到的消息
dump($data);
}

}

3. 函数讲解

基础依赖
1
composer require php-amqplib/php-amqplib
使用示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
use App\Logic\RabbitMqClient;

// 创建 RabbitMqClient 实例
$rabbitMqClient = new RabbitMqClient();

// 设置队列名称
$rabbitMqClient->setQueue('your_queue_name');

// 发送消息
$rabbitMqClient->send(['message' => 'Hello, RabbitMQ!']);

// 接收消息
$rabbitMqClient->recv(function ($msg) {
echo "Received: ", $msg->body, "\n";
});

// 关闭连接
$rabbitMqClient->close();

方法

__construct()

初始化 RabbitMqClient 类并建立与 RabbitMQ 代理的连接。

setQueue(string $queueName): self

设置要发送/接收消息的队列名称。

send(mixed $data): void

向队列发送消息。

recv(callable $callback): void

从队列接收消息。使用回调函数处理接收到的消息。

getQueueName(): string

获取当前队列的名称。

getChan(): AbstractChannel|AMQPChannel

获取当前的 AMQP 通道。

close(): void

关闭与 RabbitMQ 的连接和通道。

注意事项
  • 请确保在使用 send()recv() 方法之后调用 close() 方法以关闭连接,避免资源泄露。
0%