때때로 쿼리가 느려지는 이유는 무엇입니까?


16

Windows Server 2008 R2에서 MySQL 5.1을 실행하고 있습니다.

우리는 최근 데이터베이스에서 진단을 해왔 으며 설명 할 수없는 방해물을 발견했습니다 . 오랜 시간이 걸린 쿼리 (> 2000ms)를 기록 할 코드를 추가했습니다. 그 결과는 놀라웠습니다 (그리고 교착 상태에 대한 설명 일 수도 있습니다).

일반적으로 시간이 거의 걸리지 않는 쿼리 (<10ms)는 4 초에서 13 초가 걸립니다. 분명히, 이들은 지속적으로 (1 초에 몇 번) 실행되고 이러한 쿼리 시간 급증으로 고통받지 않는 쿼리입니다.

우리는 명백한 실수를 찾아서 많은 운이 없었던 색인을 살펴 보았습니다.

최신 정보

사람들 테이블 :

| people | CREATE TABLE `people` (
`people_id` bigint(20) NOT NULL AUTO_INCREMENT,
`company_id` bigint(20) NOT NULL,
`name` varchar(255) DEFAULT NULL,
`password` varchar(255) DEFAULT NULL,
`temp_password` varchar(10) DEFAULT NULL,
`reset_password_hash` varchar(255) DEFAULT NULL,
`email` varchar(255) DEFAULT NULL,
`phone` varchar(32) DEFAULT NULL,
`mobile` varchar(32) DEFAULT NULL,
`iphone_device_id` varchar(160) DEFAULT NULL,
`iphone_device_time` datetime DEFAULT NULL,
`last_checkin` datetime DEFAULT NULL,
`location_lat` double DEFAULT NULL,
`location_long` double DEFAULT NULL,
`gps_strength` smallint(6) DEFAULT NULL,
`picture_blob_id` bigint(20) DEFAULT NULL,
`authority` int(11) NOT NULL DEFAULT '0',
`active` tinyint(1) NOT NULL DEFAULT '1',
`date_created` datetime NOT NULL,
`last_login` datetime NOT NULL,
`panic_mode` tinyint(1) NOT NULL DEFAULT '0',
`battery_level` double DEFAULT NULL,
`battery_state` varchar(32) DEFAULT NULL,
PRIMARY KEY (`people_id`),
KEY `email` (`email`),
KEY `company_id` (`company_id`),
KEY `iphone_device_id` (`iphone_device_id`),
KEY `picture_blob_id` (`picture_blob_id`),
CONSTRAINT `people_ibfk_1` FOREIGN KEY (`company_id`) REFERENCES `companies` (`company_id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `people_ibfk_2` FOREIGN KEY (`picture_blob_id`) REFERENCES `blobs` (`blob_id`) ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=4658 DEFAULT CHARSET=utf8 |

인덱스 :

+--------+------------+------------------+--------------+------------------+-----------+-------------+----------+--------+------+------------+---------+
| Table  | Non_unique | Key_name         | Seq_in_index | Column_name      | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment |
+--------+------------+------------------+--------------+------------------+-----------+-------------+----------+--------+------+------------+---------+
| people |          0 | PRIMARY          |            1 | people_id        | A         |        3502 |     NULL | NULL   |      | BTREE      |         |
| people |          1 | email            |            1 | email            | A         |        3502 |     NULL | NULL   | YES  | BTREE      |         |
| people |          1 | company_id       |            1 | company_id       | A         |        3502 |     NULL | NULL   |      | BTREE      |         |
| people |          1 | iphone_device_id |            1 | iphone_device_id | A         |        3502 |     NULL | NULL   | YES  | BTREE      |         |
| people |          1 | picture_blob_id  |            1 | picture_blob_id  | A         |        3502 |     NULL | NULL   | YES  | BTREE      |         |
+--------+------------+------------------+--------------+------------------+-----------+-------------+----------+--------+------+------------+---------+

서버의 테이블에 ~ 5000 행이있어 문제가 발생합니다.


1
이전 두 질문에서 아직 보여주지 않은 것이 있습니다. 이 질문에 three (3) thiings를 추가하십시오 : 1) 테이블 사람들을 보여주십시오 \ G 2) 사람들로부터 인덱스를 보여주십시오; 3) 인원수 선택 (1)
RolandoMySQLDBA

@RolandoMySQLDBA 나는 내일 출근하자마자 그렇게 할 것입니다. 건배 :)
RedBlueThing

내 답변을 업데이트했습니다. 읽어주세요 !!!
RolandoMySQLDBA

@RolandoMySQLDBA 감사합니다 :). 여전히이 물건을 파싱하고 있습니다. 우리가 어떻게 가는지 알려 드리겠습니다.
RedBlueThing

답변:


14

이전 두 질문 ( Question1 , Question2 ) 의 UPDATE 쿼리는 PRIMARY KEY에 의해 행 수준 잠금으로 테이블 'people'을 때리고 있습니다. 이것은 2011 년 6 월 6 일 오전 1시 10 분에 Question1에서 다시 언급 한 것입니다

모든 트랜잭션이 PRIMARY 키를 통과하고 있습니다. PRIMARY는 InnoDB의 클러스터형 인덱스이므로 PRIMARY 키와 행 자체가 함께 있습니다. 따라서 행 순회 및 기본 키는 하나이며 동일합니다. 따라서 PRIMARY KEY의 인덱스 잠금도 행 수준 잠금입니다.

인덱스 속도 저하를 유발할 수있는 다른 사항은 아직 고려되지 않았습니다. InnoDB에서 NON-UNIQUE 인덱스 사용. 고유하지 않은 인덱스를 사용하는 InnoDB의 모든 인덱스 조회에는 고유하지 않은 키에 연결된 각 행의 rowID도 있습니다. rowID는 기본적으로 Clustered Index에서 나온다 . 고유하지 않은 인덱스를 업데이트하면 테이블에 기본 키가없는 경우에도 항상 클러스터형 인덱스와 상호 작용해야합니다.

고려해야 할 또 다른 사항은 인덱스에서 BTREE 노드를 관리하는 프로세스입니다. 때로는 노드의 페이지 분할이 필요합니다. 고유하지 않은 인덱스의 BTREE 노드에있는 모든 항목에는 클러스터형 인덱스 내의 rowID와 함께 고유하지 않은 필드가 포함됩니다. 데이터 무결성을 방해하지 않으면 서 이러한 BTREE 페이지의 분할을 적절히 완화하려면 rowID와 연관된 행이 내부적으로 행 레벨 잠금을 경험해야합니다.

'people'테이블에 고유하지 않은 인덱스가 많은 경우 테이블 스페이스에 많은 수의 인덱스 페이지를 준비하고 때때로 작은 행 잠금을 잠글 수 있도록 준비하십시오.

분명하지 않은 또 다른 요소가 있습니다 : 키 인구

때때로 인덱스가 채워지면 인덱스를 구성하는 키 값이 시간이 지남에 따라 기울어 질 수 있으며 MySQL Query Optimizer가 키 조회에서 인덱스 스캔으로, 마지막으로 전체 테이블 스캔으로 전환 될 수 있습니다. lopsidedness ot 키를 보상하기 위해 새 인덱스로 테이블을 다시 디자인하지 않으면 제어 할 수 없습니다. 'people'테이블의 테이블 구조, 'people'테이블 수 및 'people'테이블의 show 인덱스 출력을 제공하십시오 .

쿼리에서 PRIMARY KEY 만 사용하더라도 고유하지 않은 인덱스에서 키의 lopsidedness는 여전히 BTREE 밸런싱 및 페이지 분할이 필요합니다. 이러한 BTREE 관리는 원하지 않는 간헐적 인 행 레벨 잠금으로 인해 현저한 속도 저하를 초래합니다.

업데이트 2011-06-14 22:19

질문 1의 쿼리

UPDATE people SET company_id = 1610, name = '<name>', password = '<hash>',
temp_password = NULL, reset_password_hash = NULL, email = '<redacted>@yahoo.com',
phone = NULL, mobile = '<phone>', iphone_device_id = 'android:<id>-<id>',
iphone_device_time = '2011-06-06 05:35:09', last_checkin = '2011-06-06 05:24:42',
location_lat = <lat>, location_long = -<lng>, gps_strength = 3296,
picture_blob_id = 1190,
authority = 1, active = 1, date_created = '2011-04-13 20:21:20',
last_login = '2011-06-06 05:35:09', panic_mode = 0,
battery_level = NULL, battery_state = NULL WHERE people_id = 3125

UPDATE people SET company_id = 1610, name = '<name>', password = '<hash>',
temp_password = NULL, reset_password_hash = NULL, email = '<redacted>@yahoo.com',
phone = NULL, mobile = '<phone>', iphone_device_id = 'android:<id>-<id>-<id>-<id>',
iphone_device_time = '2011-06-06 05:24:42', last_checkin = '2011-06-06 05:35:07',
location_lat = <lat>, location_long = -<lng>, gps_strength = 3296,
picture_blob_id = 1190,
authority = 1, active = 1, date_created = '2011-04-13 20:21:20',
last_login = '2011-06-06 05:35:09', panic_mode = 0,
battery_level = NULL, battery_state = NULL WHERE people_id = 3125

사건의 순서를 묘사하십시오

  1. PRIMARY KEY로 행 찾기
  2. 행 및 클러스터형 인덱스 잠금
  3. 업데이트중인 모든 열에 대한 MVCC 데이터 생성
  4. 4 개의 열이 색인화됩니다 (이메일, company_id, iphone_device_id, picture_blob_id)
  5. 각 인덱스에는 BTREE 관리가 필요합니다
  6. 동일한 트랜잭션 공간 내에서 1-5 단계를 동일한 행에서 반복하여 동일한 열을 업데이트하려고합니다 (두 쿼리에서 동일하게 이메일, 두 쿼리에서 동일하게 company_id, 두 쿼리에서 동일, picture_blob_id, iphone_device_id가 다름).

질문 2의 쿼리

UPDATE people SET iphone_device_id=NULL
WHERE iphone_device_id='iphone:<device_id_blah>' AND people_id<>666;

UPDATE people SET company_id = 444, name = 'Dad', password = '<pass>',
temp_password = NULL, reset_password_hash = NULL, email = '<redacted>@gmail.com',
phone = NULL, mobile = NULL, iphone_device_id = 'iphone:<device_id_blah>',
iphone_device_time = '2011-06-06 19:12:29', last_checkin = '2011-06-07 02:49:47',
location_lat = <lat>, location_long = <lng>, gps_strength = 66,
picture_blob_id = 1661,
authority = 1, active = 1, date_created = '2011-03-20 19:18:34',
last_login = '2011-06-07 11:15:01', panic_mode = 0, battery_level = 0.55,
battery_state = 'unplugged' WHERE people_id = 666;

첫 번째 쿼리가 people_id 666을 제외한 모든 항목을 업데이트하기 때문에이 두 쿼리는 더욱 혼란스러워집니다. 첫 번째 쿼리만으로 수백 개의 행이 고통스럽게 잠기고 있습니다. 두 번째 쿼리는 5 개의 이벤트 시퀀스를 실행하는 people_id 666을 업데이트합니다. 첫 번째 쿼리는 people_id 666을 제외한 모든 행에서 동일한 5 개의 이벤트 시퀀스를 실행하지만 iphone_device_id의 인덱스는 서로 다른 두 개의 쿼리가있는 인터셉트 과정에 있습니다. 누군가 BTREE 페이지를 선착순으로 고정시켜야합니다.

충돌 과정에서 이러한 두 쌍의 쿼리에 직면하여 하나의 인덱스 내에서 동일한 BTREE 페이지를 잠글 수있는 것은 InnoDB 또는 ACID 호환 RDBMS에 대한 장황한 경험이 될 수 있습니다. 따라서 쿼리가 AUTOCOMMIT = 1로 실행되도록하거나 더티 읽기를 허용하여 인덱스를 느리게하는 것은 이러한 쿼리 쌍의 운명입니다 (이와 같은 충돌은 READ-COMMITTED 및 READ-UNCOMMITED를 MVCC의 악몽으로 만들지 만).

업데이트 2011-06-15 10:29

@RedBlueThing : 질문 2의 쿼리에서 첫 번째 쿼리는 범위 쿼리이므로 많은 행 잠금이 수행됩니다. 또한 두 쿼리 모두 동일한 공간을 잠그려고 시도합니다. 0 페이지 번호 4611 n 비트 152는 일차적으로 클러스터형 인덱스 인 PRIMARY KEY에서 잠 깁니다.

최소한 일련의 이벤트를 기반으로 앱이 실행되도록하기 위해 시도 할 수있는 두 가지 옵션이 있습니다.

옵션 1)이 테이블을 MyISAM으로 변환하십시오 (적어도 개발 서버에서는). 각 UPDATE, INSERT 및 DELETE는 선착순으로 전체 테이블 잠금을 적용합니다.

옵션 2) SERIALIZABLE 격리 수준을 사용해보십시오 . 공유 모드에서 의도 된 모든 행을 잠급니다.

예상되는 일련의 이벤트는이 두 가지 대체 옵션을 사용하여 중단되거나 성공합니다. 이 두 가지 옵션이 모두 실패하면 앱을 살펴보고 쿼리 실행 순서의 우선 순위를 정해야합니다. 우선 순위를 설정 한 후에는 이러한 옵션을 취소 할 수 있습니다 (옵션 1의 경우 InnoDB로 돌아 가기, 옵션 2의 경우 기본 격리 수준으로 돌아 가기 [SERIALIZABLE 사용 중지]).


@RolandoMySQLDBA 요청한 세부 사항으로 질문을 업데이트했습니다.
RedBlueThing

@RolandoMySQLDBA 이것에 다시 한번 감사합니다. 질문 2에 대해 첫 번째 쿼리가 왜 수백 개의 행을 잠그는 지 궁금합니다. 장치 ID와 일치하는 비 666 행만 잠그지 않습니까? (즉, 단일 행)
RedBlueThing

@RolandoMySQLDBA 질문 1의 제안을 바탕으로 자동 커밋 설정을 확인하고 켜져 있는지 확인했습니다.
RedBlueThing

@RolandoMySQLDBA 첫 번째 질문 (행의 모든 ​​필드를 업데이트하는 것 제외)의 쿼리에 특정 문제가 있습니까? 쿼리에 13 초의 실행 시간을 설명하는 것이 있습니까? 네 개의 열을 인덱싱하는 것이 권장하는 것이 아니라는 사실을 알고 있지만 실제로 성능이 좋지 않습니까?
RedBlueThing

@RolandoMySQLDBA +1 및 모든 제안에 감사드립니다. 우리는 문제를 해결하기 위해 격리 수준을 변경하지 않았습니다. 대신 우리는 질문 2에 대한 부분적인 필드 업데이트를 수행하고 업데이트 경로에서 쿼리를 최적화했습니다. 짜잔! 더 이상 교착 상태가 없습니다. :)
RedBlueThing

3

'innodb %'와 같은 변수 표시 -특히 데이터 및 인덱스가 버퍼 풀의 크기에 도달하지 않은 경우 디스크를 이전보다 훨씬 더 많이 타격 할 수 있습니다. I / O는 큰 성능 저하 요인입니다.

대부분의 분야는 필요한 것의 두 배입니다. BIGINT (8 바이트)는 대부분의 ID에서 지나치게 과잉입니다. 5000 개의 행에는 SMALLINT UNSIGNED 만 필요합니다 (65K 제한, 2 바이트 만). 또는 안전 여유를 위해 MEDIUMINT를 사용하십시오.

DOUBLE은 8 바이트의 비용으로 16 자리의 유효 숫자를 제공합니다. battery_level의 유효 자릿수가 2 자리 이상입니까? FLOAT는 4 바이트를 사용합니다.

여기서 요점은 "더 작을수록-> 더 캐시-> 더 빠릅니다"입니다.

느린 쿼리를 보여주세요. 갑자기 느려진 것들 중 적어도 일부. 우리는 그것들 없이는 추측 만 할 수 있습니다. slowlog를 켜고 long_query_time = 1을 설정하십시오. 이들은 가장 느린 쿼리를 찾는 데 도움이됩니다.

"복합"인덱스의 이점을 이해하십니까?

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