MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

MariaDB用户统计功能的实现与应用

2021-03-141.2k 阅读

MariaDB用户统计功能的核心概念

用户统计在数据库管理中的重要性

在数据库管理的众多任务中,用户统计功能扮演着举足轻重的角色。对于数据库管理员(DBA)而言,了解数据库的用户使用情况是优化数据库性能、保障安全以及规划资源的基础。通过用户统计,我们能够知晓不同用户对数据库资源的消耗程度,哪些用户频繁地进行读写操作,哪些用户长时间处于活跃状态等。

以一个电商数据库为例,众多的后台管理用户、数据分析用户以及应用程序连接用户共同使用该数据库。通过用户统计,DBA可以发现某些数据分析任务由于使用复杂的查询语句,长时间占用数据库资源,影响了前台应用程序的响应速度。这时,DBA可以根据用户统计信息,对这些任务进行优化,或者调整资源分配策略,优先保障前台业务的流畅运行。

从安全角度看,用户统计能帮助发现异常的用户行为。例如,如果某个普通用户突然发起大量高权限的操作,通过用户统计记录的操作频率和权限使用情况,DBA 能够及时察觉并采取相应的安全措施,如冻结该用户账号、进行详细的审计等。

MariaDB用户相关信息的存储结构

MariaDB将用户相关信息存储在系统数据库mysql中,主要涉及user表。user表包含了丰富的用户属性字段,如Host(用户允许连接的主机地址)、User(用户名)、Password(加密后的用户密码)、Select_priv(是否拥有SELECT权限)等一系列权限相关字段。

以下是获取user表结构的SQL语句:

DESCRIBE mysql.user;

该表结构对于理解用户统计功能至关重要,因为我们后续实现用户统计功能时,很多数据来源就是这张表。例如,通过统计不同主机连接的用户数量,我们可以了解数据库的连接分布情况,判断是否存在某个网段的异常连接。

统计维度与指标

  1. 连接统计 连接统计主要关注用户与数据库建立连接的相关指标。包括:
  • 连接次数:统计每个用户在特定时间段内成功连接到 MariaDB 数据库的次数。这有助于了解哪些用户是数据库的高频使用者,对于数据库资源的规划和分配有重要意义。例如,某些应用程序可能频繁地与数据库建立短连接进行数据交互,通过统计连接次数,我们可以判断该应用程序对数据库连接资源的需求程度。
  • 连接时长:记录每次连接从建立到断开所持续的时间。长时间的连接可能会占用数据库的宝贵资源,通过统计连接时长,DBA可以发现并处理那些长时间闲置但未断开的连接,释放资源。例如,某些数据分析任务可能会长时间占用连接进行复杂查询,了解连接时长有助于评估此类任务对数据库性能的影响。
  1. 权限使用统计 权限使用统计侧重于分析用户对不同数据库操作权限的使用情况。
  • 读操作统计:统计用户执行SELECT语句的次数、涉及的表和数据量等。了解读操作的分布情况,有助于优化数据库的查询性能。例如,如果某个表经常被大量用户读取,我们可以考虑对该表进行适当的索引优化,提高查询效率。
  • 写操作统计:包括INSERTUPDATEDELETE等操作的次数和影响的数据行数。写操作通常会对数据库的性能产生较大影响,尤其是在高并发场景下。通过统计写操作,DBA可以发现频繁进行写操作的用户或业务模块,评估其对数据库一致性和性能的影响,并采取相应的优化措施,如批量操作、事务优化等。
  1. 资源消耗统计 资源消耗统计关注用户在数据库操作过程中对系统资源的占用情况。
  • CPU消耗:虽然 MariaDB 本身没有直接提供用户级别的 CPU 消耗统计功能,但通过操作系统的监控工具结合数据库日志,我们可以大致估算不同用户操作所消耗的 CPU 资源。例如,在 Linux 系统下,通过top命令结合数据库进程 ID,可以观察到数据库进程的 CPU 占用情况,再结合数据库日志中记录的用户操作时间和操作类型,分析不同用户操作对 CPU 的影响。
  • 内存消耗:类似 CPU 消耗,MariaDB 也没有直接提供用户级别的内存消耗统计。但我们可以通过监控数据库缓存的使用情况以及用户查询结果集的大小等间接方式来估算用户的内存消耗。例如,某些复杂查询可能会产生较大的结果集,占用较多的内存,如果这些查询是由特定用户发起的,就可以针对性地对这些用户的查询进行优化,减少内存占用。

基于SQL实现基本用户统计功能

连接次数统计

要统计用户的连接次数,我们可以借助 MariaDB 的通用查询日志(General Query Log)。通用查询日志记录了所有连接到数据库的客户端的连接和查询信息。首先,确保通用查询日志已开启,可通过修改 MariaDB 配置文件(通常是my.cnfmy.ini)来开启:

[mysqld]
general_log = 1
general_log_file = /var/log/mysql/mysql.log

重启 MariaDB 服务使配置生效。

通用查询日志文件格式如下:

2023-10-01T10:00:00.000000Z 1 Connect user1@192.168.1.100 on 
2023-10-01T10:00:05.000000Z 1 Query SELECT * FROM products

第一行记录了用户user1192.168.1.100主机连接到数据库,第二行记录了该用户执行的查询语句。

为了统计连接次数,我们可以使用以下步骤:

  1. 导入日志文件到临时表。假设日志文件名为mysql.log,先创建临时表:
CREATE TEMPORARY TABLE temp_log (
    log_entry VARCHAR(255)
);

然后使用LOAD DATA INFILE将日志文件内容导入临时表:

LOAD DATA INFILE '/var/log/mysql/mysql.log'
INTO TABLE temp_log
LINES TERMINATED BY '\n';
  1. 解析临时表中的连接记录并统计连接次数:
SELECT 
    SUBSTRING_INDEX(SUBSTRING_INDEX(log_entry, ' ', 3), ' ', -1) AS user,
    COUNT(*) AS connection_count
FROM 
    temp_log
WHERE 
    log_entry LIKE '%Connect%'
GROUP BY 
    SUBSTRING_INDEX(SUBSTRING_INDEX(log_entry, ' ', 3), ' ', -1);

上述 SQL 语句通过对日志记录的解析,提取出用户名,并统计每个用户的连接次数。

连接时长统计

连接时长的统计相对复杂一些,因为通用查询日志并没有直接记录连接断开的时间。但我们可以通过记录连接建立时间和后续查询时间的差值来近似计算连接时长。

  1. 首先,我们需要修改通用查询日志记录格式,使其包含时间戳的高精度信息。在my.cnfmy.ini中添加:
[mysqld]
log_output = FILE
general_log = 1
general_log_file = /var/log/mysql/mysql.log
log_timestamps = SYSTEM

重启 MariaDB 服务。

  1. 接下来,我们假设日志文件内容如下:
2023-10-01 10:00:00.000000 1 Connect user1@192.168.1.100 on 
2023-10-01 10:00:05.000000 1 Query SELECT * FROM products
2023-10-01 10:00:10.000000 1 Quit

我们可以创建如下临时表来存储解析后的日志信息:

CREATE TEMPORARY TABLE parsed_log (
    event_time DATETIME,
    thread_id INT,
    event_type VARCHAR(20),
    user_host VARCHAR(255),
    query VARCHAR(255)
);
  1. 然后编写 SQL 语句解析日志文件并插入到临时表:
LOAD DATA INFILE '/var/log/mysql/mysql.log'
INTO TABLE parsed_log
FIELDS TERMINATED BY ' '
LINES TERMINATED BY '\n'
(@var1, @var2, @var3, @var4, @var5)
SET 
    event_time = STR_TO_DATE(CONCAT(@var1, ' ', @var2), '%Y-%m-%d %H:%i:%s.%f'),
    thread_id = @var3,
    event_type = @var4,
    user_host = CASE WHEN @var4 = 'Connect' THEN @var5 END,
    query = CASE WHEN @var4 = 'Query' THEN @var5 END;
  1. 最后,计算每个用户的连接时长:
WITH ConnectEvents AS (
    SELECT 
        event_time,
        user_host,
        thread_id
    FROM 
        parsed_log
    WHERE 
        event_type = 'Connect'
),
QuitEvents AS (
    SELECT 
        event_time,
        thread_id
    FROM 
        parsed_log
    WHERE 
        event_type = 'Quit'
)
SELECT 
    CE.user_host,
    TIMESTAMPDIFF(SECOND, CE.event_time, QE.event_time) AS connection_duration
FROM 
    ConnectEvents CE
JOIN 
    QuitEvents QE ON CE.thread_id = QE.thread_id;

上述 SQL 通过公共的thread_id关联连接建立事件和断开事件,并计算出连接时长。

权限使用统计 - 读操作

对于读操作(主要是SELECT语句)的统计,我们可以通过启用慢查询日志(Slow Query Log)并结合一些日志解析技巧来实现。慢查询日志记录了执行时间超过指定阈值的查询语句。首先,确保慢查询日志已开启并设置合理的阈值,在my.cnfmy.ini中添加:

[mysqld]
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow-query.log
long_query_time = 2

上述配置表示记录执行时间超过2秒的查询语句到slow-query.log文件。

假设慢查询日志文件内容如下:

# Time: 2023-10-01T10:00:00.000000Z
# User@Host: user1[user1] @ 192.168.1.100 [192.168.1.100]
# Query_time: 3.000000  Lock_time: 0.000000 Rows_sent: 100 Rows_examined: 1000
SELECT * FROM products WHERE category = 'electronics';

我们可以创建临时表来存储解析后的日志信息:

CREATE TEMPORARY TABLE slow_query_parsed (
    query_time DECIMAL(10, 6),
    user_host VARCHAR(255),
    query_text TEXT
);

然后编写 SQL 语句解析日志文件并插入到临时表:

LOAD DATA INFILE '/var/log/mysql/slow-query.log'
INTO TABLE slow_query_parsed
LINES STARTING BY '# Time:'
(@var1, @var2, @var3, @var4, @var5, @var6, @var7, @var8)
SET 
    query_time = SUBSTRING_INDEX(SUBSTRING_INDEX(@var4, ' ', 2), ' ', -1),
    user_host = SUBSTRING_INDEX(SUBSTRING_INDEX(@var2, '[', 2), ']', 1),
    query_text = @var8;

最后,统计每个用户的读操作次数和涉及的表:

SELECT 
    user_host,
    COUNT(*) AS read_operation_count,
    SUBSTRING_INDEX(SUBSTRING_INDEX(query_text, 'FROM ', 2), ' ', -1) AS table_used
FROM 
    slow_query_parsed
WHERE 
    query_text LIKE 'SELECT%'
GROUP BY 
    user_host,
    SUBSTRING_INDEX(SUBSTRING_INDEX(query_text, 'FROM ', 2), ' ', -1);

上述 SQL 语句通过解析慢查询日志,统计每个用户执行SELECT语句的次数以及涉及的表。

权限使用统计 - 写操作

写操作(INSERTUPDATEDELETE)的统计方法与读操作类似,同样借助慢查询日志。

  1. 首先,确保慢查询日志配置正确,如前文所述。

  2. 创建临时表来存储解析后的日志信息:

CREATE TEMPORARY TABLE write_query_parsed (
    query_time DECIMAL(10, 6),
    user_host VARCHAR(255),
    query_text TEXT
);
  1. 解析慢查询日志并插入到临时表:
LOAD DATA INFILE '/var/log/mysql/slow-query.log'
INTO TABLE write_query_parsed
LINES STARTING BY '# Time:'
(@var1, @var2, @var3, @var4, @var5, @var6, @var7, @var8)
SET 
    query_time = SUBSTRING_INDEX(SUBSTRING_INDEX(@var4, ' ', 2), ' ', -1),
    user_host = SUBSTRING_INDEX(SUBSTRING_INDEX(@var2, '[', 2), ']', 1),
    query_text = @var8;
  1. 统计每个用户的写操作次数和涉及的表:
SELECT 
    user_host,
    COUNT(*) AS write_operation_count,
    SUBSTRING_INDEX(SUBSTRING_INDEX(query_text, ' ', 2), ' ', -1) AS operation_type,
    SUBSTRING_INDEX(SUBSTRING_INDEX(query_text, ' ', 3), ' ', -1) AS table_used
FROM 
    write_query_parsed
WHERE 
    query_text REGEXP '^(INSERT|UPDATE|DELETE)'
GROUP BY 
    user_host,
    SUBSTRING_INDEX(SUBSTRING_INDEX(query_text, ' ', 2), ' ', -1),
    SUBSTRING_INDEX(SUBSTRING_INDEX(query_text, ' ', 3), ' ', -1);

上述 SQL 语句通过解析慢查询日志,统计每个用户执行INSERTUPDATEDELETE操作的次数以及涉及的表。

高级用户统计功能实现

使用存储过程优化统计逻辑

随着统计功能的复杂度增加,使用 SQL 语句直接进行统计可能会变得冗长且难以维护。这时,存储过程可以发挥重要作用。例如,我们可以创建一个存储过程来统计某个时间段内用户的连接次数、连接时长、读操作次数和写操作次数。

  1. 创建存储过程:
DELIMITER //

CREATE PROCEDURE UserStatistics(
    IN start_time DATETIME,
    IN end_time DATETIME
)
BEGIN
    -- 连接次数统计
    SELECT 
        SUBSTRING_INDEX(SUBSTRING_INDEX(log_entry, ' ', 3), ' ', -1) AS user,
        COUNT(*) AS connection_count
    FROM 
        temp_log
    WHERE 
        log_entry LIKE '%Connect%'
        AND SUBSTRING_INDEX(log_entry, ' ', 1) BETWEEN start_time AND end_time
    GROUP BY 
        SUBSTRING_INDEX(SUBSTRING_INDEX(log_entry, ' ', 3), ' ', -1);

    -- 连接时长统计
    WITH ConnectEvents AS (
        SELECT 
            event_time,
            user_host,
            thread_id
        FROM 
            parsed_log
        WHERE 
            event_type = 'Connect'
            AND event_time BETWEEN start_time AND end_time
    ),
    QuitEvents AS (
        SELECT 
            event_time,
            thread_id
        FROM 
            parsed_log
        WHERE 
            event_type = 'Quit'
            AND event_time BETWEEN start_time AND end_time
    )
    SELECT 
        CE.user_host,
        TIMESTAMPDIFF(SECOND, CE.event_time, QE.event_time) AS connection_duration
    FROM 
        ConnectEvents CE
    JOIN 
        QuitEvents QE ON CE.thread_id = QE.thread_id;

    -- 读操作统计
    SELECT 
        user_host,
        COUNT(*) AS read_operation_count,
        SUBSTRING_INDEX(SUBSTRING_INDEX(query_text, 'FROM ', 2), ' ', -1) AS table_used
    FROM 
        slow_query_parsed
    WHERE 
        query_text LIKE 'SELECT%'
        AND query_time BETWEEN start_time AND end_time
    GROUP BY 
        user_host,
        SUBSTRING_INDEX(SUBSTRING_INDEX(query_text, 'FROM ', 2), ' ', -1);

    -- 写操作统计
    SELECT 
        user_host,
        COUNT(*) AS write_operation_count,
        SUBSTRING_INDEX(SUBSTRING_INDEX(query_text, ' ', 2), ' ', -1) AS operation_type,
        SUBSTRING_INDEX(SUBSTRING_INDEX(query_text, ' ', 3), ' ', -1) AS table_used
    FROM 
        write_query_parsed
    WHERE 
        query_text REGEXP '^(INSERT|UPDATE|DELETE)'
        AND query_time BETWEEN start_time AND end_time
    GROUP BY 
        user_host,
        SUBSTRING_INDEX(SUBSTRING_INDEX(query_text, ' ', 2), ' ', -1),
        SUBSTRING_INDEX(SUBSTRING_INDEX(query_text, ' ', 3), ' ', -1);
END //

DELIMITER ;
  1. 调用存储过程:
CALL UserStatistics('2023-10-01 00:00:00', '2023-10-01 23:59:59');

通过存储过程,我们将复杂的统计逻辑封装起来,提高了代码的可读性和可维护性,同时也方便在不同场景下复用。

利用触发器记录用户操作

触发器(Trigger)是 MariaDB 中一种特殊的存储过程,它会在特定的数据库事件(如插入、更新、删除数据)发生时自动执行。我们可以利用触发器来实时记录用户的操作,从而实现更精确的用户统计。

例如,我们创建一个触发器,在每次用户执行INSERT操作时记录相关信息。

  1. 创建记录INSERT操作的表:
CREATE TABLE insert_operations (
    operation_id INT AUTO_INCREMENT PRIMARY KEY,
    user_host VARCHAR(255),
    operation_time DATETIME,
    table_name VARCHAR(255),
    inserted_data TEXT
);
  1. 创建触发器:
DELIMITER //

CREATE TRIGGER after_insert_trigger
AFTER INSERT ON your_table
FOR EACH ROW
BEGIN
    INSERT INTO insert_operations (user_host, operation_time, table_name, inserted_data)
    VALUES (CURRENT_USER(), NOW(), 'your_table', CONCAT(NEW.column1, ', ', NEW.column2, ', ...'));
END //

DELIMITER ;

上述触发器在your_table表每次执行INSERT操作后,将执行操作的用户、操作时间、表名以及插入的数据记录到insert_operations表中。通过类似的方法,我们可以创建针对UPDATEDELETE操作的触发器,从而更全面地记录用户的写操作,为用户统计提供更准确的数据。

结合系统视图进行综合统计

MariaDB 提供了一些系统视图,如information_schema.processlist,它显示了当前正在执行的线程信息,包括用户、主机、查询语句等。我们可以结合这些系统视图与前面介绍的日志解析方法进行更综合的用户统计。

例如,要统计当前活跃用户的连接信息和正在执行的查询:

SELECT 
    user,
    host,
    info AS current_query
FROM 
    information_schema.processlist;

结合日志解析得到的历史连接和操作信息,我们可以对用户的行为进行更全面的分析。例如,通过对比当前活跃连接与历史连接次数,我们可以发现连接频率的异常变化;通过分析当前正在执行的查询与历史查询记录,我们可以了解用户的查询习惯和资源消耗模式。

MariaDB用户统计功能的应用场景

数据库性能优化

  1. 资源分配优化 通过用户连接次数和连接时长的统计,DBA 可以了解不同用户对数据库连接资源的需求。对于连接次数频繁且连接时长较长的用户,如某些数据分析任务的用户,DBA 可以考虑为其分配专门的连接池,或者调整数据库的连接参数,如增加最大连接数等,以满足其需求,同时避免对其他用户造成影响。

例如,在一个包含多个业务模块的数据库中,数据分析模块的用户经常进行长时间的复杂查询,占用大量连接资源。通过用户统计发现这一情况后,DBA 为数据分析模块单独配置了一个连接池,并调整了连接池的参数,使其能够满足数据分析任务的需求,同时优化了其他业务模块的连接分配,提高了整体的数据库性能。

  1. 查询优化 权限使用统计中的读操作和写操作统计可以帮助 DBA 发现频繁执行的查询语句和性能瓶颈。对于频繁执行且耗时较长的查询,如某些复杂的SELECT语句,DBA 可以通过分析查询逻辑、添加合适的索引、优化表结构等方式进行优化。

例如,通过用户统计发现某个用户经常执行一条涉及多表连接且性能较差的SELECT语句。DBA 对该查询进行分析,发现缺少必要的索引,于是为相关表添加了合适的索引,大大提高了查询性能,减少了数据库的 CPU 和内存消耗。

安全审计

  1. 异常行为检测 用户统计功能可以帮助发现用户的异常行为。例如,如果某个普通用户突然执行大量高权限的操作,或者连接次数在短时间内急剧增加,这些异常行为都可以通过用户统计信息被及时发现。

以一个银行数据库为例,普通柜员用户通常只执行一些常规的账户查询和简单的交易操作。如果通过用户统计发现某个柜员用户突然执行大量的账户资金转移操作,这明显属于异常行为,数据库管理员可以立即采取措施,如冻结该用户账号,并进行详细的审计和调查,以防止潜在的安全风险。

  1. 权限管理优化 通过统计用户对不同权限的使用情况,DBA 可以评估用户实际所需的权限,避免权限过度分配。例如,如果某个用户从未使用过DELETE权限,但却拥有该权限,DBA 可以考虑收回该权限,降低数据库被误操作或恶意攻击的风险。

业务分析与决策支持

  1. 用户行为分析 从用户统计信息中,业务分析师可以了解不同用户群体对数据库的使用模式,进而分析用户的业务需求和行为习惯。例如,通过分析电商数据库中不同类型用户(如普通消费者、商家、运营人员)的数据库操作,业务分析师可以发现消费者主要进行商品查询和订单操作,商家主要进行商品管理操作,运营人员主要进行数据分析和统计操作。基于这些分析结果,电商平台可以优化其业务流程和用户界面,提升用户体验。

  2. 业务趋势预测 长期的用户统计数据可以用于预测业务趋势。例如,通过统计不同时间段内用户对数据库的读写操作次数和数据量,结合业务发展情况,企业可以预测未来数据库的负载情况,提前进行资源规划和系统升级。如果发现随着业务的增长,数据库的写操作次数和数据量呈快速上升趋势,企业可以提前规划存储扩容和性能优化方案,以保障业务的持续稳定运行。

在实际应用中,MariaDB 的用户统计功能需要与数据库的整体架构、业务需求以及安全策略相结合,通过合理的配置和开发,充分发挥其在数据库管理、安全保障和业务支持等方面的重要作用。通过不断优化和完善用户统计功能,企业可以更好地管理和利用数据库资源,提升业务竞争力。