[英]MYSQL query performs very slow
我已經開發了一個用戶批量上傳模塊。 有兩種情況,當數據庫具有零記錄時,我批量上傳了20000條記錄。 大約需要5個小時。 但是,當數據庫中已經有大約30 000條記錄時,上載非常慢。 上載2萬條記錄大約需要11個小時。 我只是通過fgetcsv
方法讀取CSV文件。
if (($handle = fopen($filePath, "r")) !== FALSE) {
while (($peopleData = fgetcsv($handle, 10240, ",")) !== FALSE) {
if (count($peopleData) == $fieldsCount) {
//inside i check if user already exist (firstName & lastName & DOB)
//if not, i check if email exist. if exist, update the records.
//other wise insert a new record.
}}}
下面是運行的查詢。 (我正在使用Yii框架)
SELECT *
FROM `AdvanceBulkInsert` `t`
WHERE renameSource='24851_bulk_people_2016-02-25_LE CARVALHO 1.zip.csv'
LIMIT 1
SELECT cf.*, ctyp.typeName, cfv.id as customId, cfv.customFieldId,
cfv.relatedId, cfv.fieldValue, cfv.createdAt
FROM `CustomField` `cf`
INNER JOIN CustomType ctyp on ctyp.id = cf.customTypeId
LEFT OUTER JOIN CustomValue cfv on cf.id = cfv.customFieldId
and relatedId = 0
LEFT JOIN CustomFieldSubArea cfsa on cfsa.customFieldId = cf.id
WHERE ((relatedTable = 'people' and enabled = '1')
AND (onCreate = '1'))
AND (cfsa.subarea='peoplebulkinsert')
ORDER BY cf.sortOrder, cf.label
SELECT *
FROM `User` `t`
WHERE `t`.`firstName`='Franck'
AND `t`.`lastName`='ALLEGAERT '
AND `t`.`dateOfBirth`='1971-07-29'
AND (userType NOT IN ("1"))
LIMIT 1
如果存在,請更新用戶:
UPDATE `User` SET `id`='51394', `address1`='49 GRANDE RUE',
`mobile`='', `name`=NULL, `firstName`='Franck',
`lastName`='ALLEGAERT ', `username`=NULL,
`password`=NULL, `email`=NULL, `gender`=0,
`zip`='60310', `countryCode`='DZ',
`joinedDate`='2016-02-23 10:44:18',
`signUpDate`='0000-00-00 00:00:00',
`supporterDate`='2016-02-25 13:26:37', `userType`=3,
`signup`=0, `isSysUser`=0, `dateOfBirth`='1971-07-29',
`reqruiteCount`=0, `keywords`='70,71,72,73,74,75',
`delStatus`=0, `city`='AMY', `isUnsubEmail`=0,
`isManual`=1, `isSignupConfirmed`=0, `profImage`=NULL,
`totalDonations`=NULL, `isMcContact`=NULL,
`emailStatus`=NULL, `notes`=NULL,
`addressInvalidatedAt`=NULL,
`createdAt`='2016-02-23 10:44:18',
`updatedAt`='2016-02-25 13:26:37', `longLat`=NULL
WHERE `User`.`id`='51394'
如果用戶不存在,請插入新記錄。
表引擎類型為MYISAM。 僅電子郵件列具有索引。
我該如何優化以減少處理時間?
查詢2花費了0.4701秒,這意味着30 000條記錄將花費14103秒,大約235分鍾。 大約6個小時。
更新資料
CREATE TABLE IF NOT EXISTS `User` (
`id` bigint(20) NOT NULL,
`address1` text COLLATE utf8_unicode_ci,
`mobile` varchar(15) COLLATE utf8_unicode_ci DEFAULT NULL,
`name` varchar(45) COLLATE utf8_unicode_ci DEFAULT NULL,
`firstName` varchar(64) COLLATE utf8_unicode_ci DEFAULT NULL,
`lastName` varchar(64) COLLATE utf8_unicode_ci DEFAULT NULL,
`username` varchar(20) COLLATE utf8_unicode_ci DEFAULT NULL,
`password` varchar(45) COLLATE utf8_unicode_ci DEFAULT NULL,
`email` varchar(45) COLLATE utf8_unicode_ci DEFAULT NULL,
`gender` tinyint(2) NOT NULL DEFAULT '0' COMMENT '1 - female, 2-male, 0 - unknown',
`zip` varchar(15) COLLATE utf8_unicode_ci DEFAULT NULL,
`countryCode` varchar(3) COLLATE utf8_unicode_ci DEFAULT NULL,
`joinedDate` datetime DEFAULT NULL,
`signUpDate` datetime NOT NULL COMMENT 'User signed up date',
`supporterDate` datetime NOT NULL COMMENT 'Date which user get supporter',
`userType` tinyint(2) NOT NULL,
`signup` tinyint(2) NOT NULL DEFAULT '0' COMMENT 'whether user followed signup process 1 - signup, 0 - not signup',
`isSysUser` tinyint(1) NOT NULL DEFAULT '0' COMMENT '1 - system user, 0 - not a system user',
`dateOfBirth` date DEFAULT NULL COMMENT 'User date of birth',
`reqruiteCount` int(11) DEFAULT '0' COMMENT 'User count that he has reqruited',
`keywords` text COLLATE utf8_unicode_ci COMMENT 'Kewords',
`delStatus` tinyint(2) NOT NULL DEFAULT '0' COMMENT '0 - active, 1 - deleted',
`city` varchar(50) COLLATE utf8_unicode_ci DEFAULT NULL,
`isUnsubEmail` tinyint(1) NOT NULL DEFAULT '0' COMMENT '0 - ok, 1 - Unsubscribed form email',
`isManual` tinyint(1) NOT NULL DEFAULT '0' COMMENT '0 - ok, 1 - Manualy add',
`longLat` varchar(45) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT 'Longitude and Latitude',
`isSignupConfirmed` tinyint(4) NOT NULL DEFAULT '0' COMMENT 'Whether user has confirmed signup ',
`profImage` tinytext COLLATE utf8_unicode_ci COMMENT 'Profile image name or URL',
`totalDonations` float DEFAULT NULL COMMENT 'Total donations made by the user',
`isMcContact` tinyint(1) DEFAULT NULL COMMENT '1 - Mailchimp contact',
`emailStatus` tinyint(2) DEFAULT NULL COMMENT '1-bounced, 2-blocked',
`notes` text COLLATE utf8_unicode_ci,
`addressInvalidatedAt` datetime DEFAULT NULL,
`createdAt` datetime NOT NULL,
`updatedAt` datetime DEFAULT NULL
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
CREATE TABLE IF NOT EXISTS `AdvanceBulkInsert` (
`id` int(11) NOT NULL,
`source` varchar(256) NOT NULL,
`renameSource` varchar(256) DEFAULT NULL,
`countryCode` varchar(3) NOT NULL,
`userType` tinyint(2) NOT NULL,
`size` varchar(128) NOT NULL,
`errors` varchar(512) NOT NULL,
`status` char(1) NOT NULL COMMENT '1:Queued, 2:In Progress, 3:Error, 4:Finished, 5:Cancel',
`createdAt` datetime NOT NULL,
`createdBy` int(11) NOT NULL
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
CREATE TABLE IF NOT EXISTS `CustomField` (
`id` int(11) NOT NULL,
`customTypeId` int(11) NOT NULL,
`fieldName` varchar(64) COLLATE utf8_unicode_ci DEFAULT NULL,
`relatedTable` varchar(64) COLLATE utf8_unicode_ci DEFAULT NULL,
`defaultValue` text COLLATE utf8_unicode_ci,
`sortOrder` int(11) NOT NULL DEFAULT '0',
`enabled` char(1) COLLATE utf8_unicode_ci DEFAULT '1',
`listItemTag` char(1) COLLATE utf8_unicode_ci DEFAULT NULL,
`required` char(1) COLLATE utf8_unicode_ci DEFAULT '0',
`onCreate` char(1) COLLATE utf8_unicode_ci DEFAULT '1',
`onEdit` char(1) COLLATE utf8_unicode_ci DEFAULT '1',
`onView` char(1) COLLATE utf8_unicode_ci DEFAULT '1',
`listValues` text COLLATE utf8_unicode_ci,
`label` varchar(64) COLLATE utf8_unicode_ci DEFAULT NULL,
`htmlOptions` text COLLATE utf8_unicode_ci
) ENGINE=MyISAM AUTO_INCREMENT=12 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
CREATE TABLE IF NOT EXISTS `CustomFieldSubArea` (
`id` int(11) NOT NULL,
`customFieldId` int(11) NOT NULL,
`subarea` varchar(256) COLLATE utf8_unicode_ci NOT NULL
) ENGINE=MyISAM AUTO_INCREMENT=43 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
CREATE TABLE IF NOT EXISTS `CustomValue` (
`id` int(11) NOT NULL,
`customFieldId` int(11) NOT NULL,
`relatedId` int(11) NOT NULL,
`fieldValue` text COLLATE utf8_unicode_ci,
`createdAt` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
) ENGINE=MyISAM AUTO_INCREMENT=86866 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
整個PHP代碼在這里http://pastie.org/10737962
更新2
解釋查詢的輸出
索引是您的朋友。
UPDATE User ... WHERE id = ...
需要ID的索引,可能是PRIMARY KEY
。
對於renameSource
同樣renameSource
。
SELECT *
FROM `User` `t`
WHERE `t`.`firstName`='Franck'
AND `t`.`lastName`='ALLEGAERT '
AND `t`.`dateOfBirth`='1971-07-29'
AND (userType NOT IN ("1"))
LIMIT 1;
需要INDEX(firstName, lastName, dateOfBirth)
; 字段可以是任何順序(在這種情況下)。
查看每個查詢以查看其需求,然后將該INDEX
添加到表中。 閱讀我的關於建立索引的食譜 。
嘗試使用以下方法來提高查詢性能:
User.id='51394'
引號中,而是將User.id= 51394
代替。 ENGINE=MyISAM
則無法在數據庫表之間定義索引,請將數據庫引擎更改為ENGINE=InnoDB
。 並創建一些索引,例如外鍵,全文本索引。 據我了解,對於SELECT * FROM AdvanceBulkInsert
...的所有結果,您都運行一個SELECT cf.*
請求,對於所有SELECT cf.*
,您都運行SELECT * FROM User
我認為問題在於您向基地發送了太多請求。
我認為您應該將所有選擇請求合並為一個大請求。
為了那個原因:
用EXISTS IN (SELECT * FROM AdvanceBulkInsert where ...)
或JOIN
替換SELECT * FROM AdvanceBulkInsert
用NOT EXISTS IN(SELECT * from User WHERE )
替換SELECT * FROM User
NOT EXISTS IN(SELECT * from User WHERE )
然后,對合並選擇的所有結果調用更新。
您也應該一遍一遍地處理您的請求,以找出哪個請求花費的時間最多,並且您也應該使用ANALYZE來查找請求中哪一部分花費時間。
編輯:
現在,我看到了您的代碼:
一些線索:
您是否為cf.customTypeId,cfv.customFieldId,cfsa.customFieldId用戶索引。 dateOfBirth,用戶。 firstName,user.lastName?
如果您有使用CustomFieldSubArea的WHERE,則不需要做LEFT JOIN CustomFieldSubArea,一個簡單的JOIN CustomFieldSubArea就足夠了。
您將使用relatedId = 0大量啟動查詢2,也許您可以將結果保存在var中?
如果不需要排序的數據,請刪除“ ORDER BY cf.sortOrder,cf.label”。 否則,在cf.sortOrder,cf.label上添加索引
當您需要找出查詢耗時的原因時,您需要檢查各個部分。 正如您在問題中所顯示的, 解釋語句可以為您提供很大幫助。 通常,最重要的列是:
我會為第一和第三查詢發布分析數據,但是它們都是非常簡單的查詢。 這是給您帶來麻煩的查詢的細分:
EXPLAIN SELECT cf.*, ctyp.typeName, cfv.id as customId, cfv.customFieldId,
cfv.relatedId, cfv.fieldValue, cfv.createdAt
FROM `CustomField` `cf`
INNER JOIN CustomType ctyp on ctyp.id = cf.customTypeId
LEFT OUTER JOIN CustomValue cfv on cf.id = cfv.customFieldId
and relatedId = 0
LEFT JOIN CustomFieldSubArea cfsa on cfsa.customFieldId = cf.id
WHERE ((relatedTable = 'people' and enabled = '1')
AND (onCreate = '1'))
AND (cfsa.subarea='peoplebulkinsert')
ORDER BY cf.sortOrder, cf.label
讓我解釋一下上面的清單。 粗體列必須完全具有索引。 連接表是昂貴的操作,否則需要遍歷兩個表的所有行。 如果在可連接列上建立索引,則數據庫引擎將找到更快,更好的方法。 對於任何數據庫,這應該是慣例
斜體列不是必須具有索引的,但是如果您有大量的行(20 000是大量的),則還應該在用於搜索的列上具有索引,這可能不會對處理速度產生影響,但是值得額外的時間。
因此,您需要在theese列中添加索引
要驗證結果,請嘗試在添加索引(可能還有其他一些選擇/插入/更新查詢)之后再次運行explain語句。 額外的列應顯示諸如“使用索引”之類的內容,而possible_keys列應列出已使用的鍵(每個聯接查詢甚至兩個或更多)。
旁注:您的代碼中有一些錯別字,如果其他人也需要對您的代碼進行處理,則應予以糾正:“ reqruiteCount”作為表列,“ fileUplaod”作為引用的代碼中的數組索引。
對於我的工作,我必須每天添加一個524列和10k記錄的CSV。 當我嘗試解析它並用php添加記錄時,那太可怕了。
因此,我建議您查看有關LOAD DATA LOCAL INFILE的文檔
例如,我復制/粘貼了自己的代碼,但根據您的需求使他適應
$dataload = 'LOAD DATA LOCAL INFILE "'.$filename.'"
REPLACE
INTO TABLE '.$this->csvTable.' CHARACTER SET "utf8"
FIELDS TERMINATED BY "\t"
IGNORE 1 LINES
';
$result = (bool)$this->db->query($dataload);
其中$ filename是CSV的本地路徑(您可以使用dirname(__FILE__)
來獲取它)
這個SQL命令非常快(添加或更新所有CSV只需1或2秒)
編輯:閱讀文檔,但是您當然需要在用戶表上具有uniq索引才能進行“替換”工作。 因此,您無需檢查用戶是否存在。 而且您不需要使用php解析CSV文件。
您似乎對每條記錄都有3個查詢(概率?)。 這3個查詢將需要3次訪問數據庫的時間(如果您使用yii將記錄存儲在yii對象中,則可能會進一步降低速度)。
您可以在名字/姓氏/ DOB上添加唯一鍵,在電子郵件地址上添加唯一鍵嗎?
如果是這樣,您只需執行INSERT .... ON DUPLICATE KEY UPDATE。 這樣會將其簡化為每個記錄的單個查詢,從而大大加快了工作速度。
但是這種語法的最大優點是您可以一次插入/更新許多記錄(我通常堅持約250條記錄),因此訪問數據庫的次數更少。
您可以敲擊一個類,該類將記錄僅傳遞給該類,並且當記錄數達到您的選擇時將插入該類。 還添加一個調用以將記錄插入析構函數中以插入所有最終記錄。
另一個選擇是將所有內容讀取到臨時表中,然后將其用作連接到用戶表以進行更新/插入的源。 這將需要對索引進行一些工作,但是臨時表的批量加載很快,並且使用有用的索引對其進行更新將很快。 使用它作為插入源也應該很快(如果排除已更新的記錄)。
另一個問題似乎是您的以下查詢,但不確定在哪里執行此查詢。 它似乎只需要執行一次,在這種情況下可能並不太重要。 您尚未提供CustomType表的結構,但它已連接到Customfield,並且字段customTypeId沒有索引。 因此,該連接將很慢。 同樣,在CustomValue和CustomFieldSubArea聯接上,它們基於customFieldId聯接,並且在該字段上都沒有索引(希望是唯一索引,因為這些字段不是唯一的,您將獲得很多記錄返回-每種可能的組合為1行)
SELECT cf.*, ctyp.typeName, cfv.id as customId, cfv.customFieldId,
cfv.relatedId, cfv.fieldValue, cfv.createdAt
FROM `CustomField` `cf`
INNER JOIN CustomType ctyp on ctyp.id = cf.customTypeId
LEFT OUTER JOIN CustomValue cfv on cf.id = cfv.customFieldId
and relatedId = 0
LEFT JOIN CustomFieldSubArea cfsa on cfsa.customFieldId = cf.id
WHERE ((relatedTable = 'people' and enabled = '1')
AND (onCreate = '1'))
AND (cfsa.subarea='peoplebulkinsert')
ORDER BY cf.sortOrder, cf.label
看到它,您可以嘗試減少查詢,並使用sql在線編譯器檢查時間段,然后將其包含在項目下。
始終在轉換中批量導入
$transaction = Yii::app()->db->beginTransaction();
$curRow = 0;
try
{
while (($peopleData = fgetcsv($handle, 10240, ",")) !== FALSE) {
$curRow++;
//process $peopleData
//insert row
//best to use INSERT ... ON DUPLICATE KEY UPDATE
// a = 1
// b = 2;
if ($curRow % 5000 == 0) {
$transaction->commit();
$transaction->beginTransaction();
}
}
catch (Exception $ex)
{
$transaction->rollBack();
$result = $e->getMessage();
}
//don't forget the remainder.
$transaction->commit();
我已經看到通過簡單地使用此技術,導入例程將加速500%。 我還看到了一個導入過程,該過程對每一行進行了600個查詢(選擇,插入,更新和顯示表結構的混合)。 這項技術使該過程加快了30%。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.