PHP와 MySQL간에 이상한 쿼리 시간 초과가 발생하는 원인은 무엇입니까?


11

저는 많은 다른 고객들이 사용하는 SaaS (Software-as-a-Service) 응용 프로그램의 수석 개발자입니다. 우리의 소프트웨어는 MySQL 백엔드로 구동되는 Apache / PHP 애플리케이션 서버 클러스터에서 실행됩니다. 에 하나 개의 소프트웨어의 특정 인스턴스, 카테고리 이름의 목록을 조회 할 PHP 코드가 시간 초과되어 고객이 29 개 이상의 범주가있을 때 . 나는 이것이 의미가 없다는 것을 안다. 숫자 30에 대해서는 특별한 점이 없으며 다른 고객이 30 개가 넘는 범주를 가지고 있지만,이 하나의 설치에 30 개 이상의 범주가 있으면 문제가 100 % 재현되고 30 개 미만의 범주가 있으면 사라집니다.

해당 테이블은 다음과 같습니다.

CREATE TABLE IF NOT EXISTS `categories` (
  `id` int(10) unsigned NOT NULL auto_increment,
  `name` varchar(64) NOT NULL,
  `title` varchar(128) NOT NULL,
  `parent` int(10) unsigned NOT NULL,
  `keywords` varchar(255) NOT NULL,
  `description` text NOT NULL,
  `status` enum('Active','Inactive','_Deleted','_New') NOT NULL default 'Active',
  `style` enum('_Unknown') default NULL COMMENT 'Autoenum;',
  `order` smallint(5) unsigned NOT NULL,
  `created_at` datetime NOT NULL,
  `modified_at` datetime default NULL,
  PRIMARY KEY  (`id`),
  KEY `name` (`name`),
  KEY `parent` (`parent`),
  KEY `created_at` (`created_at`),
  KEY `modified_at` (`modified_at`),
  KEY `status` (`status`)
) ENGINE=MyISAM  DEFAULT CHARSET=latin1 COMMENT='R2' AUTO_INCREMENT=33 ;

문제의 코드는 반복적으로 테이블을 쿼리하여 모든 범주를 가져옵니다. 그것은 발행

SELECT * FROM `categories` WHERE `parent`=0 ORDER BY `order`,`name`

그런 다음 반환 된 각 행마 다이 쿼리를 반복하지만 WHERE parent=$category_id매번 사용 합니다. (이 절차가 개선 될 수 있다고 확신하지만 다른 질문 일 수 있습니다)

내가 알 수있는 한 다음 쿼리는 영원히 걸려 있습니다.

SELECT * FROM `categories` WHERE `parent`=22 ORDER BY `order`,`name`

서버의 mysql 클라이언트 에서이 쿼리를 완벽하게 실행할 수 있으며 PHPMyAdmin에서도 문제없이 실행할 수 있습니다.

그렇지 않은 것을 참고 특정 쿼리 문제입니다. 내가 만약 DELETE FROM categories WHERE id=22다음 다른 후 중단됩니다 위의 것과 유사한 쿼리. 또한 위의 쿼리 는 수동으로 실행할 때 0 행을 반환합니다 .

나는 테이블이 손상되었다고 의심했지만 REPAIR TABLE, OPTIMIZE TABLE보고 된 이러한 문제점들에 대해서는 문제를 해결하지도 않았다. 테이블을 삭제하고 다시 만들었지 만 문제가 다시 발생했습니다. 이것은 정확히 동일한 테이블 구조이며 다른 고객이 사용하는 PHP 코드는 30 개 이상의 범주를 가진 고객을 포함하여 다른 사람에게는 아무런 문제가 없습니다.

PHP 코드는 영원히 되풀이 되지 않습니다 . (이것은 무한 루프 가 아닙니다 )

MySQL 서버는 i686 (MySQL Community Edition (GPL))에서 pc-linux-gnu 용 mysqld Ver 5.0.92-community와 함께 CentOS Linux를 실행 중입니다.

MySQL 서버의로드가 낮음 :로드 평균 : 0.58, 0.75, 0.73, CPU : 4.6 % us, 2.9 % sy, 0.0 % ni, 92.2 % id, 0.0 % wa, 0.0 % hi, 0.3 % si, 0.0 % st. 무시할만한 스왑 사용 중 (448k)

이 문제를 어떻게 해결할 수 있습니까? 무슨 일이 일어나고 있는지에 대한 제안?

UPDATE : I는 TRUNCE표 에드과 더미 데이터 (30)의 행을 삽입 :

INSERT INTO `categories` (`id`, `name`, `title`, `parent`, `keywords`, `description`, `status`, `style`, `order`, `created_at`, `modified_at`) VALUES
(1, 'New Category', '', 0, '', '', 'Inactive', NULL, 1, '2011-10-25 12:06:30', '2011-10-25 12:06:34'),
(2, 'New Category', '', 0, '', '', 'Inactive', NULL, 2, '2011-10-25 12:06:39', '2011-10-25 12:06:40'),
(3, 'New Category', '', 0, '', '', 'Inactive', NULL, 3, '2011-10-25 12:06:41', '2011-10-25 12:06:42'),
(4, 'New Category', '', 0, '', '', 'Inactive', NULL, 4, '2011-10-25 12:06:46', '2011-10-25 12:06:47'),
(5, 'New Category', '', 0, '', '', 'Inactive', NULL, 5, '2011-10-25 12:06:49', NULL),
(6, 'New Category', '', 0, '', '', 'Inactive', NULL, 6, '2011-10-25 12:06:51', '2011-10-25 12:06:52'),
(7, 'New Category', '', 0, '', '', 'Inactive', NULL, 7, '2011-10-25 12:06:53', '2011-10-25 12:06:54'),
(8, 'New Category', '', 0, '', '', 'Inactive', NULL, 8, '2011-10-25 12:06:56', '2011-10-25 12:06:57'),
(9, 'New Category', '', 0, '', '', 'Inactive', NULL, 9, '2011-10-25 12:06:59', '2011-10-25 12:06:59'),
(10, 'New Category', '', 0, '', '', 'Inactive', NULL, 10, '2011-10-25 12:07:01', '2011-10-25 12:07:01'),
(11, 'New Category', '', 0, '', '', 'Inactive', NULL, 11, '2011-10-25 12:07:03', '2011-10-25 12:07:03'),
(12, 'New Category', '', 0, '', '', 'Inactive', NULL, 12, '2011-10-25 12:07:05', '2011-10-25 12:07:05'),
(13, 'New Category', '', 0, '', '', 'Inactive', NULL, 13, '2011-10-25 12:07:06', '2011-10-25 12:07:07'),
(14, 'New Category', '', 0, '', '', 'Inactive', NULL, 14, '2011-10-25 12:07:08', '2011-10-25 12:07:09'),
(15, 'New Category', '', 0, '', '', 'Inactive', NULL, 15, '2011-10-25 12:07:11', '2011-10-25 12:07:12'),
(16, 'New Category', '', 0, '', '', 'Inactive', NULL, 16, '2011-10-25 12:07:13', '2011-10-25 12:07:14'),
(17, 'New Category', '', 0, '', '', 'Inactive', NULL, 17, '2011-10-25 12:09:41', '2011-10-25 12:09:42'),
(18, 'New Category', '', 0, '', '', 'Inactive', NULL, 18, '2011-10-25 12:09:47', NULL),
(19, 'New Category', '', 0, '', '', 'Inactive', NULL, 19, '2011-10-25 12:09:48', NULL),
(20, 'New Category', '', 0, '', '', 'Inactive', NULL, 20, '2011-10-25 12:09:48', NULL),
(21, 'New Category', '', 0, '', '', 'Inactive', NULL, 21, '2011-10-25 12:09:49', NULL),
(22, 'New Category', '', 0, '', '', 'Inactive', NULL, 22, '2011-10-25 12:09:50', NULL),
(23, 'New Category', '', 0, '', '', 'Inactive', NULL, 23, '2011-10-25 12:09:51', NULL),
(24, 'New Category', '', 0, '', '', 'Inactive', NULL, 24, '2011-10-25 12:09:51', NULL),
(25, 'New Category', '', 0, '', '', 'Inactive', NULL, 25, '2011-10-25 12:09:52', NULL),
(26, 'New Category', '', 0, '', '', 'Inactive', NULL, 26, '2011-10-25 12:09:53', NULL),
(27, 'New Category', '', 0, '', '', 'Inactive', NULL, 27, '2011-10-25 12:09:54', NULL),
(28, 'New Category', '', 0, '', '', 'Inactive', NULL, 28, '2011-10-25 12:09:55', NULL),
(29, 'New Category', '', 0, '', '', 'Inactive', NULL, 29, '2011-10-25 12:09:56', NULL),
(30, 'New Category', '', 0, '', '', 'Inactive', NULL, 30, '2011-10-25 12:09:57', NULL);

부모가 전혀 없으며 모든 카테고리가 최상위에 있습니다. 문제는 여전히 있습니다. PHP에 의해 실행 된 다음 쿼리는 실패합니다.

SELECT * FROM `categories` WHERE `parent`=22 ORDER BY `order`,`name`

여기에 EXPLAIN:

mysql> EXPLAIN SELECT * FROM `categories` WHERE `parent`=22 ORDER BY `order`,`name`;
+----+-------------+------------+------+---------------+--------+---------+-------+------+-----------------------------+
| id | select_type | table      | type | possible_keys | key    | key_len | ref   | rows | Extra                       |
+----+-------------+------------+------+---------------+--------+---------+-------+------+-----------------------------+
|  1 | SIMPLE      | categories | ref  | parent        | parent | 4       | const |    1 | Using where; Using filesort | 
+----+-------------+------------+------+---------------+--------+---------+-------+------+-----------------------------+
1 row in set (0.00 sec)

업데이트 # 2 : 이제 다음을 모두 시도했습니다.

  1. 이 테이블과 데이터를 동일한 소프트웨어로 다른 사이트에 복사했습니다. 문제가 표를 따르지 않았습니다 . 이 하나의 데이터베이스에 국한된 것 같습니다.
  2. gbn의 답변이 제안한대로 색인을 변경했습니다. 문제는 남아 있었다.
  3. 테이블을 삭제하고 테이블로 다시 InnoDB만들고 위의 동일한 30 개의 테스트 행을 삽입했습니다. 문제는 남아 있었다.

이 데이터베이스와 관련이 있다고 생각합니다 ...

업데이트 # 3 : 데이터베이스를 완전히 삭제하고 새 이름으로 다시 작성하여 데이터를 가져 왔습니다. 문제는 남아있다.

나는 실제 PHP 문이에 대한 호출임을 발견했다 mysql_query(). 이 이후의 명령문은 실행되지 않습니다.

호출이 중단되는 동안 MySQL은 스레드를 휴면 상태로 표시합니다!

mysql> show full processlist;
+-------+------------------+-----------------------------+----------------------+---------+------+-------+-----------------------+
| Id    | User             | Host                        | db                   | Command | Time | State | Info                  |
+-------+------------------+-----------------------------+----------------------+---------+------+-------+-----------------------+
|  5560 | root             | localhost                   | problem_db           | Query   |    0 | NULL  | show full processlist |  
                          ----- many rows which have no relevancy; only rows from this customer's app are shown ------
| 16341 | shared_db        | oak01.sitepalette.com:53237 | shared_db            | Sleep   |  308 |       | NULL                  | 
| 16342 | problem_db       | oak01.sitepalette.com:60716 | problem_db           | Sleep   |  307 |       | NULL                  | 
| 16344 | shared_db        | oak01.sitepalette.com:53241 | shared_db            | Sleep   |  308 |       | NULL                  | 
| 16346 | problem_db       | oak01.sitepalette.com:60720 | problem_db           | Sleep   |  308 |       | NULL                  |  
+-------+------------------+-----------------------------+----------------------+---------+------+-------+-----------------------+

업데이트 # 4 : 위에서 설명한 테이블과 556 개의 행이 있는 두 테이블 의 조합으로 범위를 좁혔습니다 . 경우 테이블 미만 556 개 행을 포함, 또는 테이블 미만 30 개 행이 문제가 사라집니다. 그것은 내가 여기에 타격하고있는 일종의 MySQL 한계와 같습니다 ...categoriesmedia_imagesmedia_imagescategories

업데이트 # 5 : 방금 데이터베이스를 다른 MySQL 서버로 옮기려고했는데 문제가 사라졌습니다 ... 그래서 프로덕션 데이터베이스 서버와 관련이 있습니다 ...

업데이트 # 6 : 매번 멈추는 관련 PHP 코드는 다음과 같습니다.

    public function find($type,$conditions='',$order='',$limit='')
    {
            if($this->_link == self::AUTO_LINK)
                    $this->_link = DFStdLib::database_connect();

            if(is_resource($this->_link))
            {
                    $q = "SELECT ".($type==_COUNT?'COUNT(*)':'*')." FROM `{$this->_table}`";
                    if($conditions)
                    {
                            $q .= " WHERE $conditions";
                    }
                    if($order)
                    {
                            $q .= " ORDER BY $order";
                    }
                    if($limit)
                    {
                            $q .= " LIMIT $limit";
                    }

                    switch($type)
                    {
                            case _ALL:
                                    DFSkel::log(DFSkel::LOG_DEBUG,"mysql_query($q,$this->_link);");
                                    $res = @mysql_query($q,$this->_link);
                                    DFSkel::log(DFSkel::LOG_DEBUG,"res = $res");

이 코드는 프로덕션 환경 에 있으며 다른 모든 설치에서 잘 작동 합니다. 한 번의 설치로에 중단됩니다 $res = @mysql_query($q,$this->_link);. 난을 참조하기 때문에 알고 mysql_query디버그 로그에, 아니라 res =, 나는이시 strace는 PHP 공정, 그것은에 걸려 있어요read(

업데이트 # 무엇이든 -가 - 인 - I-증오 this- & (# ^ & -issue! 이 지금 일어나고 시작했다 둘 개 . 내 고객 난 그냥 해고 tcpdump그것은 모양 의 MySQL의 응답이 완전히 전송되지 않습니다. 전체 MySQL 응답을 보내기 전에 TCP 스트림이 중단 된 것 같습니다 (그러나 여전히 조사 중입니다)

업데이트 # 완전히 사라졌습니다.하지만 미친 듯이 작동합니다. 이제는 말이되지 않지만 해결책을 찾았습니다. MySQL 서버의 eth2인터페이스에 두 번째 IP 주소를 할당하고 NFS 트래픽에 하나의 IP를 사용하고 MySQL에 두 번째 IP를 사용하면 문제가 사라집니다. NFS + MySQL 트래픽이 모두 해당 IP로 이동하는 경우 어떻게 든 IP 주소를 오버로드하는 것과 같습니다. 그러나 IP 주소를 "오버로드"할 수 없기 때문에 이치에 맞지 않습니다. 인터페이스의 포화 상태는 확실하지만 동일한 인터페이스입니다.

도대체 무슨 일이 일어나고 있는지 아십니까? 이것은 아마도 unix.SE 또는 ServerFault 질문 일 것입니다 ... (적어도 지금은 작동합니다 ...)

업데이트 # why-oh-why : 이 문제는 여전히 발생합니다. 두 개의 다른 IP를 사용해도 시작되었습니다. 새 개인 IP를 계속 만들 수 있지만 분명히 문제가 있습니다.


여기에 mysql 내에서 재귀 계층 적 쿼리를 수행 할 때 잠재적 인 '다른 질문' 에 대한 링크가 있습니다.
— 데릭 다우니

@DTest 확실히, 나는 그것을 잠시 후에 추가 할 것입니다. 다른 링크 주셔서 감사합니다!
— Josh

우리는 적극적 으로이 질문을 찾는 사람을 위해이 문제 를 해결하기 위해 노력하고 있습니다.
— Josh

조쉬 쿼리는 MySQL 클라이언트와 PHPMyAdmin에서 정상적으로 실행된다고 말했습니까? PHP 응용 프로그램 만 사용합니까?
— marcio

@marcioAlmada 예, 맞습니다. 나는이 모든 상황에 극도로 혼란스러워합니다.
— Josh

답변:


5

쿼리 계획에서 정확히 무슨 일이 일어나고 있는지에 대한 일반적인 프로파일 링을 위해 PROFILING을 시도 할 수 있습니다.

기본적으로 행 아웃 위치를 결정하는 데 도움이됩니다.

물론,로 MySQL을 컴파일 한 경우에만 작동합니다 enable-profiling.


3

아이디어 (MyISAM에 적용되는지 확실하지 않지만 InnoDB와 협력합니다)

인덱스 "parent"를 변경하여 parent, order, name의 3 개 열에 있도록하십시오. 이것은 WHERE와 일치합니다.

를 제거하십시오 SELECT *. 필요한 열만 사용하십시오. 인덱스 "parent"에 다른 열을 추가하십시오.

이렇게하면 옵티마이 저가 이제 인덱스를 다루기 때문에 인덱스 만 사용할 수 있습니다. 인덱스가 해당 쿼리에 유용하지 않기 때문에 전체 테이블을 읽어야합니다.


parent인덱스를 다음과 같이 변경 한 후에도 문제가 지속 됩니다.(parent, order, name)
— Josh

3

프로덕션 DB 서버에서 몇 가지 사항을 확인하겠습니다.

  • 점검 # 1 : / var / lib / mysql이 마운트 된 데이터 볼륨에 불량 블록이 없는지 확인하십시오. fsck (파일 시스템 검사) 를 수행하려면 가동 중지 시간이 필요할 수 있습니다.
  • 확인 # 2 : 테이블이 DML (INSERT / UPDATE / DELETE) 또는 SELECT로 무겁지 않은지 확인하십시오
  • 확인 # 3 : PHP가 mysql_close ()를 올바르게 발행 하고 앱이 DB 연결을 닫는 데 Apache에 의존하지 않는지 확인하십시오 . 그렇지 않으면 PHP가 MySQL에 의해 효과적으로 닫힌 DB 연결 리소스를 사용하려고 할 때 일종의 경쟁 조건이있을 수 있습니다.
  • 확인 # 4 : DB 서버의 OS가 PHP와 MySQL의 눈으로 닫은 netstat 목록의 netstat 목록에 TIME_WAITs가 없는지 확인하십시오. 그러나 OS는 여전히 연결 상태입니다. 당신은 이것을 볼 수 있습니다netstat | grep -i mysql | grep TIME_WAIT
  • 확인 # 5 : mysql_pconnect를 사용하고 있지 않은지 확인하십시오 . 영구 연결이 올바르게 닫히지 않는 것에 대한 공개 버그 보고서가 여전히 있습니다 . 나는 그 연결에 액세스하려고하는 것을 싫어합니다.
  • 확인 # 6 :로드 밸런서, 스위치, 방화벽 및 DNS 서버를 통한 DB 트래픽 처리량이 프로덕션 DB 서버 및 기타 외부 서버에 대해 동일한 지 확인하십시오. 개인적으로, 나는 mysql.user 및 mysql.db의 호스트 열에서 DNS 이름을 사용하는 것을 싫어합니다. 나는 보통 고객들이 그것들을 제거하고 하드 IP로 교체하도록한다. 또한 mysqld의 DNS 사용을 추가 skip-host-cache하고 skip-name-resolve무시합니다. 따라서 @marcioAlmada의 답변과 점검 점을 살펴볼 수 있습니다.

이러한 점검이 유용하지 않다고 생각되면 최대한 빨리 의견을 말하고 답변을 제거 할 수 있도록 알려주십시오.


나는 이것이 유용한 답변이라고 생각합니다! 나는 하지 나는 그것을 시도 할 수 있도록해야합니다 나는 모든 연결을 종료하고 있습니다. 나는 그것이 나쁜 블록 (RAID10에 있음) 을 가지고 있다고 생각 하지 않지만 /var쉽게 잘못 될 수 있습니다. netstat을 확인하겠습니다. 좋은 생각입니다! 나는 사용하지 mysql_pconnect않지만 network / dns / etc를 검사 할 것입니다.
— Josh

@Josh : 불량 블록이 표시되면에 블록에 대한 메시지 가 많이 있습니다 dmesg. 하드웨어 RAID가 없으면 하드웨어 RAID 모니터 프로그램을 확인하십시오.
— derobert

이런 일이 발생하면 가끔씩 (항상 그런 것은 아님) 단일 TIME_WAITMySQL 연결을 보게 됩니다. 어떤 방법으로도 많은 수가 없습니다 ... 테이블이 활동으로 무겁지 않습니다.
— Josh

2

a) 조쉬 안녕. 쿼리는 MySQL 클라이언트와 PHPMyAdmin에서 정상적으로 실행된다고 말했습니까? PHP 응용 프로그램 만 사용합니까?
b) @marcioAlmada 예, 맞습니다

나는 당신이 schrödinbug을 쳤다고 말하고 싶습니다 . 당신은 시도 할 수 die()후 또는 쿼리 전을위한 코드 탐색하려고 if statements매우 드물게 발생한다합니다. 코드가 없을 때 무엇이 ​​걸리는지 말하기는 어렵습니다.

편집 : 나는 현재이 줄이라고 말할 것입니다

$this->_link = DFStdLib::database_connect();

함수가 호출 될 때마다 연결을 생성합니다. 문제 일 수도 있습니다. my.cnf의 max_connections는 무엇입니까?


나는 그것이 어디에 걸려 있는지 정확히 안다 : 그것은 결코 전화를 mysql_query()
— Josh

1
+ -10 줄의 코드를 게시 할 수 있습니까?
— 창세기

끝난. tcpdump 앞으로 며칠 안에 이것을 디버깅 할 것 입니다. 이건 정말 경우 입니다 PHP는 문제, 그때는 SO에 대한 새로운 질문을 게시해야한다.
— Josh

@ 조쉬 : 내 대답을 업데이트
— 창세기

@genesis 감사합니다 ...하지만 두 가지 이유가 아닙니다. 1. "자동으로 데이터베이스 링크 설정"기능을 사용하는 경우에만 해당 코드가 호출됩니다.이 기능 $this->_link은 상수 로 설정 하여 수행됩니다 self::AUTO_LINK. 2. 내가 그래도 해당 코드는 if : if($this->_link == self::AUTO_LINK)에 있으며 다음 줄 $this->_link = DFStdLib::database_connect();은 값을 변경 $this->_link하므로 if다시 실행되지 않습니다. 스레드 당 데이터베이스에 단 하나의 연결이 있다고 확신합니다. (프로세스 목록 참조)
— Josh

1

MySQL 문제가 아닌 PHP 문제라고 확신하지만 MySQL 서버를 전환 할 때 왜 작동합니까?

몇 가지 시도 :

  • 방화벽 ?? 방화벽으로 인해 응용 프로그램을 차단하고 프로덕션 데이터베이스 서버에 대한 요청을 막거나 그 반대로 할 수 있습니까?

  • 연결 구성 또는 IP 주소에 도메인 이름을 사용하고 있습니까? 도메인 이름을 사용하면 데이터베이스 상호 작용이 약간 느려질 수 있으며 짧은 PHP 최대 스크립트 실행 시간과 결합 하면 영원히 행 아웃이 발생합니다.

이 마지막 제안은 데이터베이스 서버를 전환 할 때 이상한 변수 동작을 설명하는 것 같습니다. 하나는 다른 것보다 훨씬 빠르게 응답 할 수 있으며, 발견 된 모든 레코드에 대해 보조 쿼리가 있기 때문에이 가설은 애플리케이션이 특정 양의 쿼리 된 결과로만 지연되는 이유를 설명합니다 (> 30).

적어도 우리는 일차적 인 결론에 도달했습니다. 확실히 문제는 MySQL 서버 istelf와 관련이 없습니다. 설명서를 살펴본 결과 특정 상황에 맞는 기능 제한이없는 것 같습니다. 재귀 테이블 및 특정 항목 수에 문제가 없었습니다.

희망이 도움이됩니다.


0

mysql_query () 명령을 기본 PHP5 드라이버로 업데이트하려고 했습니까? mysqli :: query ()? 이것이 무엇이든 할 지 확실하지 않지만 기회가 될 수도 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.