当前位置:首页 > 问答 > 正文

数据分页 异步请求:基于Ajax的C语言SQL分页查询实现方法

数据分页 | 异步请求:基于Ajax的C语言SQL分页查询实现方法

场景引入:当数据量爆炸时

"老王,咱们那个设备监控系统的查询页面又卡死了!" 小张急匆匆地跑进办公室,确实,随着公司物联网设备增加到10万多台,传统的全量数据加载方式已经不堪重负,每次查询都要等上十几秒,用户体验极差。

这种情况在现代Web开发中很常见——当数据量达到万级甚至百万级时,如何优雅地实现数据分页查询就成了必须解决的问题,今天我们就来聊聊如何在C语言后端结合Ajax技术实现高效的SQL分页查询。

技术选型:为什么是C+Ajax?

你可能觉得奇怪,现在不是流行Node.js、Python这些后端语言吗?为什么还要用C语言?其实在很多嵌入式系统、高性能服务器场景中,C语言仍然是首选,它的执行效率高,资源占用少,特别适合处理高并发的数据查询请求。

而前端使用Ajax异步请求,可以避免整页刷新,只更新数据部分,大大提升用户体验,这种组合既保持了传统C语言的高性能,又融入了现代Web的交互体验。

后端实现:C语言SQL分页核心代码

数据库连接准备

首先我们需要建立数据库连接,以MySQL为例:

#include <mysql/mysql.h>
MYSQL *conn;
MYSQL_RES *res;
MYSQL_ROW row;
void init_db() {
    conn = mysql_init(NULL);
    if (!mysql_real_connect(conn, "localhost", "user", "password", 
                           "database", 0, NULL, 0)) {
        fprintf(stderr, "%s\n", mysql_error(conn));
        exit(1);
    }
}

分页查询函数实现

关键的分页查询函数如下:

char* get_paged_data(int page, int page_size) {
    char query[256];
    int offset = (page - 1) * page_size;
    snprintf(query, sizeof(query), 
            "SELECT * FROM device_data LIMIT %d OFFSET %d", 
            page_size, offset);
    if (mysql_query(conn, query)) {
        fprintf(stderr, "%s\n", mysql_error(conn));
        return NULL;
    }
    res = mysql_use_result(conn);
    // 这里我们构建JSON格式的返回数据
    char *json_data = malloc(4096); // 根据实际情况调整大小
    char temp[512];
    strcpy(json_data, "{\"data\":[");
    int first = 1;
    while ((row = mysql_fetch_row(res)) != NULL) {
        if (!first) {
            strcat(json_data, ",");
        }
        first = 0;
        // 假设表有id,device_id,value三个字段
        snprintf(temp, sizeof(temp), 
                "{\"id\":%s,\"device_id\":\"%s\",\"value\":%s}", 
                row[0], row[1], row[2]);
        strcat(json_data, temp);
    }
    strcat(json_data, "]}");
    mysql_free_result(res);
    return json_data;
}

获取总记录数

分页通常需要知道总记录数来计算总页数:

int get_total_records() {
    if (mysql_query(conn, "SELECT COUNT(*) FROM device_data")) {
        fprintf(stderr, "%s\n", mysql_error(conn));
        return -1;
    }
    res = mysql_use_result(conn);
    if ((row = mysql_fetch_row(res)) != NULL) {
        int total = atoi(row[0]);
        mysql_free_result(res);
        return total;
    }
    mysql_free_result(res);
    return -1;
}

前端实现:Ajax异步请求分页数据

后端准备好了,现在看看前端如何通过Ajax获取分页数据:

数据分页 异步请求:基于Ajax的C语言SQL分页查询实现方法

// 当前页码和每页大小
let currentPage = 1;
const pageSize = 10;
// 加载分页数据
function loadPageData(page) {
    $.ajax({
        url: '/api/device_data',
        type: 'GET',
        data: {
            page: page,
            page_size: pageSize
        },
        dataType: 'json',
        beforeSend: function() {
            $('#loading-spinner').show();
        },
        success: function(response) {
            renderTable(response.data);
            updatePagination();
        },
        complete: function() {
            $('#loading-spinner').hide();
        },
        error: function(xhr, status, error) {
            console.error("加载数据失败:", error);
            alert('数据加载失败,请稍后重试');
        }
    });
}
// 渲染表格数据
function renderTable(data) {
    const $tableBody = $('#data-table tbody');
    $tableBody.empty();
    data.forEach(item => {
        $tableBody.append(`
            <tr>
                <td>${item.id}</td>
                <td>${item.device_id}</td>
                <td>${item.value}</td>
            </tr>
        `);
    });
}
// 更新分页控件
function updatePagination() {
    $.get('/api/device_data/total', function(total) {
        const totalPages = Math.ceil(total / pageSize);
        let paginationHtml = '';
        if (currentPage > 1) {
            paginationHtml += `<li class="page-item">
                <a class="page-link" href="#" onclick="changePage(${currentPage - 1})">上一页</a>
            </li>`;
        }
        for (let i = 1; i <= totalPages; i++) {
            const active = i === currentPage ? 'active' : '';
            paginationHtml += `<li class="page-item ${active}">
                <a class="page-link" href="#" onclick="changePage(${i})">${i}</a>
            </li>`;
        }
        if (currentPage < totalPages) {
            paginationHtml += `<li class="page-item">
                <a class="page-link" href="#" onclick="changePage(${currentPage + 1})">下一页</a>
            </li>`;
        }
        $('#pagination').html(paginationHtml);
    });
}
// 切换页码
function changePage(page) {
    currentPage = page;
    loadPageData(currentPage);
    return false; // 阻止默认链接行为
}
// 初始化加载第一页
$(document).ready(function() {
    loadPageData(1);
});

C语言后端接口实现

现在我们需要在C后端实现这两个API接口:

分页数据接口

void handle_device_data_request(struct mg_connection *conn, struct mg_http_message *hm) {
    // 解析查询参数
    char page_str[10], page_size_str[10];
    mg_http_get_var(&hm->query, "page", page_str, sizeof(page_str));
    mg_http_get_var(&hm->query, "page_size", page_size_str, sizeof(page_size_str));
    int page = page_str[0] ? atoi(page_str) : 1;
    int page_size = page_size_str[0] ? atoi(page_size_str) : 10;
    // 获取分页数据
    char *json_data = get_paged_data(page, page_size);
    // 发送响应
    mg_http_reply(conn, 200, "Content-Type: application/json\r\n", "%s", json_data);
    free(json_data);
}

总记录数接口

void handle_total_records_request(struct mg_connection *conn, struct mg_http_message *hm) {
    int total = get_total_records();
    if (total >= 0) {
        mg_http_reply(conn, 200, "Content-Type: application/json\r\n", 
                     "{\"total\":%d}", total);
    } else {
        mg_http_reply(conn, 500, "Content-Type: application/json\r\n", 
                     "{\"error\":\"Failed to get total records\"}");
    }
}

性能优化技巧

实际项目中,我们还需要考虑一些性能优化:

  1. SQL优化:确保分页查询的字段有适当的索引

    CREATE INDEX idx_device ON device_data(device_id);
  2. 连接池管理:避免每次请求都创建新连接

    // 初始化时创建连接池
    #define POOL_SIZE 10
    MYSQL *conn_pool[POOL_SIZE];
    // 使用时从池中获取可用连接
    MYSQL* get_db_connection() {
        for (int i = 0; i < POOL_SIZE; i++) {
            if (conn_pool[i] != NULL) {
                MYSQL *conn = conn_pool[i];
                conn_pool[i] = NULL;
                return conn;
            }
        }
        return NULL; // 所有连接都在使用中
    }
  3. 前端防抖:避免快速翻页时发送过多请求

    let debounceTimer;
    function changePage(page) {
        clearTimeout(debounceTimer);
        debounceTimer = setTimeout(() => {
            currentPage = page;
            loadPageData(currentPage);
        }, 300);
        return false;
    }
  4. 数据缓存:对热点数据进行缓存

    #include <time.h>
    typedef struct {
        char *data;
        time_t timestamp;
        int page;
    } PageCache;
    PageCache cache[100]; // 简单缓存100页
    char* get_cached_page(int page) {
        for (int i = 0; i < 100; i++) {
            if (cache[i].page == page && 
                (time(NULL) - cache[i].timestamp) < 60) { // 缓存60秒
                return strdup(cache[i].data);
            }
        }
        return NULL;
    }

实际应用中的注意事项

  1. 安全性:永远不要相信前端传来的参数

    // 检查page和page_size的合法性
    if (page < 1) page = 1;
    if (page_size < 1 || page_size > 100) page_size = 10; // 限制每页最大100条
  2. 错误处理:完善的错误处理机制

    void handle_device_data_request(struct mg_connection *conn, struct mg_http_message *hm) {
        // ... 参数解析 ...
        if (mysql_ping(conn) != 0) {
            mg_http_reply(conn, 500, "Content-Type: application/json\r\n", 
                         "{\"error\":\"Database connection lost\"}");
            return;
        }
        // ... 其余代码 ...
    }
  3. 跨域问题:如果前后端分离部署

    数据分页 异步请求:基于Ajax的C语言SQL分页查询实现方法

    mg_http_reply(conn, 200, 
                 "Content-Type: application/json\r\n"
                 "Access-Control-Allow-Origin: *\r\n", 
                 "%s", json_data);
  4. 内存管理:C语言需要特别注意内存泄漏

    void free_db_resources() {
        for (int i = 0; i < POOL_SIZE; i++) {
            if (conn_pool[i]) {
                mysql_close(conn_pool[i]);
                conn_pool[i] = NULL;
            }
        }
    }

扩展思考:更复杂的分页需求

实际项目中,分页需求可能更复杂:

  1. 条件过滤分页

    // 添加设备ID过滤
    snprintf(query, sizeof(query), 
            "SELECT * FROM device_data WHERE device_id='%s' LIMIT %d OFFSET %d", 
            device_id, page_size, offset);
  2. 排序分页

    // 按值降序排列
    snprintf(query, sizeof(query), 
            "SELECT * FROM device_data ORDER BY value DESC LIMIT %d OFFSET %d", 
            page_size, offset);
  3. 游标分页(适用于大型数据集):

    // 基于最后一条记录的ID
    snprintf(query, sizeof(query), 
            "SELECT * FROM device_data WHERE id > %d ORDER BY id LIMIT %d", 
            last_id, page_size);

通过C语言后端结合Ajax前端实现的分页查询,我们既保留了C语言的高性能优势,又获得了现代Web应用的流畅用户体验,这种方案特别适合嵌入式Web界面、物联网设备管理系统等场景。

实现过程中,关键是要处理好前后端的协作、SQL查询的优化以及内存管理等问题,随着数据量的增长,可能还需要考虑更复杂的分页策略和缓存机制。

希望这篇文章能为你实现自己的分页功能提供实用参考,如果有任何问题,欢迎在评论区交流讨论。

发表评论