簡體   English   中英

MYSQL查詢執行速度非常慢

[英]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添加到表中。 閱讀我的關於建立索引的食譜

嘗試使用以下方法來提高查詢性能:

  • 在數據庫結構中定義索引,並僅獲取所需的列。
  • 不要在選擇查詢中使用*。
  • 並且不要將id放在諸如User.id='51394'引號中,而是將User.id= 51394代替。
  • 如果您在引號中提供ID,則無法建立索引。 該方法將查詢性能提高了20%。
  • 如果使用的是ENGINE=MyISAM則無法在數據庫表之間定義索引,請將數據庫引擎更改為ENGINE=InnoDB 並創建一些索引,例如外鍵,全文本索引。

據我了解,對於SELECT * FROM AdvanceBulkInsert ...的所有結果,您都運行一個SELECT cf.*請求,對於所有SELECT cf.* ,您都運行SELECT * FROM User

我認為問題在於您向基地發送了太多請求。

我認為您應該將所有選擇請求合並為一個大請求。

為了那個原因:

然后,對合並選擇的所有結果調用更新。

您也應該一遍一遍地處理您的請求,以找出哪個請求花費的時間最多,並且您也應該使用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上添加索引

當您需要找出查詢耗時的原因時,您需要檢查各個部分。 正如您在問題中所顯示的, 解釋語句可以為您提供很大幫助。 通常,最重要的列是:

  • select_type-這應該始終是簡單的查詢/子查詢。 相關子查詢會帶來很多麻煩。 幸運的是您沒有使用任何
  • 可能的鍵-此選擇將搜索什么鍵
  • 行-由鍵/緩存和其他技術確定多少候選行。 數字越小越好
  • 額外-“使用”可告訴您如何精確找到行,這是最有用的信息

查詢分析

我會為第一和第三查詢發布分析數據,但是它們都是非常簡單的查詢。 這是給您帶來麻煩的查詢的細分:

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
  • ctyp.id = cf.customTypeId上的INNER JOIN CustomType ctyp
  • 左外聯接cf.id = cfv.customFieldIdrelatedId = 0上的CustomValue cfv
  • cfsa.customFieldId = cf.id上向左聯接CustomFieldSubArea cfsa
  • WHERE((relatedTable = '人'和啟用 = '1')和( 的onCreate = '1'))AND(cfsa.subarea = 'peoplebulkinsert')
  • ORDER BY cf.sortOrdercf.label

讓我解釋一下上面的清單。 粗體列必須完全具有索引。 連接表是昂貴的操作,否則需要遍歷兩個表的所有行。 如果在可連接列上建立索引,則數據庫引擎將找到更快,更好的方法。 對於任何數據庫,這應該是慣例

斜體列不是必須具有索引的,但是如果您有大量的行(20 000是大量的),則還應該在用於搜索的列上具有索引,這可能不會對處理速度產生影響,但是值得額外的時間。

因此,您需要在theese列中添加索引

  • CustomType-ID
  • CustomField-customTypeId,id,relatedTable,enabled,onCreate,sortOrder,label
  • CustomValue-customFieldId
  • CustomFieldSubArea-customFieldId,子區域

要驗證結果,請嘗試在添加索引(可能還有其他一些選擇/插入/更新查詢)之后再次運行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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM