【FlashDB】第二步 FlashDB 移植 STM32L475 使用QSPI驱动外部 flash W25Q64之 SFUD 移植_小石头有大内涵的博客-程序员宅基地_qspi驱动

技术标签: stm32  STM32程序设计  嵌入式硬件  arm  

第一步写好了FAL移植,那么进行第二步 SFUD 移植

【FlashDB】第一步 FlashDB 移植到 STM32L475 使用QSPI驱动外部 flash W25Q64之FAL移植

准备工作

1. SFUD 介绍

SFUD 是一款开源的串行 SPI Flash 通用驱动库。由于现有市面的串行 Flash 种类居多,各个 Flash
的规格及命令存在差异, SFUD 就是为了解决这些 Flash 的差异现状而设计,让我们的产品能够支持不同品牌及规格的
Flash,提高了涉及到 Flash 功能的软件的可重用性及可扩展性,同时也可以规避 Flash 缺货或停产给产品所带来的风险。 SFUD
是一款开源的串行 SPI Flash 通用驱动库。由于现有市面的串行 Flash 种类居多,各个 Flash 的规格及命令存在差异,
SFUD 就是为了解决这些 Flash 的差异现状而设计,让我们的产品能够支持不同品牌及规格的 Flash,提高了涉及到 Flash
功能的软件的可重用性及可扩展性,同时也可以规避 Flash 缺货或停产给产品所带来的风险。

  • 主要特点:支持 SPI/QSPI 接口、面向对象(同时支持多个 Flash 对象)、可灵活裁剪、扩展性强、支持 4 字节地址
  • 资源占用
    标准占用:RAM:0.2KB ROM:5.5KB
    最小占用:RAM:0.1KB ROM:3.6KB
  • 设计思路:
  1. 什么是 SFDP :它是 JEDEC (固态技术协会)制定的串行 Flash 功能的参数表标准,最新版 V1.6B (点击这里查看)。该标准规定了,每个 Flash 中会存在一个参数表,该表中会存放 Flash 容量、写粒度、擦除命令、地址模式等 Flash 规格参数。目前,除了部分厂家旧款 Flash 型号会不支持该标准,其他绝大多数新出厂的 Flash 均已支持 SFDP 标准。所以该库在初始化时会优先读取 SFDP 表参数。
  2. 不支持 SFDP 怎么办 :如果该 Flash 不支持 SFDP 标准,SFUD会查询配置文件 ( /sfud/inc/sfud_flash_def.h ) 中提供的 Flash 参数信息表 中是否支持该款Flash。如果不支持,则可以在配置文件中添加该款 Flash 的参数信息(添加方法详细见 2.5 添加库目前不支持Flash)。获取到了 Flash 的规格参数后,就可以实现对 Flash 的全部操作。

想实际了解请点击以下链接:
https://gitee.com/Armink/SFUD

2.打开STM32CubeMX配置单片机的QSPI相关驱动

配置如下图:

在这里插入图片描述

注意:红框中的Flash Size 一项中是可查询的flash地址位数减一
例如:我使用的是W25Q64,最大地址是0x800000,一共占据了24位,那么24-1就是Flash Size的长度,Flash Size最大到32位寻址

3. 将SFUD相关文件放入到MDK中

在这里插入图片描述
3.1 打开 sfud_port.c 文件,将一下内容替换

/*
 * This file is part of the Serial Flash Universal Driver Library.
 *
 * Copyright (c) 2018, zylx, <[email protected]>
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * 'Software'), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 * Function: Portable interface for each platform.
 * Created on: 2018-11-23
 */

#include <sfud.h>
#include <stdarg.h>
#include <stdio.h>
#include <stm32l4xx_hal.h>
#include <stm32l4xx_hal_gpio.h>
#include <string.h>

void sfud_log_info(const char *format, ...);
sfud_err qspi_send_then_recv(const void *send_buf, size_t send_length, void *recv_buf, size_t recv_length);
extern QSPI_HandleTypeDef hqspi;

typedef struct
{
    
    QSPI_HandleTypeDef *spix;
    GPIO_TypeDef *cs_gpiox;
    uint16_t cs_gpio_pin;
} spi_user_data, *spi_user_data_t;

static char log_buf[256];

void sfud_log_debug(const char *file, const long line, const char *format, ...);

static void spi_lock(const sfud_spi *spi)
{
    
    __disable_irq();
}

static void spi_unlock(const sfud_spi *spi)
{
    
    __enable_irq();
}

/**
 * SPI write data then read data
 */
static sfud_err spi_write_read(const sfud_spi *spi, const uint8_t *write_buf, size_t write_size, uint8_t *read_buf,
                               size_t read_size)
{
    
    sfud_err result = SFUD_SUCCESS;

    spi_user_data_t spi_dev = (spi_user_data_t) spi->user_data;

    if (write_size)
    {
    
        SFUD_ASSERT(write_buf);
    }
    if (read_size)
    {
    
        SFUD_ASSERT(read_buf);
    }

    /* reset cs pin */
    if (spi_dev->cs_gpiox != NULL)
        HAL_GPIO_WritePin(spi_dev->cs_gpiox, spi_dev->cs_gpio_pin, GPIO_PIN_RESET);

    if (write_size && read_size)
    {
    
        /* read data */
        qspi_send_then_recv(write_buf, write_size, read_buf, read_size);
    }
    else if (write_size)
    {
    
        /* send data */
        qspi_send_then_recv(write_buf, write_size, NULL, NULL);
    }

    /* set cs pin */
    if (spi_dev->cs_gpiox != NULL)
        HAL_GPIO_WritePin(spi_dev->cs_gpiox, spi_dev->cs_gpio_pin, GPIO_PIN_SET);

    return result;
}

/**
 * QSPI fast read data
 */
static sfud_err qspi_read(const struct __sfud_spi *spi, uint32_t addr, sfud_qspi_read_cmd_format *qspi_read_cmd_format, uint8_t *read_buf, size_t read_size)
{
    
    
    sfud_err result = SFUD_SUCCESS;
    QSPI_CommandTypeDef Cmdhandler;
    extern QSPI_HandleTypeDef hqspi;

    /* set cmd struct */
    Cmdhandler.Instruction = qspi_read_cmd_format->instruction;
    if(qspi_read_cmd_format->instruction_lines == 0)
    {
    
        Cmdhandler.InstructionMode = QSPI_INSTRUCTION_NONE;
    }else if(qspi_read_cmd_format->instruction_lines == 1)
    {
    
        Cmdhandler.InstructionMode = QSPI_INSTRUCTION_1_LINE;
    }else if(qspi_read_cmd_format->instruction_lines == 2)
    {
    
        Cmdhandler.InstructionMode = QSPI_INSTRUCTION_2_LINES;
    }else if(qspi_read_cmd_format->instruction_lines == 4)
    {
    
        Cmdhandler.InstructionMode = QSPI_INSTRUCTION_4_LINES;
    }
    
    Cmdhandler.Address = addr;
    Cmdhandler.AddressSize = QSPI_ADDRESS_24_BITS;
    if(qspi_read_cmd_format->address_lines == 0)
    {
    
        Cmdhandler.AddressMode = QSPI_ADDRESS_NONE;
    }else if(qspi_read_cmd_format->address_lines == 1)
    {
    
        Cmdhandler.AddressMode = QSPI_ADDRESS_1_LINE;
    }else if(qspi_read_cmd_format->address_lines == 2)
    {
    
        Cmdhandler.AddressMode = QSPI_ADDRESS_2_LINES;
    }else if(qspi_read_cmd_format->address_lines == 4)
    {
    
        Cmdhandler.AddressMode = QSPI_ADDRESS_4_LINES;
    }

    Cmdhandler.AlternateBytes = 0;
    Cmdhandler.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
    Cmdhandler.AlternateBytesSize = 0;

    Cmdhandler.DummyCycles = qspi_read_cmd_format->dummy_cycles;

    Cmdhandler.NbData = read_size;
    if(qspi_read_cmd_format->data_lines == 0)
    {
    
        Cmdhandler.DataMode = QSPI_DATA_NONE;
    }else if(qspi_read_cmd_format->data_lines == 1)
    {
    
        Cmdhandler.DataMode = QSPI_DATA_1_LINE;
    }else if(qspi_read_cmd_format->data_lines == 2)
    {
    
        Cmdhandler.DataMode = QSPI_DATA_2_LINES;
    }else if(qspi_read_cmd_format->data_lines == 4)
    {
    
        Cmdhandler.DataMode = QSPI_DATA_4_LINES;
    }

    Cmdhandler.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;
    Cmdhandler.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
    Cmdhandler.DdrMode = QSPI_DDR_MODE_DISABLE;
    Cmdhandler.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;
    HAL_QSPI_Command(&hqspi, &Cmdhandler, 5000);

    if (HAL_QSPI_Receive(&hqspi, read_buf, 5000) != HAL_OK)
    {
    
        sfud_log_info("qspi recv data failed(%d)!", hqspi.ErrorCode);
        hqspi.State = HAL_QSPI_STATE_READY;
        result = SFUD_ERR_READ;
    }

    return result;
}

/* about 100 microsecond delay */
static void retry_delay_100us(void)
{
    
    uint32_t delay = 2400;
    while (delay--);
}

static spi_user_data spi1 = {
     .spix = &hqspi, .cs_gpiox = NULL, .cs_gpio_pin = NULL };

sfud_err sfud_spi_port_init(sfud_flash *flash)
{
    
    sfud_err result = SFUD_SUCCESS;

    switch (flash->index)
    {
    
        case SFUD_W25_DEVICE_INDEX:
        {
    
            /* set the interfaces and data */
            flash->spi.wr = spi_write_read;
            flash->spi.qspi_read = qspi_read;
            flash->spi.lock = spi_lock;
            flash->spi.unlock = spi_unlock;
            flash->spi.user_data = &spi1;
            /* about 100 microsecond delay */
            flash->retry.delay = retry_delay_100us;
            /* adout 60 seconds timeout */
            flash->retry.times = 60 * 10000;

            break;
        }
    }

    return result;
}

/**
 * This function is print debug info.
 *
 * @param file the file which has call this function
 * @param line the line number which has call this function
 * @param format output format
 * @param ... args
 */
void sfud_log_debug(const char *file, const long line, const char *format, ...)
{
    
    va_list args;

    /* args point to the first variable parameter */
    va_start(args, format);
    printf("[SFUD](%s:%ld) ", file, line);
    /* must use vprintf to print */
    vsnprintf(log_buf, sizeof(log_buf), format, args);
    printf("%s\r\n", log_buf);
    va_end(args);
}

/**
 * This function is print routine info.
 *
 * @param format output format
 * @param ... args
 */
void sfud_log_info(const char *format, ...)
{
    
    va_list args;

    /* args point to the first variable parameter */
    va_start(args, format);
    printf("[SFUD]");
    /* must use vprintf to print */
    vsnprintf(log_buf, sizeof(log_buf), format, args);
    printf("%s\r\n", log_buf);
    va_end(args);
}

/**
 * This function can send or send then receive QSPI data.
 */
sfud_err qspi_send_then_recv(const void *send_buf, size_t send_length, void *recv_buf, size_t recv_length)
{
    
    assert_param(send_buf);
    assert_param(recv_buf);
    assert_param(send_length != 0);

    QSPI_CommandTypeDef Cmdhandler;
    unsigned char *ptr = (unsigned char *)send_buf;
    size_t count = 0;
    sfud_err result = SFUD_SUCCESS;

    /* get instruction */
    Cmdhandler.Instruction = ptr[0];
    Cmdhandler.InstructionMode = QSPI_INSTRUCTION_1_LINE;
    count++;

    /* get address */
    if (send_length > 1)
    {
    
        if (send_length >= 4)
        {
    
            /* address size is 3 Byte */
            Cmdhandler.Address = (ptr[1] << 16) | (ptr[2] << 8) | (ptr[3]);
            Cmdhandler.AddressSize = QSPI_ADDRESS_24_BITS;
            count += 3;
        }
        else
        {
    
            return SFUD_ERR_READ;
        }
        Cmdhandler.AddressMode = QSPI_ADDRESS_1_LINE;
    }
    else
    {
    
        /* no address stage */
        Cmdhandler.Address = 0 ;
        Cmdhandler.AddressMode = QSPI_ADDRESS_NONE;
        Cmdhandler.AddressSize = 0;
    }

    Cmdhandler.AlternateBytes = 0;
    Cmdhandler.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
    Cmdhandler.AlternateBytesSize = 0;
    
    Cmdhandler.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;
    Cmdhandler.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
    Cmdhandler.DdrMode = QSPI_DDR_MODE_DISABLE;
    Cmdhandler.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;

    if (send_buf && recv_buf)
    {
    
        /* recv data */
        /* set dummy cycles */
        if (count != send_length)
        {
    
            Cmdhandler.DummyCycles = (send_length - count) * 8;
        }
        else
        {
    
            Cmdhandler.DummyCycles = 0;
        }

        /* set recv size */
        Cmdhandler.DataMode = QSPI_DATA_1_LINE;
        Cmdhandler.NbData = recv_length;
        HAL_QSPI_Command(&hqspi, &Cmdhandler, 5000);
        
        if (recv_length != 0)
        {
    
            if (HAL_QSPI_Receive(&hqspi, recv_buf, 5000) != HAL_OK)
            {
    
                sfud_log_info("qspi recv data failed(%d)!", hqspi.ErrorCode);
                hqspi.State = HAL_QSPI_STATE_READY;
                result = SFUD_ERR_READ;
            }
        }

        return result;
    }
    else
    {
    
        /* send data */
        /* set dummy cycles */
        Cmdhandler.DummyCycles = 0;

        /* determine if there is data to send */
        if (send_length - count > 0)
        {
    
            Cmdhandler.DataMode = QSPI_DATA_1_LINE;
        }
        else
        {
    
            Cmdhandler.DataMode = QSPI_DATA_NONE;
        }

        /* set send buf and send size */
        Cmdhandler.NbData = send_length - count;
        HAL_QSPI_Command(&hqspi, &Cmdhandler, 5000);
        
        if (send_length - count > 0)
        {
    
            if (HAL_QSPI_Transmit(&hqspi, (uint8_t *)(ptr + count), 5000) != HAL_OK)
            {
    
                sfud_log_info("qspi send data failed(%d)!", hqspi.ErrorCode);
                hqspi.State = HAL_QSPI_STATE_READY;
                result = SFUD_ERR_WRITE;
            }
        }
        
        return result;
    }
}

3.2 注意188行-214行

在这里插入图片描述

注意:箭头所指是,HAL库初始化QSPI时所定义的宏,根据实际情况填写

3.3 打开 sfud_cfg.h 文件

在这里插入图片描述

.name flash 设备名字
.spi.name 驱动名字,无关紧要,填写对应的驱动方式即可
.chip 片子的驱动信息,重要,要填对,具体的驱动信息在 sfud_flash_def.h 122行,复制粘贴即可

至此SFUD移植完成。

4. SFUD 测试代码

void sfud_demo(uint32_t addr, size_t size, uint8_t *data);

#define SFUD_DEMO_TEST_BUFFER_SIZE                     1024
static uint8_t sfud_demo_test_buf[SFUD_DEMO_TEST_BUFFER_SIZE];
 
 int main()
 {
    
    if (sfud_init() == SFUD_SUCCESS)
    {
    
        /* enable qspi fast read mode, set four data lines width */
        sfud_qspi_fast_read_enable(sfud_get_device(SFUD_W25_DEVICE_INDEX), 4);
        sfud_demo(0, sizeof(sfud_demo_test_buf), sfud_demo_test_buf);
    }
    while(1);
 }

/**
 * SFUD demo for the first flash device test.
 *
 * @param addr flash start address
 * @param size test flash size
 * @param size test flash data buffer
 */
void sfud_demo(uint32_t addr, size_t size, uint8_t *data)
{
    
    sfud_err result = SFUD_SUCCESS;
    extern sfud_flash *sfud_dev;
    const sfud_flash *flash = sfud_get_device(SFUD_W25_DEVICE_INDEX);
    size_t i;
    /* prepare write data */
    for (i = 0; i < size; i++)
    {
    
        data[i] = i;
    }
    /* erase test */
    result = sfud_erase(flash, addr, size);
    if (result == SFUD_SUCCESS)
    {
    
        printf("Erase the %s flash data finish. Start from 0x%08X, size is %zu.\r\n", flash->name, addr, size);
    }
    else
    {
    
        printf("Erase the %s flash data failed.\r\n", flash->name);
        return;
    }
    /* write test */
    result = sfud_write(flash, addr, size, data);
    if (result == SFUD_SUCCESS)
    {
    
        printf("Write the %s flash data finish. Start from 0x%08X, size is %zu.\r\n", flash->name, addr, size);
    }
    else
    {
    
        printf("Write the %s flash data failed.\r\n", flash->name);
        return;
    }
		
		memset(data, 0, size);
    /* read test */
    result = sfud_read(flash, addr, size, data);
    if (result == SFUD_SUCCESS)
    {
    
        printf("Read the %s flash data success. Start from 0x%08X, size is %zu. The data is:\r\n", flash->name, addr, size);
        printf("Offset (h) 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F\r\n");
        for (i = 0; i < size; i++)
        {
    
            if (i % 16 == 0)
            {
    
                printf("[%08X] ", addr + i);
            }
            printf("%02X ", data[i]);
            if (((i + 1) % 16 == 0) || i == size - 1)
            {
    
                printf("\r\n");
            }
        }
        printf("\r\n");
    }
    else
    {
    
        printf("Read the %s flash data failed.\r\n", flash->name);
    }
    /* data check */
    for (i = 0; i < size; i++)
    {
    
        if (data[i] != i % 256)
        {
    
            printf("Read and check write data has an error. Write the %s flash data failed.\r\n", flash->name);
            break;
        }
    }
    if (i == size)
    {
    
        printf("The %s flash test is success.\r\n", flash->name);
    }
}
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/shileiwu0505/article/details/123029348

智能推荐

HTML5秘籍---第一章(HTML5简介)_small-dragon的博客-程序员宅基地

前端菜鸟小COPY《HTML5秘籍》中的一些精髓

【过程2】——小白养成记_不达目标不改名的mark的博客-程序员宅基地

【背景】    本以为成长不会如此之快,但是没想到却超乎想象的快,那些变化,那些激动人心的时刻,促进我的脑细胞将这些遇见、经历、变化、惊讶、思考以总结的形式记录下来,为自己后面的个人事业方向做一些初步的铺垫。【过程】    改变的过程原来还可以如此美妙,首先细数一下我看到的成果:      1.连续41天百词斩单词积累      2.半年左右时间,学习了高中所有课程的所有课本以...

jbpm hibernate.cfg.xml 连接mysql配置_JBPM的jbpm.hibernate.cfg.xml有关配置_是桃大的博客-程序员宅基地

org.hibernate.dialect.SQLServerDialectcom.microsoft.sqlserver.jdbc.SQLServerDriverjdbc:sqlserver://192.168.1.233:1433;databasename=swoa;SelectMethod=cursorsaahswoa1115create-droptrue很明晰的看出create-drop看...

深入理解Linux内存子系统_公众号:极客重生的博客-程序员宅基地

原文:https://cloud.tencent.com/developer/article/10056重新整理更新:极客重生导语linux 内存是后台开发人员,需要深入了解的计算机资源...

html设置多列布局间隙,css设置多列等高布局的方法示例_22子的博客-程序员宅基地

1.真实等高布局flex技术点:弹性盒子布局flex,默认值就是自带等高布局的特点。定义flex布局的时候,有一些默认值。flex-direction属性定义主轴的方向。默认值为row,一般是水平显示。flex容器的主轴被定义为与文本方向相同。主轴起点和主轴终点与内容方向相同。align-item属性定义flex子项在flex容器的当前行的侧轴(纵轴)方向上的对齐方式。默认值为stretch,元素...

android 官方增量更新,Android-App增量更新的使用姿势_爱情教练晋美的博客-程序员宅基地

简述增量更新,根据字面理解,就是下载增加的那部分来达到更新的目的,实际就是这个意思。原理用一个旧的Apk安装与一个新的Apk安装包使用 bsdiff工具 ,执行命令生成一个差异文件,此差异文件就是我们修改需要更新下载的那部分。引入代码及so文件首先,根据你的系统的架构选择不同的so文件放到你的工程中接着,需要把加载so文件的Java类引入到你的工程中,引入时,需注意,不能修改这个类的包名。到此,增...

随便推点

Net Core微服务:什么是微服务?微服务为什么选择net Core? 创建Core控制台,创建Core api,_tiz198183的博客-程序员宅基地_netcore微服务是什么

一、微服务不同模块放到不同进程/服务器上,模块之间通过网络通信进行协作。(主要用在访问量大,多台服务器)。二、微服务为什么选择net Core,.net Core可以跨平台。微服务中,服务的通讯有2种主要形式:1、Restful :json格式传输(http传输)2、二进制 RPC三、Visual Studio 20171、新建core控制平台(1)、新...

linux版本fedora,技术|初级:如何更新 Fedora Linux 系统_weixin_39928461的博客-程序员宅基地

本快速教程介绍了更新 Fedora Linux 安装的多种方法。安装 Fedora 之后,我做的第一件事就是尝试安装一些软件。我打开软件中心,发现该软件中心已“损坏”。 我无法从中安装任何应用程序。我不确定我的系统出了什么问题。在团队内部讨论时,Abhishek 建议我先更新系统。我更新了,更新后一切恢复正常。更新 Fedora 系统后,软件中心也能正常工作了。有时我们一直尝试解决我们所面临的问题...

访问修饰符private/protected/默认(friendly)protected 方法重写,重载_weixin_30765505的博客-程序员宅基地

访问修饰符    本类    同包    子类    其他private      True    False    False   False   获取 get set方法默认(friendly)   True    True    False    Falseprotected    True    True    True    Falsepubli...

Android版添加phonegap-读取手机短信插件教程_weixin_33912445的博客-程序员宅基地

2019独角兽企业重金招聘Python工程师标准&gt;&gt;&gt; ...

IList、List、BindingList与dataGridView的绑定问题。_weixin_30906701的博客-程序员宅基地

当在业务层BLL返回IList&lt;T&gt;或者List&lt;T&gt;集合,绑定到dataGridView控件后,dataGridView控件不能删除一行,也不能在末端新增一行,真的奇怪。但用BindingList&lt;T&gt;就可以了,所以遇到这种需要删除、新增操作时,可以在UI中把把业务层的IList&lt;T&gt;转换成BindingList&lt;T&gt;,如:...

mysql 查找相似数据_MySQL慢查询优化 | 联结原理_weixin_39648492的博客-程序员宅基地

点击上方蓝色字体,选择“设为星标”回复”资源“获取更多资源大数据技术与架构点击右侧关注,大数据开发领域最强公众号!暴走大数据点击右侧关注,暴走大数据!前段时间笔者开发某个项目遇到了MySQL性能问题,每张表的数据量都在五千万以上,个别表数据量甚至在一个亿以上,在开发的过程中遇到了非常多的数据库性能优化难点,笔者在开发过程中查询了很多资料,很多查询语句也在优化过程中取得了比较好的效果。笔者...

推荐文章

热门文章

相关标签