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.