MySQL 8.0.28 版本,增加了对连接内存进行限制的功能。
mysql> show global variables like "%connection%memory%";
+-----------------------------------+----------------------+
| Variable_name | Value |
+-----------------------------------+----------------------+
| connection_memory_chunk_size | 8912 |
| connection_memory_limit | 18446744073709551615 |
| global_connection_memory_limit | 18446744073709551615 |
| global_connection_memory_tracking | OFF |
+-----------------------------------+----------------------+
4 rows in set (0.00 sec)
mysql> show global status like "%connection%memory%";
+--------------------------+-------+
| Variable_name | Value |
+--------------------------+-------+
| Global_connection_memory | 0 |
+--------------------------+-------+
1 row in set (0.01 sec
1 计数绑定到 THD
class THD {
public:
Thd_mem_cnt m_mem_cnt;
void enable_mem_cnt() { m_mem_cnt.enable(); }
void disable_mem_cnt() { m_mem_cnt.disable(); }
};
// 建立连接时候的 prepare 阶段打开统计计数。
bool thd_prepare_connection(THD *thd) {
thd->enable_mem_cnt();
...
}
// 在析构 THD,释放资源完成后关闭计数。
void THD::release_resources() {
...
disable_mem_cnt();
}
2 connection 内存统计结构体
class Thd_mem_cnt {
private:
THD *m_thd{nullptr}; // Pointer to THD object.
// 当前使用值
ulonglong mem_counter{0}; // Amount of memory consumed by thread.
// 使用过的最大值
ulonglong max_conn_mem{0}; // Max amount memory consumed by thread.
// 当前线程统计到 global 的值
ulonglong glob_mem_counter{0}; // Amount of memory added to global
// memory counter.
};
// global 的统计值。
mysql_mutex_t LOCK_global_conn_mem_limit;
ulonglong global_conn_mem_limit = 0;
ulonglong global_conn_mem_counter = 0;
3 connetion内存统计-分配内存
// 分配内存计数。
void Thd_mem_cnt::alloc_cnt(size_t size) {
mem_counter += size;
max_conn_mem = std::max(max_conn_mem, mem_counter);
...
if (mem_counter > m_thd->variables.conn_mem_limit) {
(void)generate_error(ER_DA_CONN_LIMIT, m_thd->variables.conn_mem_limit,
mem_counter);
}
if ((curr_mode & MEM_CNT_UPDATE_GLOBAL_COUNTER) &&
m_thd->variables.conn_global_mem_tracking &&
max_conn_mem > glob_mem_counter) {
const ulonglong curr_mem =
(max_conn_mem / m_thd->variables.conn_mem_chunk_size + 1) *
m_thd->variables.conn_mem_chunk_size; // 向上取整
assert(curr_mem > glob_mem_counter && curr_mem > mem_counter);
ulonglong delta = curr_mem - glob_mem_counter;
ulonglong global_conn_mem_counter_save;
ulonglong global_conn_mem_limit_save;
{
MUTEX_LOCK(lock, &LOCK_global_conn_mem_limit);
global_conn_mem_counter += delta;
global_conn_mem_counter_save = global_conn_mem_counter;
global_conn_mem_limit_save = global_conn_mem_limit;
}
glob_mem_counter = curr_mem;
max_conn_mem = std::max(max_conn_mem, glob_mem_counter);
if (global_conn_mem_counter_save > global_conn_mem_limit_save) {
...
(void)generate_error(ER_DA_GLOBAL_CONN_LIMIT, global_conn_mem_limit_save,
global_conn_mem_counter_save);
}
}
}
代码中对于更新到 global 的 curr_mem 计算:
const ulonglong curr_mem =
(max_conn_mem / m_thd->variables.conn_mem_chunk_size + 1) *
m_thd->variables.conn_mem_chunk_size; // 向上取整
最大情况下, curr_mem 比 max_conn_mem 多了一个 conn_mem_chunk_size,也就是统计到global 的值是偏大的。
4 connetion内存统计-释放内存
void Thd_mem_cnt::free_cnt(size_t size) {
if (mem_counter >= size) {
mem_counter -= size;
} else {
/* Freeing memory allocated by another. */
mem_counter = 0;
}
}
在释放内存时,只是简单的将 mem_counter 值更新。
int Thd_mem_cnt::reset() {
...
max_conn_mem = mem_counter;
if (m_thd->variables.conn_global_mem_tracking &&
(curr_mode & MEM_CNT_UPDATE_GLOBAL_COUNTER)) {
ulonglong delta;
ulonglong global_conn_mem_counter_save;
ulonglong global_conn_mem_limit_save;
if (glob_mem_counter > mem_counter) {
delta = glob_mem_counter - mem_counter;
MUTEX_LOCK(lock, &LOCK_global_conn_mem_limit);
assert(global_conn_mem_counter >= delta);
global_conn_mem_counter -= delta;
global_conn_mem_counter_save = global_conn_mem_counter;
global_conn_mem_limit_save = global_conn_mem_limit;
} else {
delta = mem_counter - glob_mem_counter;
MUTEX_LOCK(lock, &LOCK_global_conn_mem_limit);
global_conn_mem_counter += delta;
global_conn_mem_counter_save = global_conn_mem_counter;
global_conn_mem_limit_save = global_conn_mem_limit;
}
glob_mem_counter = mem_counter;
...
}
...
return 0;
}
在 reset 函数中,会进行当前内存使用值与统计到 global 的值一次同步。free_cnt 更新的内存变化值,在 reset 函数中才会更新到 global.
那么 reset 是在哪里调用的,在 do_command 函数中,在 dispatch_command 调用之前。
bool do_command(THD *thd) {
...
rc = thd->m_mem_cnt.reset();
...
return_value = dispatch_command(thd, &com_data, command);
...
return return_value;
}
5 enum_mem_cnt_mode
connection 内存限制跟当前用户权限有关,这就涉及到 connection 内存限制的 mode 变量,它还定义了超过内存限制后的行为。
enum enum_mem_cnt_mode {
MEM_CNT_DEFAULT = 0U, // 不计数
MEM_CNT_UPDATE_GLOBAL_COUNTER = (1U << 0), // 更新global信息
MEM_CNT_GENERATE_ERROR = (1U << 1), // 产生OOM错误信息
MEM_CNT_GENERATE_LOG_ERROR = (1U << 2) // 产生OOM错误信息写入日志
};
mode 代表计数方式以及采取的处理方式。
5.1 mode 初始化
static void prepare_new_connection_state(THD *thd) {
...
const bool is_admin_conn = sctx->check_access(SUPER_ACL) ||
sctx->has_global_grant(STRING_WITH_LEN("CONNECTION_ADMIN")).first);
thd->m_mem_cnt.set_orig_mode(is_admin_conn ? MEM_CNT_UPDATE_GLOBAL_COUNTER
: (MEM_CNT_UPDATE_GLOBAL_COUNTER |
MEM_CNT_GENERATE_ERROR |
MEM_CNT_GENERATE_LOG_ERROR));
...
}
对于 admin_conn 用户只进行更新global动作。其他权限的用户还会执行写错误日志、kill连接等。