在 C# 应用程序的 MySQL 数据库中,简单 select 查询在非常大的 table 中需要更多时间
Simple select query takes more time in very large table in MySQL database in C# application
我在 ASP.NET 的 C# Web 应用程序中使用 MySQL 数据库。 MySQL 服务器版本为 5.7,PC 内存为 8 GB。当我在 MySQL 数据库 table 中执行 select 查询时,执行需要更多时间;一个简单的 select 查询需要 大约 42 秒 。 table 中的 1 crorerecord(1000 万条记录)。 我还为 table 建立了索引。我该如何解决这个问题?
以下是我的table结构。
CREATE TABLE `smstable_read` (
`MessageID` int(11) NOT NULL AUTO_INCREMENT,
`ApplicationID` int(11) DEFAULT NULL,
`Api_userid` int(11) DEFAULT NULL,
`ReturnMessageID` varchar(255) DEFAULT NULL,
`Sequence_Id` int(11) DEFAULT NULL,
`messagetext` longtext,
`adtextid` int(11) DEFAULT NULL,
`mobileno` varchar(255) DEFAULT NULL,
`deliverystatus` int(11) DEFAULT NULL,
`SMSlength` int(11) DEFAULT NULL,
`DOC` varchar(255) DEFAULT NULL,
`DOM` varchar(255) DEFAULT NULL,
`BatchID` int(11) DEFAULT NULL,
`StudentID` int(11) DEFAULT NULL,
`SMSSentTime` varchar(255) DEFAULT NULL,
`SMSDeliveredTime` varchar(255) DEFAULT NULL,
`SMSDeliveredTimeTicks` decimal(28,0) DEFAULT '0',
`SMSSentTimeTicks` decimal(28,0) DEFAULT '0',
`Sent_SMS_Day` int(11) DEFAULT NULL,
`Sent_SMS_Month` int(11) DEFAULT NULL,
`Sent_SMS_Year` int(11) DEFAULT NULL,
`smssent` int(11) DEFAULT '1',
`Batch_Name` varchar(255) DEFAULT NULL,
`User_ID` varchar(255) DEFAULT NULL,
`Year_ID` int(11) DEFAULT NULL,
`Date_Time` varchar(255) DEFAULT NULL,
`IsGroup` double DEFAULT NULL,
`Date_Time_Ticks` decimal(28,0) DEFAULT NULL,
`IsNotificationSent` int(11) DEFAULT NULL,
`Module_Id` double DEFAULT NULL,
`Doc_Batch` decimal(28,0) DEFAULT NULL,
`SMS_Category_ID` int(11) DEFAULT NULL,
`SID` int(11) DEFAULT NULL,
PRIMARY KEY (`MessageID`),
KEY `index2` (`ReturnMessageID`),
KEY `index3` (`mobileno`),
KEY `BatchID` (`BatchID`),
KEY `smssent` (`smssent`),
KEY `deliverystatus` (`deliverystatus`),
KEY `day` (`Sent_SMS_Day`),
KEY `month` (`Sent_SMS_Month`),
KEY `year` (`Sent_SMS_Year`),
KEY `index4` (`ApplicationID`,`SMSSentTimeTicks`),
KEY `smslength` (`SMSlength`),
KEY `studid` (`StudentID`),
KEY `batchid_studid` (`BatchID`,`StudentID`),
KEY `User_ID` (`User_ID`),
KEY `Year_Id` (`Year_ID`),
KEY `IsNotificationSent` (`IsNotificationSent`),
KEY `isgroup` (`IsGroup`),
KEY `SID` (`SID`),
KEY `SMS_Category_ID` (`SMS_Category_ID`),
KEY `SMSSentTimeTicks` (`SMSSentTimeTicks`)
) ENGINE=MyISAM AUTO_INCREMENT=16513292 DEFAULT CHARSET=utf8;
以下是我的select查询:
SELECT messagetext, SMSSentTime, StudentID, batchid,
User_ID,MessageID,Sent_SMS_Day, Sent_SMS_Month,
Sent_SMS_Year,Module_Id,Year_ID,Doc_Batch
FROM smstable_read
WHERE StudentID=977 AND SID = 8582 AND MessageID>16013282
您的数据库似乎没有正确索引,甚至没有正确规范化。规范化数据库将大大加快所有查询的速度。特别是考虑到 mysql 在查询中每个 table 仅使用一个索引这一事实。即使您有很多索引,也不能使用它们。
您当前的查询过滤器 StudentID
、SID
和 MessageID
。最后一个是不平等比较,因此索引不会非常有效,但其他两列是平等比较。我建议这样的索引:
KEY `studid` (`StudentID`,`SID`)
通过删除 SID
上的现有索引来跟进。如果您发现您不想删除它,因为它在另一个查询中使用,则进一步证明您的 table 迫切需要规范化。
太多索引会减慢插入速度并为每个索引增加一点开销SELECT,因为查询规划器需要更多努力来确定要使用哪个索引。
你需要了解复合索引和覆盖索引。阅读这些内容。
您的查询速度很慢,因为它正在对 table 进行半扫描。它使用主键查找具有限定 MessageID
的第一行,然后查看 table 的每一行以查找匹配的行。
您的过滤条件是 StudentID = constant
、SID = constant
和 MessageID > constant
。这意味着您需要在索引中按顺序排列这三列。前两个过滤条件将随机访问您的索引到正确的位置。第三个条件将在查询中的常量值之后开始扫描索引。它被称为索引范围扫描操作,效率很高。
ALTER TABLE smstable_read
ADD INDEX StudentSidMessage (StudentId, SID, MessageId);
这个复合索引应该可以使您的查询更加高效。请注意,在 MyISAM 中,table 的主键列应该出现在复合索引中。在这种情况下这很酷,因为它也是您的查询条件的一部分。
如果这个查询被频繁使用,你可以做一个覆盖索引:你可以将查询的其他列(SELECT
子句中提到的列)添加到索引中。
但是,不幸的是,您使用 longtext
数据类型定义了 messageText
列。这允许每条消息最多包含 4 GB。 (为什么?这真的是 SMS 数据吗?SMS 中每条消息有 160 字节的限制。4 GB >> 160 字节。)
现在覆盖索引的目的是让查询完全从索引中得到满足,而无需返回 table。但是当您在索引中包含 longtext
或任何其他 LOB 列时,它只包含数据的一个子集。所以覆盖索引的点就没了
如果我是你,我会更改我的 table,使 messageText
成为 VARCHAR(255)
数据类型,然后创建覆盖索引:
ALTER TABLE smstable_read
ADD INDEX StudentSidMessage (StudentId, SID, MessageId,
SMSSentTime, batchid,
User_ID, Sent_SMS_Day, Sent_SMS_Month,
Sent_SMS_Year,Module_Id,Year_ID,Doc_Batch,
messageText);
(请注意,如果可以的话,您应该将变长项目放在索引的最后。)
如果您不能更改您的应用程序来处理 VARCHAR(255)
那么请使用我提到的第一个索引。
专业提示:在 MySQL table 上放置大量单列索引很少有助于 SELECT 性能,并且总是会损害 INSERT 和 UPDATE 性能。 您需要在主键上建立索引,并且需要索引来支持您的查询 运行。 额外的索引是有害的。
我在 ASP.NET 的 C# Web 应用程序中使用 MySQL 数据库。 MySQL 服务器版本为 5.7,PC 内存为 8 GB。当我在 MySQL 数据库 table 中执行 select 查询时,执行需要更多时间;一个简单的 select 查询需要 大约 42 秒 。 table 中的 1 crorerecord(1000 万条记录)。 我还为 table 建立了索引。我该如何解决这个问题?
以下是我的table结构。
CREATE TABLE `smstable_read` (
`MessageID` int(11) NOT NULL AUTO_INCREMENT,
`ApplicationID` int(11) DEFAULT NULL,
`Api_userid` int(11) DEFAULT NULL,
`ReturnMessageID` varchar(255) DEFAULT NULL,
`Sequence_Id` int(11) DEFAULT NULL,
`messagetext` longtext,
`adtextid` int(11) DEFAULT NULL,
`mobileno` varchar(255) DEFAULT NULL,
`deliverystatus` int(11) DEFAULT NULL,
`SMSlength` int(11) DEFAULT NULL,
`DOC` varchar(255) DEFAULT NULL,
`DOM` varchar(255) DEFAULT NULL,
`BatchID` int(11) DEFAULT NULL,
`StudentID` int(11) DEFAULT NULL,
`SMSSentTime` varchar(255) DEFAULT NULL,
`SMSDeliveredTime` varchar(255) DEFAULT NULL,
`SMSDeliveredTimeTicks` decimal(28,0) DEFAULT '0',
`SMSSentTimeTicks` decimal(28,0) DEFAULT '0',
`Sent_SMS_Day` int(11) DEFAULT NULL,
`Sent_SMS_Month` int(11) DEFAULT NULL,
`Sent_SMS_Year` int(11) DEFAULT NULL,
`smssent` int(11) DEFAULT '1',
`Batch_Name` varchar(255) DEFAULT NULL,
`User_ID` varchar(255) DEFAULT NULL,
`Year_ID` int(11) DEFAULT NULL,
`Date_Time` varchar(255) DEFAULT NULL,
`IsGroup` double DEFAULT NULL,
`Date_Time_Ticks` decimal(28,0) DEFAULT NULL,
`IsNotificationSent` int(11) DEFAULT NULL,
`Module_Id` double DEFAULT NULL,
`Doc_Batch` decimal(28,0) DEFAULT NULL,
`SMS_Category_ID` int(11) DEFAULT NULL,
`SID` int(11) DEFAULT NULL,
PRIMARY KEY (`MessageID`),
KEY `index2` (`ReturnMessageID`),
KEY `index3` (`mobileno`),
KEY `BatchID` (`BatchID`),
KEY `smssent` (`smssent`),
KEY `deliverystatus` (`deliverystatus`),
KEY `day` (`Sent_SMS_Day`),
KEY `month` (`Sent_SMS_Month`),
KEY `year` (`Sent_SMS_Year`),
KEY `index4` (`ApplicationID`,`SMSSentTimeTicks`),
KEY `smslength` (`SMSlength`),
KEY `studid` (`StudentID`),
KEY `batchid_studid` (`BatchID`,`StudentID`),
KEY `User_ID` (`User_ID`),
KEY `Year_Id` (`Year_ID`),
KEY `IsNotificationSent` (`IsNotificationSent`),
KEY `isgroup` (`IsGroup`),
KEY `SID` (`SID`),
KEY `SMS_Category_ID` (`SMS_Category_ID`),
KEY `SMSSentTimeTicks` (`SMSSentTimeTicks`)
) ENGINE=MyISAM AUTO_INCREMENT=16513292 DEFAULT CHARSET=utf8;
以下是我的select查询:
SELECT messagetext, SMSSentTime, StudentID, batchid,
User_ID,MessageID,Sent_SMS_Day, Sent_SMS_Month,
Sent_SMS_Year,Module_Id,Year_ID,Doc_Batch
FROM smstable_read
WHERE StudentID=977 AND SID = 8582 AND MessageID>16013282
您的数据库似乎没有正确索引,甚至没有正确规范化。规范化数据库将大大加快所有查询的速度。特别是考虑到 mysql 在查询中每个 table 仅使用一个索引这一事实。即使您有很多索引,也不能使用它们。
您当前的查询过滤器 StudentID
、SID
和 MessageID
。最后一个是不平等比较,因此索引不会非常有效,但其他两列是平等比较。我建议这样的索引:
KEY `studid` (`StudentID`,`SID`)
通过删除 SID
上的现有索引来跟进。如果您发现您不想删除它,因为它在另一个查询中使用,则进一步证明您的 table 迫切需要规范化。
太多索引会减慢插入速度并为每个索引增加一点开销SELECT,因为查询规划器需要更多努力来确定要使用哪个索引。
你需要了解复合索引和覆盖索引。阅读这些内容。
您的查询速度很慢,因为它正在对 table 进行半扫描。它使用主键查找具有限定 MessageID
的第一行,然后查看 table 的每一行以查找匹配的行。
您的过滤条件是 StudentID = constant
、SID = constant
和 MessageID > constant
。这意味着您需要在索引中按顺序排列这三列。前两个过滤条件将随机访问您的索引到正确的位置。第三个条件将在查询中的常量值之后开始扫描索引。它被称为索引范围扫描操作,效率很高。
ALTER TABLE smstable_read
ADD INDEX StudentSidMessage (StudentId, SID, MessageId);
这个复合索引应该可以使您的查询更加高效。请注意,在 MyISAM 中,table 的主键列应该出现在复合索引中。在这种情况下这很酷,因为它也是您的查询条件的一部分。
如果这个查询被频繁使用,你可以做一个覆盖索引:你可以将查询的其他列(SELECT
子句中提到的列)添加到索引中。
但是,不幸的是,您使用 longtext
数据类型定义了 messageText
列。这允许每条消息最多包含 4 GB。 (为什么?这真的是 SMS 数据吗?SMS 中每条消息有 160 字节的限制。4 GB >> 160 字节。)
现在覆盖索引的目的是让查询完全从索引中得到满足,而无需返回 table。但是当您在索引中包含 longtext
或任何其他 LOB 列时,它只包含数据的一个子集。所以覆盖索引的点就没了
如果我是你,我会更改我的 table,使 messageText
成为 VARCHAR(255)
数据类型,然后创建覆盖索引:
ALTER TABLE smstable_read
ADD INDEX StudentSidMessage (StudentId, SID, MessageId,
SMSSentTime, batchid,
User_ID, Sent_SMS_Day, Sent_SMS_Month,
Sent_SMS_Year,Module_Id,Year_ID,Doc_Batch,
messageText);
(请注意,如果可以的话,您应该将变长项目放在索引的最后。)
如果您不能更改您的应用程序来处理 VARCHAR(255)
那么请使用我提到的第一个索引。
专业提示:在 MySQL table 上放置大量单列索引很少有助于 SELECT 性能,并且总是会损害 INSERT 和 UPDATE 性能。 您需要在主键上建立索引,并且需要索引来支持您的查询 运行。 额外的索引是有害的。