千万数据的连续ID表,快速读取其中指定的某1000条数据?

有这样一个需求:一张上千万数据的表,结构很简单:ID是自增的,你怎么快速读取其中指定的某1000条数据,比如100万到100万零1000?

这个需求其实很简单,因为是自增型ID,可能分两种状况:有聚集索引或Heap,如果是后者,我想用ID和新增时间组建非聚集索引。效果应该相差不大。

于是动手,过程如下:

一、准备测试数据:

基本测试环境:
邀月工作室

插入1000万测试数据:

/***************创建千万级测试数据库***********
****************downmoon 3w@live.cn ***************/

Create database HugeData_10Millons
go
use HugeData_10Millons
go


/***************创建测试表*********************
****************downmoo  3w@live.cn ***************/

IF NOT OBJECT_ID('[bigTable]') IS NULL
    DROP TABLE [bigTable]
GO
Create table bigTable
(PID int identity(1,1) primary key not null
,PName nvarchar(100) null
,AddTime dateTime null
,PGuid Nvarchar(40)
)
go

truncate table [bigTable]


/***************创建第一个25万测试数据*********************
****************downmoo  3w@live.cn ***************/

declare @d datetime 
set @d=getdate() 

declare @i int
set @i=1
while @i<=250000
begin
    insert into [bigTable]
    select cast(datepart(ms,getdate()) as nvarchar(3))+Replicate('A',datepart(ss,getdate()))
    ,getdate()
    ,NewID()
    set @i=@i+1
end

select [语句执行花费时间(毫秒)]=datediff(ms,@d,getdate()) 

/*
语句执行花费时间(毫秒)
94750
*/

/***************创建第二个25万测试数据*********************
****************downmoo  3w@live.cn ***************/

declare @d datetime 
set @d=getdate() 

declare @i int
set @i=1
while @i<=250000
begin
    insert into [bigTable]
    select cast(datepart(ms,getdate()) as nvarchar(3))+Replicate(Substring(cast(NEWID() as nvarchar(40)),1,6),3)
    ,getdate()
    ,NewID()
    set @i=@i+1
end

select [语句执行花费时间(毫秒)]=datediff(ms,@d,getdate()) 

/*
语句执行花费时间(毫秒)
115640
*/

/***************创建900万测试数据*********************
****************downmoo  3w@live.cn ***************/

declare @d datetime 
set @d=getdate() 

declare @i int
set @i=1
while @i<=9000000
begin
    insert into [bigTable]
    select replicate('X',ROUND((RAND()* 60),0) )+cast(datepart(ms,getdate()) as nvarchar(3))
    ,getdate()
    ,NewID()
    set @i=@i+1
end

select [语句执行花费时间(毫秒)]=datediff(ms,@d,getdate()) 
/*
语句执行花费时间(毫秒)
3813686
*/

/***************创建最后50万测试数据*********************
****************downmoo  3w@live.cn ***************/

declare @d datetime 
set @d=getdate() 

declare @i int
set @i=1
while @i<=500000
begin
    insert into [bigTable]
    select replicate('X',ROUND((RAND()* 60),0) )+cast(NewID() as nvarchar(40))
    ,getdate()
    ,NewID()
    set @i=@i+1
end

select [语句执行花费时间(毫秒)]=datediff(ms,@d,getdate()) 
/*
语句执行花费时间(毫秒)
207436
*/

/*
检查数量
select count(1) from dbo.bigTable
----------10000000
清除日志
DUMP TRANSACTION HugeData_10Millons WITH NO_LOG
BACKUP LOG HugeData_10Millons WITH NO_LOG
DBCC SHRINKDATABASE(HugeData_10Millons)

*/
完成后,数据文件大小如下:

邀月工作室

二、创建一个存储过程用于测试:

/***************查中间某段1000条顺序数据********************* ****************downmoo 3w@live.cn ***************/ Create procedure GetTop1000RecordsByRange (@begin int ,@end int ) as select top 1000 * from [bigTable] where PID between @begin and @end go

邀月说明:其实,加不加top对查询并没有影响。后面的测试证实了这一点。

测试语句:

declare @d datetime set @d=getdate() exec GetTop100RecordsByRange 1000000,10001000 select [语句执行花费时间(毫秒)]=datediff(ms,@d,getdate())

此时,由于SQL Server默认为主键PID创建了聚集索引,查询速度比较理想,平均为0-16毫秒之间,更接近于0

查询计划也如我所料:
邀月工作室

而如果以Pguid作为聚集索引键,查询计划如下:
邀月工作室

如果以AddTime作为聚集索引键,查询计划:
邀月工作室

三、修改聚集索引,以检查查询速度

/*删除系统自动创建的聚集索引 */ ALTER TABLE [dbo].[bigTable] DROP CONSTRAINT [PK__bigTable__7C8480AE] go /*创建一个非聚集索引 在PID和addtime字段 */ CREATE NONCLUSTERED INDEX bigTable_NoClusIdx ON [bigTable]([AddTime] ASC,[PID] ASC); go DROP Index [bigTable_NoClusIdx] on dbo.[bigTable] /*创建一个非聚集索引 在PID字段 */ Create NONCLUSTERED INDEX bigTable_NoclusIdx ON [bigTable](PID); go DROP Index [bigTable_NoClusIdx] on dbo.[bigTable] /*创建一个非聚集索引 在AddTime字段 */ CREATE NONCLUSTERED INDEX bigTable_NoclusIdx ON [bigTable](AddTime); go DROP Index [bigTable_NoClusIdx] on dbo.[bigTable] /*创建一个非聚集索引 在GUID字段 */ CREATE NONCLUSTERED INDEX bigTable_NoclusIdx ON [bigTable](PGuid); go DROP Index [bigTable_NoClusIdx] on dbo.[bigTable] /*创建一个聚集索引 在GUID字段 */ CREATE CLUSTERED INDEX bigTable_ClusIdx ON [bigTable](PGuid); go DROP Index [bigTable_ClusIdx] on dbo.[bigTable] /*创建一个聚集索引 在addTime字段 */ CREATE CLUSTERED INDEX bigTable_ClusIdx ON [bigTable](AddTime); go DROP Index [bigTable_ClusIdx] on dbo.[bigTable] /*创建一个聚集索引 在PID字段 */ CREATE CLUSTERED INDEX bigTable_ClusIdx ON [bigTable](PID); go


测试结果有些令我意外:

1、在没有聚集索引的前提下,无论在GUID,AddTime,PID创建非聚集索引,查询的速度均相差甚远。平均在200毫秒以上,并且此时每次查询均在10秒以上。这与查询的计划缓存有关。

2、在创建聚集索引时,性能PID>AddTime>PGuid,但总体相差不明显。

四、检查索引存储内部

使用微软未公开的一个命令DBCC IND

DBCC IND (HugeData_10Millons, bigTable, -1);

结果约有21万个数据页:(211985 row(s) affected)

为了更方便找出根页(Root Page),我们使用一个表来存放DBCC IND的查询结果:

IF OBJECTPROPERTY(object_id('sp_tablepages'), 'IsUserTable') IS NOT NULL DROP TABLE sp_tablepages; go CREATE TABLE sp_tablepages ( PageFID tinyint, PagePID int, IAMFID tinyint, IAMPID int, ObjectID int, IndexID tinyint, PartitionNumber tinyint, PartitionID bigint, iam_chain_type varchar(30), PageType tinyint, IndexLevel tinyint, NextPageFID tinyint, NextPagePID int, PrevPageFID tinyint, PrevPagePID int, CONSTRAINT sp_tablepages_PK PRIMARY KEY (PageFID, PagePID) ); go --TRUNCATE TABLE sp_tablepages; INSERT sp_tablepages EXEC ('DBCC IND (HugeData_10Millons, bigTable, 1)'); go SELECT IndexLevel , PageFID , PagePID , PrevPageFID , PrevPagePID , NextPageFID , NextPagePID FROM sp_tablepages ORDER BY IndexLevel DESC, PrevPagePID; GO

假定我们要找PID为100000的记录。附查找过程如下:

邀月工作室
https://i-blog.csdnimg.cn/blog_migrate/bebff5c31d42792fa8012552ea4977a6.png

邀月工作室

邀月工作室

小结:

1、一个聚集索引的叶级正好就是数据自身,所以当一个聚集索引被创建时,表中数据被复制并依据聚集键排序,聚集索引被逻辑维护而不是物理维护。这样,查询时通过逻辑扫描可以很快找到某行所在的索引页,进而找出连续的1000条记录所在的页。

2、 对于一个非聚集索引来说,如果是Heap,行的标识就是它们的物理行标识(RID);如果是聚集表,则为聚集健,这个值称为书签值(bookmaark value),它和索引键、包含性列一起组成了非聚集索引的叶级。另外,在B树查找非叶级的页时,将可能不得不通过指向子页的指针进行物理定位,这可能会 增加查询的时间。还有,非聚集索引仅仅包含被索引定义的数据,对于没有在索引中定义的数据,可能需要在物理行进行一个书签查找(bookmark lookup)。

3、对于大数据量的查询,建立聚集索引是必须的。如果查询以ID序列为主,可以直接在标识列建立聚集索引。如果查询以时间段为主,则可以考虑用时间和标识列建聚集索引。

以上结论谨供参考,欢迎交流。

关于索引的物理存储与查询,请查看:《Microsoft Sql server 2008 Internals》读书笔记--第六章Indexes:Internals and Management(3)

邀月注:本文版权由邀月 和优快云共同所 有,转载请注明出处。
助人等于自助! 3w@live.cn

   



<think>好的,用户之前已经询问过QSPI中的Dummy Bytes,现在他们想要一个关于ZYNQ使用QSPI驱动读取指定地址FLASH数据的Demo。我需要先理解他们的需求。用户可能是在进行嵌入式开发,特别是在使用Xilinx的ZYNQ SoC,需要实际操作示例来帮助他们实现QSPI Flash的读取功能。 首先,用户可能对ZYNQ的QSPI控制器不太熟悉,或者需要具体的代码示例来参考。他们可能已经了解理论部分,比如Dummy Bytes的作用,现在需要实际的代码实现。因此,我应该提供一个基于Vivado和SDK的示例,因为这是Xilinx开发流程中常用的工具。 接下来,我需要考虑ZYNQ的硬件配置。QSPI Flash通常连接到ZYNQ的QSPI控制器,用户需要在Vivado中配置硬件,并生成对应的硬件平台。软件部分则需要使用Xilinx SDK或Vitis来编写驱动程序。Xilinx提供了XQspiPs驱动程序,这可能是一个关键点。 用户可能需要了解如何初始化QSPI控制器、发送读取命令、处理地址和Dummy Bytes,以及读取数据。在代码示例中,需要包含这些步骤,并注释清楚每个部分的作用。例如,在Fast Read Quad I/O模式下,命令码可能是0xEB,地址通常是24位或32位,后面跟着Dummy Bytes,然后读取数据。 还要考虑不同的Flash型号可能会有不同的Dummy Bytes需求,例如Winbond的W25Q系列需要1个Dummy Byte,而其他型号可能不同。用户可能需要根据自己使用的Flash型号调整代码中的参数,比如Dummy Bytes的数量和命令码。 另外,需要注意QSPI控制器的配置,比如是否启用Quad模式,时钟频率的设置等。这些配置可能影响数据传输的稳定性和速度。在代码示例中,初始化部分需要正确配置这些参数,确保硬件和软件的兼容性。 用户可能还会遇到问题,比如数据读取不正确,这时候需要检查地址是否正确,Dummy Bytes的数量是否符合Flash规格,以及信号线连接是否正常。在注意事项中,需要提醒用户查阅Flash的数据手册,确认具体的命令和时序要求。 最后,代码示例应该简洁明了,突出关键步骤,如初始化、发送命令、处理地址和Dummy Bytes、读取数据等。同时,需要说明如何编译和运行代码,可能还需要提到调试方法,比如使用逻辑分析仪或打印调试信息。 总结来说,用户需要一个完整的、可操作的Demo,涵盖硬件配置和软件驱动,帮助他们快速实现QSPI Flash的数据读取功能。同时,需要提醒他们根据实际硬件和Flash型号调整参数,确保代码的正确性和稳定性。</think>以下是基于 **Xilinx ZYNQ 平台**使用 QSPI 驱动读取 Flash 指定地址数据的示例代码及详细步骤说明。代码以 **C 语言**实现,基于 **Vivado SDK** 或 **Vitis** 开发环境,使用 **XQspiPs 驱动库**。 --- ### **1. 硬件配置(Vivado)** #### **(1) 添加 QSPI 外设** 1. 在 Vivado Block Design 中,添加 **ZYNQ Processing System**。 2. 在 ZYNQ 配置界面,启用 **Quad SPI Flash** 外设: - 选择 `Quad SPI Flash` 接口模式(Standard/Enhanced)。 - 配置 Flash 参数(如总线宽度、时钟频率)。 #### **(2) 引脚约束** 根据开发板 QSPI Flash 的物理连接,确认引脚分配(如 `QSPI_CLK`, `QSPI_IO[0:3]`)。 --- ### **2. 软件代码(SDK/Vitis)** #### **(1) 关键函数与流程** ```c #include "xqspips.h" // QSPI驱动头文件 #include "xil_printf.h" #define QSPI_DEVICE_ID XPAR_XQSPIPS_0_DEVICE_ID // QSPI设备ID #define FLASH_ADDRESS 0x00100000 // 目标Flash地址 #define DATA_LENGTH 256 // 读取数据长度 XQspiPs QspiInstance; // QSPI实例 u8 ReadBuffer[DATA_LENGTH]; // 数据接收缓冲区 int main() { int Status; XQspiPs_Config *QspiConfig; // 初始化QSPI控制器 QspiConfig = XQspiPs_LookupConfig(QSPI_DEVICE_ID); Status = XQspiPs_CfgInitialize(&QspiInstance, QspiConfig, QspiConfig->BaseAddress); if (Status != XST_SUCCESS) { xil_printf("QSPI Initialization Failed\r\n"); return XST_FAILURE; } // 设置QSPI时钟频率(例如:100 MHz) XQspiPs_SetClkPrescaler(&QspiInstance, XQSPIPS_CLK_PRESCALE_8); // 假设输入时钟为800MHz,分频后100MHz // 切换到Quad模式(若Flash支持) XQspiPs_SetOptions(&QspiInstance, XQSPIPS_FORCE_SSELECT_OPTION | XQSPIPS_MANUAL_START_OPTION); XQspiPs_SetSlaveSelect(&QspiInstance, 0); // 选择片选信号 // 发送读取命令(以Fast Read Quad I/O为例) u8 Command[8]; // 命令+地址+Dummy Bytes u32 CmdSize = 0; Command[CmdSize++] = 0xEB; // Fast Read Quad I/O指令 Command[CmdSize++] = (FLASH_ADDRESS >> 16) & 0xFF; // 地址高位 Command[CmdSize++] = (FLASH_ADDRESS >> 8) & 0xFF; Command[CmdSize++] = FLASH_ADDRESS & 0xFF; Command[CmdSize++] = 0x00; // Dummy Byte(根据Flash手册设置) // 启动传输(发送命令+地址,接收数据) XQspiPs_PolledTransfer(&QspiInstance, Command, ReadBuffer, CmdSize + DATA_LENGTH); // 打印读取数据 xil_printf("Read Data:\r\n"); for (int i = 0; i < DATA_LENGTH; i++) { xil_printf("%02X ", ReadBuffer[i + CmdSize]); // 跳过命令段,仅显示有效数据 if ((i + 1) % 16 == 0) xil_printf("\r\n"); } return XST_SUCCESS; } ``` --- ### **3. 代码解析** #### **(1) 初始化 QSPI 控制器** - `XQspiPs_CfgInitialize()`:初始化 QSPI 控制器,加载配置。 - `XQspiPs_SetClkPrescaler()`:设置时钟分频,控制通信速率。 #### **(2) 发送读取命令** - **指令格式**: `0xEB`(Fast Read Quad I/O) + **24位地址** + **Dummy Byte**(例如 `0x00`)。 - **传输函数**: `XQspiPs_PolledTransfer()` 实现阻塞式传输,发送命令并接收数据。 #### **(3) 数据接收** - `ReadBuffer` 包含命令、地址、Dummy Bytes 和有效数据,需跳过前 `CmdSize` 字节获真实数据。 --- ### **4. 关键配置项** | **参数** | **说明** | |------------------|-------------------------------------------------------------------------| | `QSPI_DEVICE_ID` | 对应 Vivado 中 QSPI 外设的实例号(通常为 `XPAR_XQSPIPS_0_DEVICE_ID`)。 | | `FLASH_ADDRESS` | 目标 Flash 的物理地址(需对齐 Flash 页/扇区)。 | | `DATA_LENGTH` | 需读取的字节数(不超过 Flash 页大小)。 | | **Dummy Byte** | 根据 Flash 手册设置(如 Winbond W25Q 需 1 Byte)。 | --- ### **5. 实际调试步骤** 1. **硬件连接**: - 确保 ZYNQ 开发板的 QSPI Flash 引脚正确连接。 - 确认 Flash 供电正常。 2. **Flash 型号适配**: - 修改 `Command[0]` 中的指令码(如 Micron Flash 可能使用 `0xEC`)。 - 调整 Dummy Bytes 数量(参考 Flash 数据手册)。 3. **逻辑分析仪验证**: - 抓 QSPI 总线波形,确认命令、地址、Dummy Bytes 和数据时序是否符合预期。 --- ### **6. 注意事项** 1. **Flash 状态检查**: 在读取前,需检查 Flash 是否处于空闲状态(通过 `Read Status Register` 命令)。 2. **地址对齐**: Flash 读取通常不需要对齐,但跨页读取需处理地址边界。 3. **性能优化**: - 使用 DMA 传输代替轮询模式(调用 `XQspiPs_DmaTransfer()`)。 - 启用 XIP(eXecute In Place)模式直接执行 Flash 中的代码。 --- ### **7. 扩展功能** #### **(1) 多扇区连续读取** ```c // 调整地址和数据长度 #define FLASH_ADDRESS 0x00200000 #define DATA_LENGTH 4096 // 读取4KB数据 ``` #### **(2) 中断模式传输** ```c // 初始化中断控制器 XScuGic_InterruptInit(); // 注册QSPI中断回调函数 XQspiPs_SetStatusHandler(&QspiInstance, NULL, QspiHandler); // 启动中断传输 XQspiPs_InterruptTransfer(&QspiInstance, Command, ReadBuffer, CmdSize + DATA_LENGTH); ``` --- 通过以上代码和配置,您可以在 ZYNQ 平台上实现 QSPI Flash 的指定地址数据读取。实际开发中需结合具体 Flash 手册调整时序参数。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值