MySQL 기초(28) 인덱스 최적화 및 쿼리 최적화

데이터베이스 튜닝에 어떤 차원을 사용할 수 있습니까?

  • 인덱스 오류, 인덱스가 완전히 활용되지 않음 - 인덱스 생성
  • 관련 쿼리(설계 결함 또는 최후의 수단 요구 사항)에 JOIN이 너무 많습니다. - SQL 최적화
  • 서버 튜닝 및 다양한 매개변수 설정(버퍼링, 스레드 수 등) - my.cnf를 조정합니다.
  • 데이터가 너무 많음 - 하위 데이터베이스 및 하위 테이블

데이터베이스 튜닝에 대한 지식 포인트는 매우 분산되어 있습니다. 다른 DBMS, 다른 회사, 다른 직위, 다른 프로젝트는 다른 문제에 직면합니다. 여기서는 3개 장으로 나누어 자세히 설명하겠습니다.

SQL 쿼리 최적화에는 다양한 기술이 있지만 크게 두 부분 物理查询优化으로 나눌 수 있습니다.逻辑查询优化

  • 물리적 쿼리 최적화는 索引및 와 表连接方式같은 기술을 통해 최적화 되며 여기서 핵심은 인덱스 사용을 마스터하는 것입니다.
  • 논리적 쿼리 최적화는 SQL을 통해 等价变换쿼리 효율성을 높이는 것으로 , 직설적으로 말하면 쿼리 작성 방식을 바꾸면 실행 효율성이 높아질 수 있다는 뜻이다.

1. 데이터 준비

500,000개의 항목이 학생 목록에 삽입되고 10,000개의 항목이 수업 목록에 삽입됩니다.

1단계: 테이블 만들기

CREATE TABLE `class` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`className` VARCHAR(30) DEFAULT NULL,
`address` VARCHAR(40) DEFAULT NULL,
`monitor` INT NULL ,
PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

CREATE TABLE `student` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`stuno` INT NOT NULL ,
`name` VARCHAR(20) DEFAULT NULL,
`age` INT(3) DEFAULT NULL,
`classId` INT(11) DEFAULT NULL,
PRIMARY KEY (`id`)
#CONSTRAINT `fk_class_id` FOREIGN KEY (`classId`) REFERENCES `t_class` (`id`)
) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

2단계: 매개변수
설정 명령 실행: 기능 설정 생성 허용:

set global log_bin_trust_function_creators=1; # 不加global只是当前窗口有效。  


3단계: 각 데이터가 서로 다른지 확인하는 함수 만들기

#随机产生字符串
DELIMITER //
CREATE FUNCTION rand_string(n INT) RETURNS VARCHAR(255)
BEGIN
DECLARE chars_str VARCHAR(100) DEFAULT
'abcdefghijklmnopqrstuvwxyzABCDEFJHIJKLMNOPQRSTUVWXYZ';
DECLARE return_str VARCHAR(255) DEFAULT '';
DECLARE i INT DEFAULT 0;
WHILE i < n DO
SET return_str =CONCAT(return_str,SUBSTRING(chars_str,FLOOR(1+RAND()*52),1));
SET i = i + 1;
END WHILE;
RETURN return_str;
END //
DELIMITER ;
#假如要删除
#drop function rand_string;

무작위로 클래스 번호 생성

#用于随机产生多少到多少的编号
DELIMITER //
CREATE FUNCTION rand_num (from_num INT ,to_num INT) RETURNS INT(11)
BEGIN
DECLARE i INT DEFAULT 0;
SET i = FLOOR(from_num +RAND()*(to_num - from_num+1)) ;
RETURN i;
END //
DELIMITER ;

#假如要删除
#drop function rand_num;

4단계: 저장 프로시저 만들기

#创建往stu表中插入数据的存储过程
DELIMITER //
CREATE PROCEDURE insert_stu( START INT , max_num INT )
BEGIN
DECLARE i INT DEFAULT 0;
SET autocommit = 0; #设置手动提交事务
REPEAT #循环
SET i = i + 1; #赋值
INSERT INTO student (stuno, name ,age ,classId ) VALUES
((START+i),rand_string(6),rand_num(1,50),rand_num(1,1000));
UNTIL i = max_num
END REPEAT;
COMMIT; #提交事务
END //
DELIMITER ;
#假如要删除
#drop PROCEDURE insert_stu;

클래스 테이블에 데이터를 삽입하는 저장 프로시저 만들기

#执行存储过程,往class表添加随机数据
DELIMITER //
CREATE PROCEDURE `insert_class`( max_num INT )
BEGIN
DECLARE i INT DEFAULT 0;
SET autocommit = 0;
REPEAT
SET i = i + 1;
INSERT INTO class ( classname,address,monitor ) VALUES
(rand_string(8),rand_string(10),rand_num(1,100000));
UNTIL i = max_num
END REPEAT;
COMMIT;
END //
DELIMITER ;
#假如要删除
#drop PROCEDURE insert_class;

5단계: 저장 프로시저 호출

#执行存储过程,往class表添加1万条数据
CALL insert_class(10000);

#执行存储过程,往stu表添加50万条数据
CALL insert_stu(100000,500000);
CALL insert_stu(600000,1000000);

6단계: 테이블에서 인덱스를 삭제
하고 저장 프로시저 만들기

DELIMITER //
CREATE PROCEDURE `proc_drop_index`(dbname VARCHAR(200),tablename VARCHAR(200))
BEGIN
    DECLARE done INT DEFAULT 0;
    DECLARE ct INT DEFAULT 0;
    DECLARE _index VARCHAR(200) DEFAULT '';
    DECLARE _cur CURSOR FOR SELECT index_name FROM
    information_schema.STATISTICS WHERE table_schema=dbname AND table_name=tablename AND
    seq_in_index=1 AND index_name <>'PRIMARY' ;
    #每个游标必须使用不同的declare continue handler for not found set done=1来控制游标的结束
    DECLARE CONTINUE HANDLER FOR NOT FOUND set done=2 ;
    #若没有数据返回,程序继续,并将变量done设为2
    OPEN _cur;
    FETCH _cur INTO _index;
    WHILE _index<>'' DO
        SET @str = CONCAT("drop index " , _index , " on " , tablename );
        PREPARE sql_str FROM @str ;
        EXECUTE sql_str;
        DEALLOCATE PREPARE sql_str;
        SET _index='';
        FETCH _cur INTO _index;
    END WHILE;
    CLOSE _cur;
END //
DELIMITER ;

저장 프로시저 실행

CALL proc_drop_index("dbname","tablename");

2. 인덱스 실패 사례

MySQL에서 가장 효율적인 방법 중 하나는 提高性能테이블을 사용하는 것입니다 设计合理的索引. 인덱스는 데이터에 대한 효율적인 액세스를 제공하고 쿼리 속도를 높이므로 인덱스는 쿼리 속도에 중요한 영향을 미칩니다.

  • 快速地定位인덱스를 사용하면 테이블에서 레코드를 선택하여 데이터베이스 쿼리 속도를 높이고 데이터베이스 성능을 향상시킬 수 있습니다 .
  • 쿼리에서 인덱스가 사용되지 않으면 쿼리 문은 扫描表中的所有记录. 데이터 양이 많은 경우 쿼리 속도가 매우 느려집니다.

이는 대부분의 경우 인덱스를 구축하는 데 (기본적으로) 사용됩니다 B+树. 공간 컬럼 형태의 인덱스만을 사용하며 R-树, MEMORY 테이블도 이를 지원한다 hash索引.

실제로 옵티마이저는 인덱스를 사용할지 여부를 최종적으로 결정합니다. 옵티마이저는 무엇을 기반으로 합니까?기반이 cost开销(CostBaseOptimizer)아닌 规则(Rule-BasedOptimizer), 기반이 아닙니다 语义. 무엇이든 저렴한 비용으로 제공됩니다. 또한 SQL 문에서 인덱스를 사용하는지 여부는 데이터베이스 버전, 데이터 볼륨 및 데이터 선택성과 관련이 있습니다.

비용은 시간에 따라 결정되지 않습니다

2.1 내가 가장 좋아하는 전체 가치 일치

즉, 공동 인덱스 생성과 여러 인덱스 생성이 동시에 적용됩니다.

시스템에 자주 나타나는 SQL 문은 다음과 같습니다.

EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE age=30;
EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE age=30 and classId=4;
EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE age=30 and classId=4 AND name = 'abcd';

인덱싱 전 실행: (실행 시간에 주의)

mysql> SELECT SQL_NO_CACHE * FROM student WHERE age=30 and classId=4 AND name = 'abcd' ;
Empty set1 warning ( 0.28 sec)

색인

CREATE INDEX idx_age ON student(age ) ;

CREATE INDEX idx_age_classid ON student( age , classId);

CREATE INDEX idx_age_classid_name ON student( age , classId , name) ;

인덱스를 생성한 후 다음을 실행합니다.

mysql> SELECT SQL_NO_CACHE * FROM student WHERE age=30 and classId=4 AND name = 'abcd';
Empty set,1 warning (0.01 sec)

인덱스 생성 전 쿼리 시간이 0.28초, 인덱스 생성 후 쿼리 시간이 0.01초임을 알 수 있는데, 인덱스를 통해 쿼리 효율성을 크게 향상시킬 수 있다.

2.2 가장 좋은 왼쪽 접두사 규칙

MySQL은 조인트 인덱스를 구축할 때 가장 왼쪽에 있는 접두어 매칭 원칙, 즉 가장 왼쪽 우선순위를 따르며, 데이터를 검색할 때는 조인트 인덱스의 가장 왼쪽부터 매칭을 시작한다.

예시 1:

EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE student.age=30 AND student.name = 'abcd';
# 走`idx_age_classid_name`   使用了Using index condition

예 2:

EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE student.classid=1 AND student.name = 'abcd' ;

# 没有索引匹配上。 

예시 3: idx_age_classid_name 인덱스를 정상적으로 사용할 수 있나요?

EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE classid=4 and student.age=30 AND student.name = 'abcd' ;

여러 열이 색인화되는 경우 가장 왼쪽 접두사 규칙을 따라야 합니다. 이는 쿼리가 인덱스의 가장 왼쪽 열에서 시작하고 인덱스의 열을 건너뛰지 않음을 의미합니다.

mysq1> EXPLAIN SELECT SQL_NO_CACHE* FROM student WHERE student.age=30 AND student.name ='abcd';

여기에 이미지 설명을 삽입하세요.
**결론:**MySQL은 여러 필드에 대한 인덱스를 생성할 수 있으며 인덱스는 16개의 필드를 포함할 수 있습니다. 다중 컬럼 인덱스의 경우 인덱스를 사용하기 위한 ** 필터링 조건이 인덱스 생성 순서대로 충족되어야 하며, 한 필드를 건너뛰면 인덱스 뒤의 필드를 사용할 수 없습니다. **이 필드 중 첫 번째 필드가 쿼리 조건에 사용되지 않으면 다중 열(또는 결합) 인덱스가 사용되지 않습니다.

확장: Alibaba "Java 개발 매뉴얼"

인덱스 파일은 B-Tree의 가장 왼쪽 접두사 매칭 특성을 가지고 있으며, 왼쪽의 값이 정해지지 않은 경우 해당 인덱스를 사용할 수 없습니다.

2.3 기본 키 삽입 순서

스토리지 엔진을 사용하는 테이블 의 경우 InnoDB테이블의 데이터는 실제로 聚簇索引리프 노드에 저장됩니다. 레코드는 데이터 페이지에 저장되며 데이터 페이지와 레코드는 主键值从小到大레코드 순서에 따라 정렬 되므로 插入레코드가 主键值是依次增大가득 차면 데이터 페이지를 삽입할 때마다 다음 데이터 페이지로 전환하고 계속 삽입하십시오. 삽입한 기본 키 값이 갑자기 크거나 작으면 더 문제가 됩니다. 특정 데이터 페이지에 저장된 레코드가 가득 차서 저장되는 기본 키 값이 다음 사이에 있다고 가정합니다 1~100.

여기에 이미지 설명을 삽입하세요.
이때 기본 키 값이 의 다른 레코드가 삽입되면 9삽입 위치는 아래와 같습니다.
여기에 이미지 설명을 삽입하세요.
그런데 이 데이터 페이지가 이미 가득 차서 다시 삽입하면 어떻게 해야 합니까? 页面分裂현재 페이지를 두 페이지로 분할하고
이 페이지의 일부 레코드를 새로 생성된 페이지로 이동 해야 합니다 . 페이지 분할과 기록 이동은 무엇을 의미하나요? 의미: 性能损耗 !따라서
불필요한 성능 손실을 최대한 방지하려면 삽입 녹음을 허용하여 主键值依次递增이러한 성능 손실이 발생하지 않도록 하는 것이 가장 좋습니다.
따라서 우리는 다음과 같이 제안합니다. 기본 키를 갖도록 하고 AUTO_INCREMENT스토리지 엔진이 테이블에 대한 기본 키를 수동으로 삽입하는 대신 자체적으로 생성하도록 합니다.
예를 들면 person_info다음과 같습니다.

CREATE TABLE person_info(
    id INT UNSIGNED NOT NULL AUTO_INCREMENT,
    name VARCHAR(100) NOT NULL,
    birthday DATE NOT NULL,
    phone_number CHAR(11) NOT NULL,
    country varchar(100) NOT NULL,
    PRIMARY KEY (id),
    KEY idx_name_birthday_phone_number (name(10), birthday, phone_number)
);

사용자 정의 기본 키 열에는 속성이 id있으며 , 스토리지 엔진은 AUTO_INCREMENT레코드를 삽입할 때 자동으로 증가된
기본 키 값을 자동으로 채웁니다. 이러한 기본 키는 공간을 거의 차지하지 않고 순차적으로 작성되며 페이지 분할을 줄입니다.

2.4 계산, 함수, 유형 변환(자동 또는 수동)으로 인한 인덱스 오류

1.이 두 SQL을 작성하는 방법 중 어느 것이 더 좋습니까?

EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE student.name LIKE 'abc%';

EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE LEFT(student.name,3) = 'abc';
# 这个索引失效。因为用上函数了。

2. 인덱스 생성

CREATE INDEX idx_sno ON student (stuno) ;

3. 유형 1: 인덱스 최적화가 적용됩니다.

mysql> EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE student.name LIKE 'abc%';
mysql> SELECT SQL_NO_CACHE * FROM student WHERE student.name LIKE 'abc%';
+---------+---------+--------+------+---------+
| id | stuno | name | age | classId |
+---------+---------+--------+------+---------+
| 5301379 | 1233401 | AbCHEa | 164 | 259 |
| 7170042 | 3102064 | ABcHeB | 199 | 161 |
| 1901614 | 1833636 | ABcHeC | 226 | 275 |
| 5195021 | 1127043 | abchEC | 486 | 72 |
| 4047089 | 3810031 | AbCHFd | 268 | 210 |
| 4917074 | 849096 | ABcHfD | 264 | 442 |
| 1540859 | 141979 | abchFF | 119 | 140 |
| 5121801 | 1053823 | AbCHFg | 412 | 327 |
| 2441254 | 2373276 | abchFJ | 170 | 362 |
| 7039146 | 2971168 | ABcHgI | 502 | 465 |
| 1636826 | 1580286 | ABcHgK | 71 | 262 |
| 374344 | 474345 | abchHL | 367 | 212 |
| 1596534 | 169191 | AbCHHl | 102 | 146 |
...
| 5266837 | 1198859 | abclXe | 292 | 298 |
| 8126968 | 4058990 | aBClxE | 316 | 150 |
| 4298305 | 399962 | AbCLXF | 72 | 423 |
| 5813628 | 1745650 | aBClxF | 356 | 323 |
| 6980448 | 2912470 | AbCLXF | 107 | 78 |
| 7881979 | 3814001 | AbCLXF | 89 | 497 |
| 4955576 | 887598 | ABcLxg | 121 | 385 |
| 3653460 | 3585482 | AbCLXJ | 130 | 174 |
| 1231990 | 1283439 | AbCLYH | 189 | 429 |
| 6110615 | 2042637 | ABcLyh | 157 | 40 |
+---------+---------+--------+------+---------+
401 rows in set, 1 warning (0.01 sec)

두 번째 유형: 인덱스 최적화 실패

mysql> EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE LEFT(student.name,3) = 'abc';
mysql> SELECT SQL_NO_CACHE * FROM student WHERE LEFT(student.name,3) = 'abc';
+---------+---------+--------+------+---------+
| id | stuno | name | age | classId |
+---------+---------+--------+------+---------+
| 5301379 | 1233401 | AbCHEa | 164 | 259 |
| 7170042 | 3102064 | ABcHeB | 199 | 161 |
| 1901614 | 1833636 | ABcHeC | 226 | 275 |
| 5195021 | 1127043 | abchEC | 486 | 72 |
| 4047089 | 3810031 | AbCHFd | 268 | 210 |
| 4917074 | 849096 | ABcHfD | 264 | 442 |
| 1540859 | 141979 | abchFF | 119 | 140 |
| 5121801 | 1053823 | AbCHFg | 412 | 327 |
| 2441254 | 2373276 | abchFJ | 170 | 362 |
| 7039146 | 2971168 | ABcHgI | 502 | 465 |
| 1636826 | 1580286 | ABcHgK | 71 | 262 |
| 374344 | 474345 | abchHL | 367 | 212 |
| 1596534 | 169191 | AbCHHl | 102 | 146 |
...
| 5266837 | 1198859 | abclXe | 292 | 298 |
| 8126968 | 4058990 | aBClxE | 316 | 150 |
| 4298305 | 399962 | AbCLXF | 72 | 423 |
| 5813628 | 1745650 | aBClxF | 356 | 323 |
| 6980448 | 2912470 | AbCLXF | 107 | 78 |
| 7881979 | 3814001 | AbCLXF | 89 | 497 |
| 4955576 | 887598 | ABcLxg | 121 | 385 |
| 3653460 | 3585482 | AbCLXJ | 130 | 174 |
| 1231990 | 1283439 | AbCLYH | 189 | 429 |
| 6110615 | 2042637 | ABcLyh | 157 | 40 |
+---------+---------+--------+------+---------+
401 rows in set, 1 warning (3.62 sec)

타입은 'ALL'로 인덱스를 사용하지 않는다는 뜻이고, 쿼리 시간은 3.62초로 쿼리 효율이 이전보다 많이 떨어진다.

다른 예시:

  • 학생 테이블의 stuno 필드에 인덱스가 설정됩니다.

    CREATE INDEX idx_sno ON student(stuno);
    
    EXPLAIN SELECT SQL_NO_CACHE id, stuno, NAME FROM student WHERE stuno+1 = 900001;
    # 计算导致索引失效
    

    작업 결과:

여기에 이미지 설명을 삽입하세요.

  • 유형이 ALL인 이유는 계산으로 인해 인덱스 오류가 발생하기 때문입니다.

  • 인덱스 최적화가 적용됩니다(계산이 수행되지 않음).

    EXPLAIN SELECT SQL_NO_CACHE id, stuno, NAME FROM student WHERE stuno = 900000;
    

여기에 이미지 설명을 삽입하세요.

다른 예시:

  • 학생 테이블의 필드 이름에 인덱스가 설정됩니다.

    CREATE INDEX idx_name ON student (name) ; # 上面已经运行过了
    
  • 색인 실패:

    EXPLAIN SELECT id,stuno,name FROM student WHERE SUBSTRING( name,1,3)='abc';
    ## 使用函数导致失效,可以改用like abc%
    

2.5 유형 변환으로 인해 인덱스 오류가 발생함

다음 중 인덱스를 사용할 수 있는 SQL 문은 무엇입니까? (이름 필드에 인덱스가 설정되어 있다고 가정)

# 未使用到索引
EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE name=123;
# name=123发生类型转换,索引失效
# 使用到索引
EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE name='123';

# 使用到索引

2.6 범위 조건 오른쪽의 컬럼 인덱스가 유효하지 않습니다.

ALTER TABLE student DROP INDEX idx_name;
ALTER TABLE student DROP INDEX idx_age;
ALTER TABLE student DROP INDEX idx_age_classid;

show index from student;

EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE student.age=30 AND student.classId>20 AND student.name = 'abc' ;

여기에 이미지 설명을 삽입하세요.

범위 검색을 사용하므로 범위 검색 인덱스 다음의 인덱스는 무효가 됩니다.

팁: 범위 조건으로 인한 인덱스 실패로 인해 결정된 인덱스를 먼저 넣는 것을 고려할 수 있습니다.

예를 들어 위의 예에서는

create index idx_age_name_cid on student(age, name, classId);

여기서 이름은 범위 검색 classId 앞에 배치됩니다. . 인덱스가 적용됩니다.

범위에 속하는 것은 무엇입니까?

  1. 이상, 이상, 이하, 이하
  2. between

금액 쿼리, 날짜 쿼리와 같은 애플리케이션 개발의 범위 쿼리는 범위 쿼리인 경우가 많습니다. 공동 인덱스를 만들 때 나중에 넣는 것을 고려하세요.

2.7 같지 않음(!= 또는 <>) 인덱스가 잘못되었습니다.

  • 이름 필드에 대한 색인 생성

    CREATE INDEX idx_name ON student(NAME);
    
  • 인덱스가 유효하지 않은지 확인

    EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE student.name !='abc';
    

여기에 이미지 설명을 삽입하세요.

색인이 당신이 아는 것만 찾을 수 있다는 것은 절망적입니다.

2.8 null인 경우에는 인덱스를 사용할 수 있지만 null이 아닌 경우에는 인덱스를 사용할 수 없습니다.

EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE age IS NULL;

여기에 이미지 설명을 삽입하세요.

# is not null 索引失效
EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE age IS NOT NULL;

여기에 이미지 설명을 삽입하세요.

결론: 데이터 테이블을 디자인할 때는 기본값을 설정하는 것이 가장 좋으며 字段设置为 NOT NULL 约束, 예를 들어 INT 유형 필드의 기본값을 0으로 설정할 수 있습니다. 문자 유형의 기본값을 빈 문자열로 설정합니다.

확장: 마찬가지로 쿼리에서 를 사용하면 not like인덱스를 사용할 수 없으므로 전체 테이블 스캔이 발생합니다.

2.9 와일드카드 문자 %로 시작하는 인덱스는 유효하지 않습니다.

LIKE 키워드를 사용한 쿼리문에서 일치하는 문자열의 첫 번째 문자가 "%"이면 인덱스가 작동하지 않습니다. 인덱스는 "%"가 첫 번째 위치에 있지 않은 경우에만 작동합니다.

확장: Alibaba "Java 개발 매뉴얼"
[필수] 왼쪽 퍼지 또는 전체 퍼지 페이지 검색은 엄격히 금지됩니다. 필요한 경우 검색 엔진을 사용하여 해결하십시오.

2.10 OR 전후에 인덱스가 없는 컬럼이 있고 인덱스가 유효하지 않습니다.

WHERE 절에서 OR 앞의 조건 컬럼은 인덱싱되지만 OR 뒤의 조건 컬럼은 인덱싱되지 않으면 인덱스가 무효화된다. 즉, OR 전후의 두 조건의 열이 모두 인덱스인 경우에만 쿼리에 인덱스가 사용됩니다 .

OR의 의미는 두 조건 중 하나만 만족하면 因此只有一个条伴列进行了索引是没有意义的조건 컬럼이 인덱싱되지 않는 한 전체 테이블 스캔을 수행하므로 인덱싱된 조건 컬럼도 무효가 된다는 뜻이다.

2.11 데이터베이스와 테이블의 문자 집합은 utf8mb4를 균일하게 사용합니다.

utf8mb4(버전 5.5.3 이상에서 지원)의 통합 사용은 더 나은 호환성을 가지며, 통합 문자 세트는 문자 세트 변환으로 인해 발생하는 문자 왜곡을 방지할 수 있습니다. 다른
문자 세트를 먼저 비교해야 하므로 转换인덱스 오류가 발생합니다.

2.12 연습 및 일반적인 조언

연습 : 가설: index(a,b,c)
여기에 이미지 설명을 삽입하세요.
일반적인 조언:

  • 단일 열 인덱스의 경우 현재 쿼리에 대해 더 나은 필터링 기능을 갖춘 인덱스를 선택하십시오.
  • 결합된 인덱스를 선택할 때 현재 쿼리에서 필터링 가능성이 가장 높은 필드는 인덱스 필드 순서에서 앞쪽에 배치되어야 더 좋습니다. .
  • 결합된 인덱스를 선택할 때 현재 쿼리의 where 절에 더 많은 필드를 포함할 수 있는 인덱스를 선택하십시오.
  • 결합된 인덱스를 선택할 때 특정 필드가 범위 쿼리에 나타날 수 있는 경우 해당 필드를 인덱스 순서의 맨 마지막에 배치해 보세요.

즉, SQL 문을 작성할 때 인덱스 오류가 발생하지 않도록 노력하십시오.

3. 관련 쿼리 최적화

3.1 데이터 준비

#分类
CREATE TABLE IF NOT EXISTS `type`(
`id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`card` INT(10) UNSIGNED NOT NULL,
PRIMARY KEY ( `id` )
);

#图书
CREATE TABLE IF NOT EXISTS `book`(
	`bookid` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
    `card`INT(10) UNSIGNED NOT NULL,
	PRIMARY KEY (`bookid`)
);

#向分类表中添加20条记录
INSERT INTO type (card) VALUES (FLOOR(1 +(RAND() * 20)));



#向图书表中添加20条记录
INSERT INTO book(card) VALUES (FLOOR(1 +(RAND() * 20)) );


3.2 왼쪽 외부 조인 사용

EXPLAIN 분석을 시작해 보겠습니다.

EXPLAIN SELECT SQL_NO_CACHE * FROM `type` LEFT JOIN book ON type.card = book.card;

여기에 이미지 설명을 삽입하세요.
결론: 유형에는
인덱스 최적화를 추가하기 위해 All이 있습니다.

# 添加索引
ALTER TABLE book ADD INDEX Y(card); #【被驱动表】,可以避免全表扫描

EXPLAIN SELECT SQL_NO_CACHE * FROM `type` LEFT JOIN book ON type.card = book.card;

여기에 이미지 설명을 삽입하세요.

두 번째 줄의 유형이 ref로 변경되고 행도 명백한 최적화로 변경된 것을 확인할 수 있습니다. 이는 왼쪽 조인 속성에 의해 결정됩니다. LEFT JOIN 조건은 오른쪽 테이블에서 행을 검색하는 방법을 결정하는 데 사용되며 모든 행은 왼쪽에 있어야 하므로 오른쪽이 핵심이므로 인덱스를 생성해야 합니다.

색인의 한 면만 추가할 수 있는 경우 被驱动表색인을 추가하십시오.

ALTER TABLE `type` ADD INDEX X (card); #【驱动表】,无法避免全表扫描

EXPLAIN SELECT SQL_NO_CACHE * FROM `type` LEFT JOIN book ON type.card = book.card;

여기에 이미지 설명을 삽입하세요.
그 다음에:

DROP INDEX Y ON book;
EXPLAIN SELECT SQL_NO_CACHE * FROM `type` LEFT JOIN book ON type.card = book.card;

여기에 이미지 설명을 삽입하세요.

구동 인덱스를 제거하고 다시 조인 버퍼가 됩니다.

3.3 내부 조인 사용

전제 지식

여기에 이미지 설명을 삽입하세요.

drop index X on type;
drop index Y on book;#(如果已经删除了可以不用再执行该操作)

내부 조인으로 대체(MySQL은 자동으로 드라이버 테이블을 선택함)

EXPLAIN SELECT SQL_NO_CACHE * FROM type INNER JOIN book ON type.card=book.card;

여기에 이미지 설명을 삽입하세요.
인덱스 최적화 추가

ALTER TABLE book ADD INDEX Y (card);

EXPLAIN SELECT SQL_NO_CACHE * FROM type INNER JOIN book ON type.card=book.card;

여기에 이미지 설명을 삽입하세요.

# type 加索引
ALTER TABLE type ADD INDEX X (card);
# 观察执行情况
EXPLAIN SELECT SQL_NO_CACHE * FROM type INNER JOIN book ON type.card=book.card;

여기에 이미지 설명을 삽입하세요.

여기에 입력할 인덱스를 추가한 후에도 드라이버 테이블과 피동 테이블은 여전히 ​​동일합니다.

유형에 일부 데이터를 추가한 후

최적화 프로그램은 어떤 데이터가 더 작은지 결정합니다. 드라이버 테이블과 마찬가지로

결론적으로:

  • 内连接기본 구동 테이블은 최적화 프로그램에 의해 결정됩니다. 옵티마이저가 더 작다고 생각하는 비용이 드라이버 테이블로 사용됩니다.

  • 두 테이블 중 하나만 인덱스가 있는 경우 인덱스가 있는 테이블이 사용됩니다 被驱动表.

    • 이유: 드라이버 테이블을 완전히 확인해야 합니다. 인덱스가 있는지 없는지 확인해야 합니다.
  • 두 개의 인덱스가 모두 존재하는 경우 많은 양의 데이터가 사용됩니다 被驱动表(작은 테이블이 큰 테이블을 구동함).

    • 이유: 모든 드라이버 테이블을 검색해야 하며, 큰 테이블은 인덱스를 통해 더 빠르게 검색할 수 있다.

3.4 Join문의 원리

조인 방법은 여러 테이블을 연결하는데, 이는 본질적으로 테이블 간 데이터의 순환 일치입니다. MySQL 버전 5.5 이후 MySQL은 테이블을 연결하는 한 가지 방법, 즉 중첩 루프( Nested Loop Join)만 지원합니다. 연관된 테이블의 데이터 양이 많으면 조인 연관 실행 시간이 매우 길어집니다. MySQL 5.5 이후 버전에서 MySQL은 BNLJ알고리즘을 도입하여 중첩 실행을 최적화합니다.

1. 드라이버 테이블과 피동 테이블

구동 테이블은 마스터 테이블이고, 피동 테이블은 슬레이브 테이블과 비구동 테이블입니다.

  • 내부 조인의 경우:

    SELECT * FROM A JOIN B ON ...
    

    A는 드라이버 테이블이어야 합니까?반드시 그런 것은 아닙니다. 최적화 프로그램은 쿼리 문을 기반으로 최적화하고 먼저 확인할 테이블을 결정합니다. 먼저 쿼리된 테이블이 구동 테이블이고, 그 반대의 경우도 피동 테이블입니다. explain 키워드를 통해 보실 수 있습니다.

    • 외부 조인의 경우:

      SELECT * FROM A LEFT JOIN B ON ...
      #或
      SELECT *FROM B RIGHT JOIN A ON ...
      

      일반적으로 다들 A가 구동 테이블이고 B가 피동 테이블이라고 생각하는 경우가 많습니다. 그러나 반드시 그런 것은 아닙니다. 테스트는 다음과 같습니다.

      CREATE TABLE a(f1 INT,f2 INT,INDEX(f1))ENGINE=INNODB;
      
      CREATE TABLE b(f1 INT,f2 INT)ENGINE=INNODB;
      
      INSERT INTO a VALUES(1,1),(2,2),(3,3),(4,4),(5,5),(6,6);
      
      INSERT INTO b VALUES (3,3),(4,4),(5,5),(6,6),(7,7),(8,8);
      
      #测试1
      EXPLAIN SELECT* FROM a LEFT JOIN b ON (a.f1=b.f1)WHERE (a.f2=b.f2);
      
      #测试2
      EXPLAIN SELECT * FROM a LEFT JOIN b oN (a.f1=b.f1) AND (a.f2=b.f2);
      
      

      테스트 1 결과:
      여기에 이미지 설명을 삽입하세요.
      이 결론에 도달하는 것은 믿기지 않습니다. 표시 경고를 따라 살펴보겠습니다.
      여기에 이미지 설명을 삽입하세요.
      테스트 2 결과:
      여기에 이미지 설명을 삽입하세요.
      계속해서 경고 표시 \G
      여기에 이미지 설명을 삽입하세요.

2.Simple Nested-Loop Join(단순 중첩 루프 조인)

알고리즘은 매우 간단합니다. 테이블 A에서 데이터 1을 가져와 테이블 B를 통과하고 일치하는 데이터를 결과에 넣는 등... 구동 테이블 A의 각 레코드는 구동 테이블의 레코드에 대해 판단됩니다. 비:

여기에 이미지 설명을 삽입하세요.
이 예는 인덱스가 없는 전체 테이블 스캔입니다.

이 방법의 효율성이 매우 낮다는 것을 알 수 있는데, 위의 Table A 데이터 100개 항목과 Table B 데이터 1000개 항목을 기준으로 계산하면 A*B=100,000번이 된다. 비용 통계는 다음과 같습니다.

여기에 이미지 설명을 삽입하세요.
물론 mysql은 테이블을 그렇게 대략적으로 연결하지 않을 것이므로 Nested-Loop Join을 위한 다음 두 가지 최적화 알고리즘이 나타났습니다.

3.인덱스 중첩 루프 조인(Index Nested Loop Join)

Index Nested-Loop Join의 최적화 아이디어는 주로 목적을 위한 것이므로 减少内层表数据的匹配次数구동 테이블에 있어야 합니다 有索引. 외부 테이블 일치 조건은 내부 테이블 인덱스와 직접 일치하여 내부 테이블의 각 레코드와의 비교를 방지하므로 내부 테이블과의 일치 횟수가 크게 줄어듭니다.

여기에 이미지 설명을 삽입하세요.
구동 테이블의 각 레코드는 구동 테이블의 인덱스를 통해 접근하는데, 인덱스 쿼리 비용이 상대적으로 고정되어 있기 때문에 MySQL 옵티마이저는 레코드 수가 적은 테이블을 구동 테이블(외관)로 사용하는 경향이 있다.
여기에 이미지 설명을 삽입하세요.
구동 테이블을 인덱스하면 효율성이 매우 높지만, 해당 인덱스가 기본키 인덱스가 아닌 경우에는 테이블 쿼리를 수행해야 한다. 이에 비해 구동 테이블의 인덱스는 기본 키 인덱스이므로 더 효율적입니다.

4.Block Nested-Loop Join(중첩 루프 연결 차단)

인덱스가 있으면 인덱스 방식을 사용하여 조인하게 되며, 조인 컬럼에 인덱스가 없으면 구동 테이블을 너무 많이 스캔해야 한다. Driven 테이블에 접근할 때마다 해당 테이블의 레코드가 메모리에 로드된 후 Driver 테이블에서 레코드를 가져와서 일치시키고, 일치가 완료된 후 메모리를 비운 다음 레코드를 가져옵니다. 드라이버 테이블에서 로드된 후 피동 테이블이 로드되고, 테이블의 레코드가 메모리에 로드되어 반복적으로 일치되므로 IO 수가 크게 늘어납니다. Driven 테이블의 IO 수를 줄이기 위해 Block Nested-Loop Join 방식이 등장했습니다.

더 이상 구동 테이블의 데이터를 하나씩 획득하는 것이 아니라, 하나씩 획득하며, 데이터 컬럼의 해당 부분(조인 버퍼에 의해 크기가 제한됨)을 조인 버퍼에 캐시한 후 이를 캐싱하기 위해 도입되었습니다 join buffer缓冲区. 驱动表join구동 테이블 각각의 전체 테이블 스캔 레코드 - 조인 버퍼 의 모든 드라이버 테이블 레코드를 한 번에 일치시키고 内存中操作( ) 간단한 중첩 루프의 여러 비교를 하나로 병합하여 구동 테이블의 액세스 빈도를 줄입니다.

알아채다:

여기에는 관련 테이블의 컬럼뿐만 아니라 select 이후의 컬럼도 캐시됩니다. (드라이버 테이블이 저장됩니다)

N개의 조인 연관이 있는 SQL에서는 N-1개의 조인 버퍼가 할당됩니다. 따라서 쿼리 시 불필요한 필드를 줄여 조인 버퍼에 더 많은 컬럼을 저장할 수 있도록 노력하세요.

여기에 이미지 설명을 삽입하세요.
여기에 이미지 설명을 삽입하세요.
매개변수 설정:

  • block_nested_loop

    block_nested_loop` 상태를 전달합니다 show variables like '%optimizer_switch%'查看. 기본값은 활성화되어 있습니다. . - -

  • Join_buffer_size

    드라이버 테이블을 한번에 로드할 수 있는지 여부는 조인 버퍼가 모든 데이터를 저장할 수 있는지 여부에 따라 달라지며 기본적으로 join_buffer_size=256k.

    mysql> show variables like '%join_buffer%';
    +------------------+--------+
    | Variable_name    | Value  |
    +------------------+--------+
    | join_buffer_size | 262144 |
    +------------------+--------+
    1 row in set (0.00 sec)
    

    Join_buffer_size의 최대값은 32비트 시스템에서 4G에 적용할 수 있으며, 64비트 운영 체제에서 4G보다 큰 조인 버퍼 공간에 적용할 수 있습니다. (64비트 Windows를 제외하면 최대값은 4GB로 잘리고 경고가 표시됩니다.) 발급됩니다)

5.참가요약

1. 전체 효율 비교 : INLJ > BNLJ > SNLJ

2. 큰 결과 세트를 구동하려면 항상 작은 결과 세트를 사용하십시오(그 본질은 외부 루프의 데이터 양을 줄이는 것입니다)(작은 측정 단위는 테이블 행 수 * 각 행의 크기를 나타냄) )

# straight_join 不然优化器优化谁是驱动表  驱动表 straight_join 被驱动表
# 这个例子是说t2 的列比较多,相同的join buffer 加的会比较少。所以不适合用t2 作为  !!!驱动表
select t1.b,t2.* from t1 straight_join t2 on (t1.b=t2.b) where t2.id<=180;#推荐

select t1.b,t2.* from t2 straight_join t1 on (t1.b=t2.b) where t2.id<=100;#不推荐

3. 구동 테이블의 매칭 조건에 인덱스 추가(내부 테이블의 루프 매칭 횟수 감소)

4. 조인 버퍼 크기를 늘립니다. (한 번에 더 많은 데이터를 캐시할수록 내부 패키지가 테이블을 스캔하는 횟수가 줄어듭니다.)

5. 불필요한 필드 쿼리를 줄입니다 驱动表. (필드 수가 적을수록 조인 버퍼에 의해 캐시되는 데이터가 많아집니다.)

6. 구동 테이블로 사용할 테이블을 결정할 때 두 테이블을 각각의 조건에 따라 필터링해야 하며 필터링이 완료된 후 조인에 참여하는 각 필드의 총 데이터 양이 계산됩니다. 더 작은 데이터 볼륨은 "작은 테이블"이며 드라이버 테이블로 사용해야 합니다.

3.5 요약

  • 구동 테이블의 JOIN 필드에 인덱스가 생성되었는지 확인하세요.
  • JOIN이 필요한 필드의 경우 데이터 유형이 절대적으로 일관되게 유지되어야 합니다.
  • LEFT JOIN 시 작은 테이블을 구동 테이블로 선택합니다 大表作为被驱动表. 외부 루프 수를 줄이세요.
  • INNER JOIN이 수행되면 MySQL은 자동으로 小结果集的表选为驱动表. MySQL 최적화 전략을 신뢰하도록 선택하십시오.
  • 여러 테이블을 직접 연결할 수 있는 경우 하위 쿼리를 사용하지 않고 직접 연결해 보세요. (쿼리 수를 줄입니다)
  • 서브쿼리 사용은 권장하지 않으며 서브쿼리 SQL을 분할하여 여러 쿼리와 프로그램을 결합하거나 서브쿼리 대신 JOIN을 사용하는 것이 좋습니다.
  • 파생 테이블은 인덱스를 생성할 수 없습니다.

3.5.해시 조인

BNLJ는 MySQL 버전 8.0.20부터 폐기됩니다. 왜냐하면 MySQL 버전 8.0.18부터 해시 조인이 추가되고 기본적으로 해시 조인을 사용하기 때문입니다.

여기에 이미지 설명을 삽입하세요.

  • 중첩 루프:
    중첩 루프는 연결되는 데이터의 하위 집합이 작을 때 더 나은 선택입니다.

  • 해시 조인(Hash Join)은 大数据集连接이를 수행하는 일반적인 방법으로, 최적화 프로그램은 두 테이블 중 더 작은(상대적으로 작은) 테이블을 사용하여 조인 키(Join Key)를 사용하여 메모리에 테이블을 생성한 다음 더 散列表큰 테이블을 스캔하고 해시 테이블을 검색하여 일치하는 항목을 찾습니다. 해시 테이블입니다. 좋습니다.

    • 이 방법은 더 작은 테이블이 메모리에 완전히 배치되어 총 비용이 두 테이블에 액세스하는 데 드는 비용의 합이 되는 상황에 적합합니다.

    • 테이블이 매우 커서 메모리에 완전히 들어갈 수 없을 때 옵티마이저는 이를 여러 부분으로 나누고 若干不同的分区, 메모리에 넣을 수 없는 부분은 디스크의 임시 세그먼트에 기록합니다. 세그먼트가 필요하므로 I/O 성능을 최대한 향상시킬 수 있습니다.

    • 인덱스와 병렬 쿼리가 없는 대규모 테이블이 있는 환경에서 잘 작동하며 최고의 성능을 제공합니다. 대부분의 사람들은 조인(Join)의 대형 리프트라고 말합니다. Hash Join은 Hash의 특성에 따라 결정되는 동등한 조인(예: WHERE A.COL1=B.COL2)에만 적용할 수 있습니다.

여기에 이미지 설명을 삽입하세요.

4. 하위 쿼리 최적화

MySQL은 버전 4.1부터 하위 쿼리를 지원합니다. 하위 쿼리를 사용하여 SELECT 문의 중첩 쿼리를 수행할 수 있습니다. 즉, 하나의 SELECT 쿼리의 결과가 다른 SELECT 문의 조건으로 사용됩니다. 子查询可以一次性完成很多逻辑上需要多个步骤才能完成的SQL操作.

**하위 쿼리는 MySQL의 중요한 기능으로, SQL 문을 통해 보다 복잡한 쿼리를 구현하는 데 도움이 됩니다. 그러나 하위 쿼리의 실행 효율성은 높지 않습니다. **이유:

① 하위 쿼리를 실행할 때 MySQL은 내부 쿼리문의 쿼리 결과가 필요하며 建立一个临时表, 외부 쿼리문은 임시 테이블의 레코드를 쿼리합니다. 쿼리가 완료된 후 撤销这些临时表. 이렇게 하면 CPU 및 IO 리소스가 너무 많이 소비되고 느린 쿼리가 많이 생성됩니다.

② 서브 쿼리의 결과 세트는 메모리 임시 테이블이든 디스크 임시 테이블이든 임시 테이블에 저장되므로 不会存在索引쿼리 성능은 어느 정도 영향을 받습니다.

③ 더 큰 결과 집합을 반환하는 하위 쿼리의 경우 쿼리 성능에 미치는 영향이 더 커집니다.

MySQL에서는 하위 쿼리 대신 조인(JOIN) 쿼리를 사용할 수 있습니다 . 조인 쿼리에는 필요하지 않으며 建立临时表쿼리 速度比子查询要快에 인덱스를 사용하면 성능이 더 좋아집니다.

예시 1: 학생 테이블에서 모니터인 학생 정보 조회

  • 하위 쿼리 사용

    #创建班级表中班长的索引
    CREATE INDEX idx_monitor ON class ( monitor ) ;
    EXPLAIN SELECT *FROM student stu1
    WHERE stu1.stuno IN(
    SELECT monitor
    FROM class c
    WHERE monitor IS NOT NULL);
    
  • 권장 사항: 다중 테이블 쿼리 사용

    EXPLAIN SELECT stu1.* FROM student stu1 JOIN class c
    ON stu1.stuno = c.monitor
    WHERE c.monitor IS NOT NULL;
    

예시 2: 반장이 아닌 학생을 모두 수강·추천하지 않음

  • 하위 쿼리

    EXPLAIN SELECT SQL_NO_CACHE a.* FROM student a
    WHERE a.stuno NOT IN (
    SELECT monitor FROM class bWHERE monitor IS NOT NULL);
    
  • 다중 테이블 쿼리로 수정

    EXPLAIN SELECT SQL_NO_CACHE a.*
    FROM student a LEFT OUTER JOIN class b ON a. stuno =b.monitor
    WHERE b.monitor IS NULL;
    

결론: NOT IN 또는 NOT EXISTS를 사용하지 말고 대신 LEFT JOIN Xxx ON xx WHERE xx IS NULL을 사용하십시오.

5. 정렬 최적화

5.1 정렬 최적화

질문: WHERE 조건 필드에 인덱스를 추가하는데 왜 ORDER BY 필드에 인덱스를 추가해야 합니까?

답변:

MySQL에서는 두 가지 정렬 방법, 즉 정렬이 지원 FileSort됩니다 Index.

  • 인덱스 정렬에서는 인덱스를 사용하여 데이터의 질서를 보장할 수 있으므로 정렬할 필요가 없습니다 效率更高.
  • FileSort 정렬은 内存中일반적으로 CPU较多. 정렬할 결과가 클 경우 정렬을 위해 임시 파일 I/O가 디스크에 수행되므로 비효율적입니다.

최적화 제안:

  1. SQL에서는 WHERE 절과 ORDER BY 절에 존재한다는 목적으로 WHERE 절 避免全表扫描과 ORDER BY 절 에 인덱스를 사용할 수 있습니다 避免使用FileSort排序. 물론 어떤 경우에는 전체 테이블 스캔이나 FileSort 정렬이 반드시 인덱싱보다 느린 것은 아닙니다. 그러나 일반적으로 쿼리 효율성을 향상하려면 여전히 이를 피해야 합니다.
  2. ORDER BY 정렬을 완료하려면 Index를 사용해 보세요. WHERE와 ORDER BY 뒤에 동일한 컬럼이 오면 단일 인덱스 컬럼을 사용하고, 서로 다른 경우에는 결합 인덱스를 사용합니다.
  3. Index를 사용할 수 없는 경우 FileSort 메서드를 조정해야 합니다.

5.2 테스트

학생 테이블과 클래스 테이블에 생성된 인덱스를 삭제합니다.

#方式1:
DROP INDEX idx_monitor ON class;

DROP INDEX idx_cid ON student;
DROP INDEX idx_age ON student;DROP INDEX idx_name ON student ;
DROP INDEX idx_age_name_classid ON student ;DROP INDEX idx_age_classid_name ON student ;

#方式2:
call proc_drop_index( 'test' , 'student' );

다음 인덱스를 사용할 수 있나요, 아니면 제거할 수 있나요?using filesort

프로세스 1:

EXPLAIN SELECT SQL_NO_CACHE * FROM student ORDER BY age,classid;

EXPLAIN SELECT SQL_NO_CACHE * FROM student ORDER BY age,classid limit 10;

여기에 이미지 설명을 삽입하세요.

프로세스 2: 주문시 제한이 없으며 인덱스가 무효화됩니다.

#创建索引
CREATE INDEX idx_age_classid_name ON student (age,classid, NAME);
#不限制,索引失效
EXPLAIN SELECT SQL_NO_CACHE * FROM student ORDER BY age ,classid ;

여기에 이미지 설명을 삽입하세요.

여기의 옵티마이저는 여전히 테이블로 반환되어야 한다고 생각합니다. 인덱싱하지 않으면 시간이 더 걸립니다.

커버링 인덱스를 사용해 보세요
여기에 이미지 설명을 삽입하세요.

테이블을 반환할 필요가 없으며 최적화 프로그램은 인덱싱이 더 빠르다고 생각합니다. 그냥 인덱스를 사용하세요.

제한 조건 추가
여기에 이미지 설명을 삽입하세요.

테이블 반환 수를 줄이려면 제한을 늘리십시오. 최적화 프로그램은 인덱싱이 더 빠르다고 생각하고 인덱스를 사용하게 됩니다.

프로세스 3: Order by 시 순서가 잘못되어 인덱스가 유효하지 않습니다.

CREATE INDEX idx_age_classid_stuno ON student (age,classid,stuno) ;

#以下哪些索引失效?

# 不会走,最左前缀原则
EXPLAIN SELECT* FROM student ORDER BY classid LIMIT 10;

# 不会走,最左前缀原则
EXPLAIN SELECT* FROM student ORDER BY classid,NAME LIMIT 10;

# 走
EXPLAIN SELECT* FROM student ORDER BY age,classid, stuno LIMIT 10;
# 走
EXPLAIN SELECT *FROM student ORDER BY age,classid LIMIT 10;
# 走
EXPLAIN SELECT * FROM student ORDER BY age LIMIT 10;

프로세스 4: order by 동안 규칙이 일치하지 않고 인덱스가 유효하지 않습니다(잘못된 순서, 인덱싱 없음, 역방향, 인덱싱 없음).

# age desc 方向反 索引失效
EXPLAIN SELECT * FROM student ORDER BY age DESC, classid ASC LIMIT 10;

# 没有最左前缀 索引失效
EXPLAIN SELECT * FROM student ORDER BY classid DESC, NAME DESC LIMIT 10;

# age asc 没问题 classid desc 降序, 优化器认为,文件排序比较快索引失效
# 方向反了不走索引
EXPLAIN SELECT * FROM student ORDER BY age ASC, classid DESC LIMIT 10;

# Backward index scan 走索引了,倒着走索引
EXPLAIN SELECT * FROM student ORDER BY age DESC, classid DESC LIMIT 10; 

프로세스 5: 필터링 없음, 인덱싱 없음

EXPLAIN SELECT * FROM student WHERE age=45 ORDER BY classid;

EXPLAIN SELECT * FROM student WHERE age=45 ORDER BY classid , name;

여기에 이미지 설명을 삽입하세요.

EXPLAIN SELECT *FROM student WHERE classid=45 order by age;

EXPLAIN SELECT * FROM student WHERE classid=45 order by age limit 10;

여기에 이미지 설명을 삽입하세요.
여기서 첫 번째 정렬은 이해하기 쉬운 파일 정렬 사용입니다.

왜 제2조가 아닌가 Using filesort ?

여기서 유형 = 인덱스, 키 = idx_age_classid_name입니다. 이는 최적화 프로그램이 idx_age_classid_name 인덱스의 완전한 탐색을 기대한다는 것을 의미합니다. 인덱스 자체가 나이에 따라 오름차순으로 저장되기 때문입니다. . 따라서 순회 프로세스 중에 처음 10개의 classid=45가 발견되는 한. 이동을 중지할 수 있습니다. 데이터를 테이블에 반환합니다.

요약:

INDEX a_b_c( a, b,c)

order by 能使用索引最左前缀
- ORDER BY a
- ORDER BY a, b
- ORDER BY a , b, c
- ORDER BY a DESC, b DESC,c DESC


# 如果WHERE使用索引的最左前缀定义为常量,则order by 能使用索引
- WHERE a = const ORDER BY b, c
- WHERE a = const AND b = const ORDER BY c
- WHERE a = const ORDER BY b, c
- WHERE a = const AND b > const ORDER BY b , c

# 不能使用索引进行排序
- ORDER BY a ASC, b DESC, c DESC/*排序不一致*/
- WHERE g = const ORDER BY b,c/*丢失a索引*/
- WHERE a = const ORDER BY c/*丢失b索引*/
- WHERE a = const ORDER BY a, d /*d不是索引的一部分*/
- WHERE a in (...) ORDER BY b,c /*对于排序来说,多个相等条件也是范围查询*/

하나의 인덱스만 사용되며, where에 하나의 인덱스를, order by에 하나의 인덱스를 사용할 수 있는 방법은 없습니다.

그러나 조인트 인덱스는 생성될 수 있습니다.

5.3 사례 실습

ORDER BY 절에서는 Index 정렬을 사용하고 FileSort 정렬은 사용하지 마십시오.

케이스를 실행하기 전에 학생의 인덱스를 지우고 기본 키만 남겨 둡니다.

DROP INDEX idx_age ON student;
DROP INDEX idx_age_classid_stuno ON student;DROP INDEX idx_age_classid_name ON student;
#或者
call proc_drop_index( 'test' , ' student' ) ;

show index from student;

시나리오: 30세이고 학생 번호가 101000 미만인 학생을 사용자 이름별로 정렬하여 쿼리합니다.

EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE age = 30 AND stuno <101000 ORDER BY NAME;

여기에 이미지 설명을 삽입하세요.

mysql>  SELECT SQL_NO_CACHE * FROM student WHERE age = 30 AND stuno <101000 ORDER BY NAME;
+-----+--------+--------+------+---------+
| id  | stuno  | name   | age  | classId |
+-----+--------+--------+------+---------+
| 417 | 100417 | bBAYtX |   30 |     159 |

....

| 372 | 100372 | xwODCc |   30 |     764 |
+-----+--------+--------+------+---------+
18 rows in set, 1 warning (0.17 sec)

결론: 유형은 ALL이며 이는 최악의 경우입니다. Using filsort최악의 시나리오이기도 한 Extra 에도 등장합니다 . 최적화는 필수입니다.

최적화 아이디어:

옵션 1: 파일 정렬을 제거하기 위해 인덱스를 구축할 수 있습니다.

#创建新索引
CREATE INDEX idx_age_name ON student(age , NAME);

EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE age = 30 AND stuno <101000 ORDER BY NAME;

여기에 이미지 설명을 삽입하세요.
옵션 2: 조건 필터링 및 정렬에 상위 인덱스를 사용해보세요.

create index idx_age_stuno_name on student(age,stuno,name);
EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE age = 30 AND stuno <101000 ORDER BY NAME;

여기에 이미지 설명을 삽입하세요.
다음 솔루션이 사용되지만 Using filesort더 빠릅니다.

이유:

모든 정렬은 조건부 필터링 후에 수행됩니다. 따라서 조건에 따라 대부분의 데이터를 걸러낸다면 나머지 수백, 수천 개의 데이터를 정렬하는 것은 성능 소모가 크지 않으며, 인덱스가 정렬을 최적화하더라도 실제 성능 향상은 매우 제한적입니다. stuno<101000의 조건에 비해 인덱스를 사용하지 않을 경우 수만 개의 데이터를 스캔해야 하므로 성능 집약적이므로 이 필드에 인덱스를 배치하는 것이 가장 비용 효율적이고 최적의 선택입니다. .

결론:
1.두 개의 인덱스가 동시에 존재하며 MySQL은 자동으로 최적의 솔루션을 선택합니다. (이 예에서는 mysql이 idx_age_stuno_name을 선택합니다.) 하지만,随着数据量的变化,选择的索引也会随之变化的。

2. [범위 조건]과 [그룹화 또는 정렬 기준] 필드 중 선택 항목이 있는 경우 해당 조건 필드의 필터링 수량을 관찰하는 것이 우선이며, 필터링할 데이터가 충분하고 필터링할 데이터가 많지 않은
경우 정렬될 경우 범위 필드의 인덱싱에 우선순위가 부여됩니다. 그 반대.

생각: 여기서는 다음 지수를 사용합니다. 실현 가능합니까?

DROP INDEX idx_age_stuno_name ON student;

# 当然可以了,因为3个也只是用到了两个索引
CREATE INDEX idx_age_stuno ON student(age , stuno ) ;

5.4 파일 정렬 알고리즘: 양방향 정렬과 단방향 정렬

정렬된 필드가 인덱스 열에 없으면 양방향 정렬단방향 정렬이라는filesort 두 가지 알고리즘이 사용됩니다 .

양방향 정렬(느림)

  • MySQL 4.1之前是使用双路排序, 말 그대로 디스크를 두 번 스캔하고, 마지막으로 데이터를 가져오고, 행 포인터를 읽고 열별로 정렬하고, 정렬한 다음, 정렬된 목록을 스캔하고, 목록의 값에 따라 목록에서 출력된 해당 데이터를 다시 읽는 것을 의미합니다.
  • 디스크에서 정렬 필드를 가져와서 버퍼에서 정렬한 다음 디스크에서 다른 필드를 가져옵니다.

일괄 데이터를 얻으려면 디스크를 두 번 스캔해야 하는데, 우리 모두 알고 있듯이 IO는 시간이 많이 걸리기 때문에 mysql4.1 이후 두 번째로 향상된 알고리즘인 단방향 정렬이 등장했습니다.

단방향 정렬 (빠름)

디스크에서 쿼리를 읽고 所有列열별 순서에 따라 sort_buffer(정렬 캐시) 버퍼에서 정렬하지만 단방향 정렬 효율성이 더 빠르고 두 번째 데이터 읽기를 방지하므로 더 많은 공간을 사용하게 됩니다. 그리고 랜덤 IO가 순차 IO로 바뀌고 해당 행이 메모리에 저장됩니다.

결론 및 제기된 질문

  • 단일 경로는 뒤에서 나오므로 일반적으로 이중 경로보다 좋습니다.
  • 하지만 단일 채널을 사용하는 데에는 문제가 있습니다.
    • sort_buffer에서는 단일 채널이 다중 채널보다 낫습니다 多占用更多空间. 단일 채널이 모든 필드를 가져가기 때문에 꺼낸 데이터의 총 크기가 용량을 초과하여 매번 해당 sort_buffer용량의 데이터만 가져올 수 있기 때문입니다. sort_buffer정렬(tmp 파일 생성, 다중 채널 병합), 정렬 후 sort_buffer 용량을 가져온 다음 정렬... 결과적으로 다중 I/O가 발생합니다.
    • 단일 채널은 원래 하나의 I/O 작업을 절약하려고 했지만 反而导致了大量的I/O操作게인이 게인보다 더 컸습니다.

최적화 전략

1. sort_buffer_size를 늘려보세요

  • 어떤 알고리즘을 사용하든 이 매개변수를 늘리면 효율성이 향상되지만, 이 매개변수는 프로세스(연결)별로 1M~8M 사이에서 조정되므로 시스템 성능에 따라 개선되어야 합니다
    . MySQL5.7, InnoDB 스토리지 엔진의 기본값은 1048576바이트, 1MB입니다.

    mysql> SHOW VARIABLES LIKE '%sort_buffer_size%';
    +-------------------------+---------+
    | Variable_name           | Value   |
    +-------------------------+---------+
    | innodb_sort_buffer_size | 1048576 |
    | myisam_sort_buffer_size | 8388608 |
    | sort_buffer_size        | 262144  |
    +-------------------------+---------+
    3 rows in set (0.00 sec)
    

2. max_length_for_sort_data를 늘려보세요.

  • 이 매개변수를 늘리면 알고리즘이 개선될 확률이 높아집니다.

    mysql> SHow VARIABLES LIKE '%max_length_for_sort_data%';
    +--------------------------+-------+
    | Variable_name            | Value |
    +--------------------------+-------+
    | max_length_for_sort_data | 4096  |
    +--------------------------+-------+
    1 row in set (0.00 sec)
    
  • 그러나 너무 높게 설정하면 sort_buffer_size전체 데이터 용량을 초과할 확률이 높아지는데, 명백한 증상은 디스크 I/O 활동이 높고 프로세서 사용량이 낮다는 것입니다. 반환해야 하는 열의 총 길이가 더 큰 경우 max_length_for_sort_data사용하고 双路算法, 그렇지 않으면 단방향 알고리즘을 사용합니다. 1024~8192바이트 사이에서 조정 가능합니다.

**3.Select*는 주문 시 금기 사항입니다. 필요한 필드만 쿼리하는 것이 가장 좋습니다. **이유:

  • 쿼리의 필드 크기 합계가 이보다 작고 max_length_for_sort_data정렬 필드가 TEXT|BLOB 유형이 아닌 경우 향상된 알고리즘인 단방향 정렬이 사용되고, 그렇지 않은 경우 이전 알고리즘인 다중 방향 정렬이 사용됩니다.
  • 두 알고리즘의 데이터가 sort_buffer_size의 용량을 초과할 수 있으며 이를 초과하면 병합 정렬을 위한 tmp 파일이 생성되어 다중 I/O가 발생합니다. 그러나 단방향 정렬 알고리즘을 사용하면 위험이 더 커지므로 이를 사용해야 합니다. 개선될 것입니다 sort_buffer_size.

6.GROUP BY 최적화

  • Group by에서 인덱스를 사용하는 원리는 order by와 거의 동일하며, group by에서는 인덱스를 필터 조건으로 사용하지 않더라도 인덱스를 직접 사용할 수도 있다. .
  • 인덱스 구성을 위한 가장 좋은 왼쪽 접두사 규칙에 따라 먼저 정렬별로 그룹화한 다음 그룹화합니다.
  • 인덱스 컬럼을 사용할 수 없는 경우 매개변수 설정을 늘리 max_length_for_sort_data십시오 sort_buffer_size.
  • where의 효율성이 갖는 것보다 높기 때문에 조건을 where에 쓸 수 있으면 갖는 것에 쓰지 마세요.
  • order by의 사용을 줄여보세요.비즈니스와 소통할 때 정렬 없이 정렬을 피하거나 프로그램에 정렬을 넣을 수 있습니다.
  • Order by, group by, independent 등의 문은 더 많은 CPU를 소비하며 데이터베이스의 CPU 리소스는 매우 소중합니다.
  • order by, group by, distinct 등의 쿼리문이 포함되어 있으며 where 조건으로 필터링된 결과 집합은 1,000행 이내로 유지되어야 하며, 그렇지 않으면 SQL이 매우 느려집니다.

7. 페이징 쿼리 최적화

일반적으로 페이징 쿼리를 수행할 때 커버링 인덱스를 생성하면 성능을 더 향상시킬 수 있습니다. 일반적이고 매우 골치 아픈 문제는 2000000,10 제한입니다. 이때 MySQL은 처음 2000010개의 레코드를 정렬해야 하며 2000000 - 2000010개의 레코드만 반환합니다. 다른 레코드는 폐기되고 쿼리 정렬 비용이 매우 높습니다.

EXPLAIN SELECT * FROM student LIMIT 2088800,10;

최적화 아이디어 1:
인덱스에 대한 정렬 및 페이징 작업을 완료하고 마지막으로 기본 키를 기반으로 원래 테이블 쿼리에 필요한 다른 열 내용과 다시 연결합니다.

EXPLAIN SELECT * FROM student t, ( SELECT id FROM student ORDER BY id LIMIT 2000000,10) a WHERE t.id = a.id;

여기에 이미지 설명을 삽입하세요.
최적화 아이디어 2 (거의 사용 불가능)

이 솔루션은 자동 증가 기본 키가 있는 테이블에 적합하며 특정 위치에서 Limit 쿼리를 쿼리로 변환할 수 있습니다.

EXPLAIN SELECT * FROM student WHERE id > 2080880 LIMIT 10;

여기에 이미지 설명을 삽입하세요.

신뢰성이 없고, 제작 중에 ID가 삭제될 수도 있고, 쿼리 조건도 그렇게 단순할 수 없습니다.

8. 커버링 인덱스 우선순위 지정

8.1 커버링 인덱스란 무엇입니까?

방법 1의 이해 : 인덱스는 행을 효율적으로 찾는 방법이지만 일반 데이터베이스도 인덱스를 사용하여 열의 데이터를 찾을 수 있으므로
전체 행을 읽을 필요가 없습니다. 결국 인덱스 리프 노드는 자신이 인덱스한 데이터를 저장하므로 인덱스를 읽어 원하는 데이터를 얻을 수 있으면
행을 읽을 필요가 없습니다. 쿼리 결과를 만족하는 데이터가 포함된 인덱스를 커버링 인덱스라고 합니다.

**이해 방법 2:** 쿼리의 SELECT, JOIN 및 WHERE 절에 사용된 모든 열을 포함하는 비클러스터형 복합 인덱스 형태
(즉, 인덱싱할 필드가 쿼리 조건에 포함된 필드와 정확히 일치함) ) 필드).

쉽게 말하면 索引列+主键가 포함되어 있습니다 SELECT 到 FROM之间查询的列.

**예 1:**커버링 인덱스의 모습.索引列+主键

#斯降之前的索引
DROP INDEX idx_age_stuno ON student ;
CREATE INDEX idx_age_name ON student (age , NAME);

EXPLAIN SELECT * FROM student WHERE age <>20;

여기에 이미지 설명을 삽입하세요.

EXPLAIN SELECT id, age , NAME FROM student WHERE age <> 28;

여기에 이미지 설명을 삽입하세요.
선언된 인덱스는 위에서 모두 사용되었으나, 다음 상황에서는 그렇지 않습니다. 쿼리 열에 classid라는 열이 추가되어 인덱스가 사용되지 않음을 나타냅니다.

EXPLAIN SELECT id, age , NAME,classid FROM student WHERE age <> 28;

여기에 이미지 설명을 삽입하세요.
예 2:

EXPLAIN SELECT *FROM student WHERE NAME LIKE '%abc';

여기에 이미지 설명을 삽입하세요.

CREATE INDEX idx_age_name ON student (age , NAME);
EXPLAIN SELECT id, age ,NAME FROM student WHERE NAME LIKE '%abc ';

여기에 이미지 설명을 삽입하세요.

# 索引覆盖失效
EXPLAIN SELECT id, age ,NAME,classid FROM student WHERE NAME LIKE '%abc ';

쿼리에 분류 ID가 너무 많아 인덱스가 사용되지 않습니다.
여기에 이미지 설명을 삽입하세요.

앞에서 언급했듯이 불평등과 모호성은 인덱스 오류를 유발합니다. 그런데 왜 여기서 다시 사용되는 걸까요? 그 이유는 최적화 프로그램이 데이터가 이미 색인화되어 있음을 발견했기 때문입니다. 인덱스를 직접 순회하여 데이터를 반환할 수 있습니다. . 인덱스를 순회하는 것은 전체 테이블을 순회하는 것보다 확실히 데이터가 적습니다. 이렇게 하면 IO가 줄어들 수 있습니다.

모든 것은 비용을 고려한 것입니다.

8.2 인덱스 커버링의 장점과 단점

이점:
1. Innodb 테이블 인덱스의 2차 쿼리 방지(테이블 반환)

Innodb는 클러스터형 인덱스 순서로 저장되는데, Innodb의 경우 보조 인덱스는 리프 노드에 해당 행의 기본 키 정보를 저장하는데, 보조 인덱스를 사용하여 데이터를 쿼리할 경우 해당 키 값을 찾은 후, 실제로 필요한 데이터를 얻기 위해 기본 키를 통해 보조 쿼리를 수행합니다.

커버링 인덱스에서는 보조 인덱스의 키 값에서 필요한 데이터를 얻을 수 있어 避免了对主键的二次查询,减少了IO操作쿼리 효율성이 향상됩니다.

2. 무작위 IO를 순차 IO로 전환하여 쿼리 효율성을 높일 수 있습니다.

커버링 인덱스는 키 값 순서대로 저장되므로 IO 집약적인 범위 검색의 경우 디스크에서 각 행의 데이터를 무작위로 읽는 데 필요한 IO가 훨씬 적으므로 커버링 인덱스를 사용하면 액세스 중에 디스크를 随机读取的IO변경할 수도 있습니다. . 인덱스로 검색되었습니다 顺序IO.

3. 인덱스의 데이터 양이 더 작고 컴팩트합니다.

인덱스는 원본 데이터보다 작아야 합니다. . 이렇게 하면 IO를 줄일 수 있습니다.

커버링 인덱스는 트리 검색 수를 줄이고 쿼리 성능을 크게 향상시킬 수 있으므로 커버링 인덱스를 사용하는 것이 일반적인 성능 최적화 방법입니다.

단점:

索引字段的维护항상 가격이 있습니다. 따라서 포함 인덱스를 지원하기 위해 중복 인덱스를 구축할 때 고려해야 할 장단점이 있습니다. 이것이 비즈니스 DBA, 즉 비즈니스 데이터 설계자의 일입니다.

9. 문자열에 인덱스를 추가하는 방법

다음과 같이 정의된 교사 테이블이 있습니다.

create table teacher(
ID bigint unsigned primary key,
email varchar(64),
...
)engine=innodb;

강사는 자신의 이메일 주소를 사용하여 로그인해야 하므로 다음과 유사한 설명이 비즈니스 코드에 나타나야 합니다.

mysql> select col1, col2 from teacher where email='xxx';  

이메일 필드에 색인이 없으면 이 명령문만 수행할 수 있습니다 全表扫描.

9.1 접두사 색인

MySQL은 접두사 인덱스를 지원합니다. 기본적으로 접두사 길이를 지정하지 않고 인덱스를 생성하면 인덱스에 전체 문자열이 포함됩니다
.

mysql> alter table teacher add index index1(email);
#或
mysql> alter table teacher add index index2(email(6))

이 두 가지 다른 정의 사이의 데이터 구조와 저장의 차이점은 무엇입니까? 다음 그림은 이 두 인덱스의 개략도이며
여기에 이미지 설명을 삽입하세요.
,
여기에 이미지 설명을 삽입하세요.
index1을 사용하는 경우 (즉, 전체 이메일 문자열의 인덱스 구조) 실행 순서는 다음과 같습니다.

  1. index1 인덱스 트리에서 인덱스 값이 '[email protected]'인 레코드를 찾아 ID2 값을 획득합니다.
  2. 기본 키로 이동하여 기본 키 값이 ID2인 행을 찾아 이메일의 값이 올바른지 판단하고 이 레코드 행을 결과 세트에 추가합니다.
  3. index1 인덱스 트리에서 방금 찾은 위치에서 다음 레코드를 가져오고
    email='[email protected]' 조건이 더 이상 충족되지 않는 것을 확인하고 루프가 종료됩니다.

이 프로세스 동안 기본 키 인덱스에서 데이터를 한 번만 검색하면 되므로 시스템에서는 한 행만 스캔된 것으로 간주합니다.

index2(예: email(6) 인덱스 구조)를 사용하는 경우 실행 순서는 다음과 같습니다.

  1. index2 인덱스 트리에서 인덱스 값 'zhangs'를 만족하는 레코드를 찾으세요. 가장 먼저 찾은 레코드는 ID1입니다.
  2. 기본 키로 가서 기본 키 값이 ID1인 행을 찾으면 이메일의 값이 '[email protected]'이 아닌 것으로 판단되어 이 행의 레코드는 삭제됩니다.
  3. 방금 index2에서 찾은 위치에서 다음 레코드를 가져와 여전히 'zhangs'임을 확인합니다. ID2를 꺼내고 ID 인덱스의 전체 행을 가져와서 이번에는 값이 올바른지 판단합니다. 이 레코드 행을 에 추가합니다
    . 결과 세트;
  4. idxe2에서 얻은 값이 'zhangs'가 아닐 때까지 이전 단계를 반복하고 루프가 종료됩니다.

즉, ** 접두사 인덱스를 사용하고 길이를 정의하면 추가 쿼리 비용을 추가하지 않고도 공간을 절약할 수 있습니다. **
차별에 대해서는 앞서 말씀드렸는데, 차별이 높을수록 좋습니다. 구별이 높을수록 중복 키 값이 줄어들기 때문입니다.

9.2 접두어 지수가 포함 지수에 미치는 영향

결론적으로:

Prefix 인덱스를 사용하면 쿼리 성능을 최적화하기 위해 Covering Index가 필요하지 않으며, 이는 Prefix 인덱스 사용 여부를 선택할 때 고려해야 할 요소이기도 합니다.

10. 인덱스 푸시다운

10.1 사용 전, 후 비교

ICP(인덱스 조건 푸시다운)는 MySQL 5.6의 새로운 기능으로, 인덱스를 사용하여 스토리지 엔진 계층에서 데이터를 필터링하는 최적화 방법입니다.

  • ICP가 없으면 스토리지 엔진은 인덱스를 탐색하여 기본 테이블에서 행을 찾은 다음 이를 MySQL 서버로 반환합니다. MySQL 서버는 WHERE행을 유지하기 위한 후속 조건을 평가합니다.
  • ICP가 활성화된 후 WHERE인덱스의 열만 사용하여 일부 조건을 필터링할 수 있는 경우 MySQL 서버는 WHERE필터링을 위해 이러한 조건을 스토리지 엔진에 넣습니다. 그런 다음 스토리지 엔진은 인덱스 항목을 사용하여 데이터를 필터링하고 이 조건이 충족되는 경우에만 테이블에서 행을 읽습니다.
    • 이점: ICP는 스토리지 엔진이 기본 테이블에 액세스해야 하는 횟수와 MySQL 서버가 스토리지 엔진에 액세스해야 하는 횟수를 줄일 수 있습니다.
    • 그러나 ICP는 加速效果스토리지 엔진을 통과 ICP筛选하는 데이터의 비율 에 따라 달라집니다.

예:

key1에는 인덱스가 있습니다
여기에 이미지 설명을 삽입하세요.

여기서 '%a'와 같은 조건은
실제로 어떤 조건을 충족하는지 계산하기 위해 인덱스에 포함될 수 있습니다. . . . 조건에 맞는 항목을 필터링하고 목록으로 돌아갑니다. 이런 방식으로 테이블에 반환되는 데이터를 크게 줄일 수 있습니다. 또 다른 장점은 인덱스 푸시다운이 없으면 모든 데이터를 테이블로 반환하여 이를 찾아야 한다는 것입니다. 이러한 데이터는 다른 페이지에 있을 수 있으므로 IO가 발생할 수 있습니다.

다음 조건이 충족되지 않을 때까지 조건이 푸시됩니다.

10.2 ICP 켜기/끄기

  • 인덱스 조건부 푸시다운은 기본적으로 활성화되어 있습니다. 이는 시스템 변수를 설정하여 제어할 수 있습니다 optimizer_switch.index_condition_pushdown

    #打开索引下推
    SET optimizer_switch = 'index_condition_pushdown=off ' ;
    #关闭索引下推
    SET optimizer_switch = 'index_condition_pushdown=on ' ;
    
    
  • 인덱스 조건을 사용하여 푸시다운할 경우 EXPLAIN명령문 출력 결과에서 Extra 열의 내용이 로 표시됩니다 Using index condition.

10.3ICP 사용 사례

테이블 생성

CREATE TABLE `people` (
	`id` INT NOT NULL AUTO_INCREMENT,
	`zipcode` VARCHAR ( 20 ) COLLATE utf8_bin DEFAULT NULL,
	`firstname` varchar(20)COLLATE utf8_bin DEFAULT NULL,
	`lastname` varchar(20) COLLATE utf8_bin DEFAULT NULL,
	`address` varchar (50)COLLATE utf8_bin DEFAULT NULL,
	PRIMARY KEY ( `id`),
KEY `zip_last_first`( `zipcode` , `lastname`, `firstname`)
)ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb3 COLLATE=utf8_bin;

데이터 삽입

INSERT INTO `people` VALUES
( '1', '000001','三','张','北京市'),
 ( '2', '000002 ','四','李','南京市'),
 ( '3', '000003', '五','王','上海市'),
 ( '4 ', '000001','六','赵','天津市');

이 테이블에 대한 공동 인덱스 zip_last_first(우편번호, 성, 이름)를 정의합니다. 사람의 우편번호는 알고 있지만 성이 확실하지 않은 경우 다음 검색을 수행할 수 있습니다.

SELECT *FROM people
WHERE zipcode= '000001'
AND lastname LIKE '%张%'
AND address LIKE '%北京市%';

여기에 이미지 설명을 삽입하세요.

쿼리 계획을 실행하여 Extra 에 표시되는 SQL을 확인합니다 Using index condition. 이는 인덱스 푸시다운이 사용되었음을 나타냅니다. 또한 Usingwhere조건에 필터링이 필요한 인덱스가 아닌 열의 데이터가 포함되어 있음을 의미합니다. 즉, 조건 주소 LIKE '%Beijing City%'는 인덱스 열이 아니므로 서버 측에서 필터링해야 함을 의미합니다.

10.4 ICP 켜기 및 끄기 성능 비교

저장 프로시저를 생성하는 주요 목적은 000001 데이터를 많이 삽입하여 스토리지 엔진 계층에서 필터링하여 쿼리 시 IO를 줄이고 버퍼 풀(캐시 데이터 페이지, 없음)의 역할을 줄이는 것입니다. IO).

DELIMITER //
CREATE PROCEDURE insert_people( max_num INT )
BEGIN
DECLARE i INT DEFAULT 0;
	SET autocommit = 0;
	REPEAT
	SET i = i + 1;
	INSERT INTo people ( zipcode, firstname , lastname , address ) VALUES ( '000001','六', '赵','天津市');

	UNTIL i = max_num
	END REPEAT;
	COMMIT;
END //
DELIMITER ;


저장 프로시저 호출

call insert_people(1000000);

먼저 열어보세요 profiling.

#查看
mysql> show variables like 'profiling%';
+------------------------+-------+
| Variable_name          | Value |
+------------------------+-------+
| profiling              | OFF   |
| profiling_history_size | 15    |
+------------------------+-------+
set profiling=1 ;

SQL 문을 실행하면 인덱스 푸시다운이 기본적으로 활성화됩니다.

SELECT * FROM people WHERE zipcode= '000001' AND lastname LIKE '%张%';

인덱스 푸시다운을 사용하지 않고 SQL 문을 다시 실행합니다.

SELECT /*+ no_icp (people) */ * FROM people WHERE zipcode='000001' AND lastname LIKE '%张%';

현재 세션에서 생성된 모든 프로필 보기

show profiles\G ;

결과는 다음과 같습니다.

여기에 이미지 설명을 삽입하세요.
여기에 이미지 설명을 삽입하세요.
여러 테스트의 효율성을 비교하면 ICP 최적화를 사용한 쿼리 효율성이 더 좋습니다. 보다 확실한 효과를 얻으려면 더 많은 데이터를 저장하는 것이 좋습니다.

10.1 사용 전후의 스캔 과정

ICP 인덱스 스캐닝을 사용하지 않는 과정에서:

저장 계층: 인덱스 키 조건을 만족하는 인덱스 레코드에 해당하는 레코드 행 전체만 꺼내어 서버 계층으로 반환한다.

서버 계층: 후속 where 조건을 사용하여 마지막 행이 반환될 때까지 반환된 데이터를 필터링합니다.

여기에 이미지 설명을 삽입하세요.
여기에 이미지 설명을 삽입하세요.
ICP 스캐닝을 사용하는 과정:
저장 계층:
먼저 인덱스 키 조건을 만족하는 인덱스 기록 간격을 결정한 다음 인덱스 필터를 사용하여 인덱스를 필터링합니다. 인덱스 필터 조건을 만족하는 인덱스 레코드만
테이블로 반환되며, 레코드 전체 행이 서버 계층으로 반환됩니다. 인덱스 필터 조건을 만족하지 않는 인덱스 레코드는 폐기되며,
테이블이나 서버 레이어로 반환되지 않습니다.
서버 계층:
반환된 데이터의 최종 필터링을 위해 테이블 ​​필터 조건을 사용합니다.
여기에 이미지 설명을 삽입하세요.
여기에 이미지 설명을 삽입하세요.
사용 전과 후의 비용 차이. 사용 전
스토리지 계층에서는 인덱스 필터로 필터링해야 할 레코드 행이 많이 반환되었으며,
ICP 사용 후 인덱스 필터 조건을 충족하지 못한 레코드는 직접 제거되어 별도의 작업이 필요하지 않았습니다. 테이블로 반환되어 서버 계층으로 전달됩니다.
ICP의 가속 효과는 스토리지 엔진에서 ICP에 의해 필터링된 데이터의 비율에 따라 달라집니다.

10.5 ICP 사용 조건

  1. 테이블 액세스 유형이 range, ref, eq_ref 및 ref_or_null인 경우 ICP를 사용할 수 있습니다.

  2. ICP는 분할된 테이블 과 테이블을 포함한 테이블 InnoDB사용될 수 있습니다.MyISAMInnoDBMyISAM

  3. InnoDB테이블 의 경우 ICP보조 인덱스에만 사용됩니다. ICP의 목표는 전체 행 읽기 수를 줄여서 I/O 작업을 줄이는 것입니다.

  4. SQL이 포함 인덱스를 사용하는 경우 ICP는 지원되지 않습니다. 이 경우 ICP를 사용해도 I/O가 줄어들지 않기 때문입니다.

    인덱스 커버리지를 사용할 수 없습니다. 한 가지 이유는 인덱스 커버리지에 테이블 백업이 필요하지 않기 때문입니다. . ICP의 기능은 테이블로의 반환을 줄이는 것입니다. ICP는 테이블을 반환해야 합니다.

  5. 상관 하위 쿼리 조건은 ICP를 사용할 수 없습니다.

11. 일반 인덱스와 고유 인덱스

성능 관점에서 고유 인덱스를 선택해야 할까요, 아니면 일반 인덱스를 선택해야 할까요? 선정 기준은 무엇인가요?

기본 키 열을 ID로 하는 테이블이 있다고 가정하고, 테이블에 k 필드가 있고, k에 대한 인덱스가 있으며, k 필드의 값이 반복되지 않는다고 가정합니다.

이 테이블의 테이블 생성 문은 다음과 같습니다.

mysql> create table test(
id int primary key,
k int not null,
name varchar(16),
index (k)
)engine=InnoDB;

표에서 R1~R5의 (ID,k) 값은 각각 (100,1), (200,2), (300,3), (500,5), (600,6)이다.

11.1 쿼리 프로세스

쿼리를 실행하는 명령문이 k=5인 test에서 id를 선택한다고 가정합니다.


  • 일반 인덱스의 경우 조건을 충족하는 첫 번째 레코드(5,500)를 찾은 후 k=5 조건을 충족하지 않는 첫 번째 레코드를 만날 때까지 다음 레코드를 찾아야 합니다 .
  • 고유 인덱스의 경우 인덱스가 고유성을 정의하므로 조건을 충족하는 첫 번째 레코드가 발견되면 검색이 중지됩니다
    .

그렇다면 이러한 차이로 인해 발생하는 성능 격차는 무엇입니까? 대답은 입니다 微乎其微.

11.2 업데이트 프로세스

일반 인덱스와 고유 인덱스가 업데이트 문 성능에 미치는 영향을 설명하기 위해 변경 버퍼를 소개하겠습니다.

데이터 페이지를 업데이트해야 할 때 데이터 페이지가 메모리에 있으면 직접 업데이트되고, 데이터 페이지가 아직 메모리에 없으면 데이터 일관성에 영향을 주지 않고 디스크에서 읽을 필요가
없습니다 InooDB会将这些更新操作缓存在change buffer中.
이 데이터 페이지를 입력하세요. 다음 쿼리가 이 데이터 페이지에 액세스해야 하는 경우 데이터 페이지를 메모리로 읽은 다음
변경 버퍼에서 이 페이지와 관련된 작업을 수행합니다. 이러한 방식으로 데이터 로직의 정확성이 보장될 수 있습니다.

변경 버퍼의 작업을 원본 데이터 페이지에 적용하고 최신 결과를 얻는 프로세스를 병합이라고 합니다. 이 데이터 페이지에 액세스할 때
병합을 트리거하는 것 외에도 시스템에는 后台线程会定期병합이 있습니다. 이 과정 에서 数据库正常关闭(shutdown)병합
작업도 수행됩니다.

업데이트 작업을 변경 버퍼에 먼저 기록할 수 있으면 减少读磁盘해당 문의 실행 속도가 크게 향상됩니다. 또한
데이터를 메모리로 읽으려면 버퍼 풀을 점유해야 하므로 이 방법을 사용하면 避免占用内存메모리 활용도 향상될 수 있습니다.
唯一索引的更新就不能使用change buffer, 실제로는 일반 인덱스만 사용할 수 있습니다.

이 테이블에 새 레코드(4,400)를 삽입하려는 경우 InnoDB 프로세스는 무엇입니까?

11.3 변경 버퍼의 사용 시나리오

  1. 일반 인덱스와 고유 인덱스 중에서 선택하는 방법은 무엇입니까? 실제로 이 두 가지 유형의 인덱스 간에 쿼리 기능에는 차이가 없으며, 주요 고려 사항은
    업데이트 성능에 미치는 영향입니다. 따라서 일반 인덱스를 선택하는 것이 좋습니다.


  2. 실제 사용에서는 일반 인덱스와 변경 버퍼를 함께 사용하면 대용량 데이터가 포함된 테이블의 업데이트를 분명히 최적화 할 수 있음을 알 수 있습니다 .

  3. 모든 업데이트가 바로 이 레코드에 대한 쿼리로 이어지면 변경 버퍼를 닫아야 합니다. 다른 경우 에는
    버퍼 변경으로 업데이트 성능이 향상될 수 있습니다.

  4. 고유 인덱스는 변경 버퍼의 최적화 메커니즘을 사용할 수 없으므로 비즈니스가 허용 가능한 경우
    성능 관점에서 고유하지 않은 인덱스에 우선 순위를 두는 것이 좋습니다. 하지만 "사업이 보장되지 않을 수 있는" 경우 어떻게 해야 합니까?

    - 우선 업무의 정확성이 우선입니다. 우리의 전제는 성능 문제를 논의하기 전에 "비즈니스 코드가 중복 데이터를 쓰지 않도록 보장한다"는 것입니다
    . 비즈니스에서 이를 보장할 수 없거나 비즈니스에서 데이터베이스에 제약을 가해야 하는 경우 고유 인덱스를 생성하는 것 외에는 선택의 여지가 없습니다. 이 경우 이 섹션의 의미 는 많은 양의 데이터 삽입이 느리고 메모리 적중률이 낮은 상황에 직면했을 때 문제 해결 아이디어를 하나
    더 제공하는 것입니다 .

    그런 다음 일부 "아카이브 라이브러리" 시나리오에서는 고유 인덱스 사용을 고려할 수 있습니다. 예를 들어 온라인 데이터는 반년 동안만 보관하면 되며 그 이후에는 기록 데이터가 아카이브에 저장됩니다. 이때 보관된 데이터는 고유한 키 충돌이 없음을 확인했습니다. 보관 효율성을 높이려면 테이블의 고유 인덱스를 일반 인덱스로 변경하는 것을 고려할 수 있습니다.

12. 기타 쿼리 최적화 전략

12.1 EXISTS와 IN의 차이점

질문:

어떤 경우에 EXISTS를 사용해야 하고 어떤 경우에 IN을 사용해야 하는지 잘 모르겠습니다. 선택 기준은 테이블의 인덱스 사용 가능 여부에 따라 결정되나요?

답변:

인덱싱은 필수사항인데 실제로 테이블 크기에 따라 선택 여부가 결정됩니다. 선택기준을 이렇게 생각하시면 됩니다 小表驱动大表. 이런 방식으로 효율성이 가장 높습니다.

예를 들어:

SELECT *FROM A WHERE cc IN (SELECT cc FROM B)

SELECT *FROM A WHERE EXISTS (SELECT cc FROM B WHERE B.cc=A.cc)

A가 B보다 작으면 EXISTS를 사용합니다. EXISTS의 구현은 외부 루프와 동일하므로 구현 논리는 다음과 유사합니다.

for i in A
	for j in B
		if j.cc == i.cc then ...

구현 논리는 다음과 유사하므로 B가 A보다 작을 때 IN을 사용합니다.

for i in B
	for j in A
		if j.cc == i.cc then ...

더 작은 테이블이 이를 구동하는 데 사용되며, 테이블 A가 작으면 EXISTS가 사용되고, 테이블 B가 작으면 IN이 사용됩니다.

12.2 COUNT(*) 및 COUNT(특정 필드) 효율성

SELECT COUNT(*)질문: MySQL에서 데이터 테이블의 행 수를 계산하는 방법에는 , SELECT COUNT(1)및 의 세 가지가 있습니다 SELECT COUNT(具体字段). 이 세 가지 방법의 쿼리 효율성은 무엇입니까?

답:
전제: 특정 필드에서 비어 있지 않은 데이터 행의 수를 계산하려면 다른 문제입니다. 결국 실행 효율성을 비교하는 전제는 결과가 동일해야 한다는 것입니다.

1단계: COUNT(*) sum은 COUNT(1)모든 결과에 대해 수행되며 COUNT, sum과 sum 사이에는 본질적으로 차이가 없습니다(둘의 실행 시간은 약간 다를 수 있지만 실행 효율성은 동일하다고 간주할 수 있습니다) COUNT(*). COUNT(1)WHERE 절이 있는 경우 필터링 조건에 맞는 모든 데이터 행이 집계되고, WHERE 절이 없는 경우 데이터 테이블의 데이터 행 수가 집계됩니다.

2단계: MyISAM 스토리지 엔진인 경우 데이터 테이블의 행 수를 계산하는 o(1)복잡도는 1에 불과합니다. 이는 각 MyISAM 데이터 테이블에 row_count값을 저장하는 메타 정보가 있고 테이블에서 일관성이 보장되기 때문입니다. -레벨 잠금.

InnoDB 스토리지 엔진의 경우 InnoDB는 트랜잭션을 지원하고 행 수준 잠금 및 MVCC 메커니즘을 사용하기 때문에 MyISAM과 같은 row_count 변수를 유지할 수 없으므로 O(n) 복잡도로 루프 + 계산을 통해 완료해야 합니다 扫描全表. .

**링크(키포인트) 3:**InnoDB 엔진에서 COUNT(具体字段)데이터 행 수를 계산하는 데 사용하는 경우 보조 인덱스를 사용해 보세요. 기본 키에 사용되는 인덱스는 클러스터형 인덱스이기 때문에 클러스터형 인덱스는 많은 정보를 담고 있고 보조 인덱스(비클러스터형 인덱스)에 비해 크기도 당연히 큽니다. COUNT(*)의 경우 COUNT(1)특정 행을 찾을 필요가 없고 행 수만 계산하며 시스템에서는 自动공간을 적게 차지하는 보조 인덱스를 사용하여 통계를 수행합니다.

보조 인덱스가 여러 개인 경우 key_len더 작은 보조 인덱스가 스캔에 사용됩니다. 보조 인덱스가 없는 경우에는 기본 키 인덱스를 통계에 사용합니다.

12.3 SELECT(*) 소개

테이블 쿼리에서는 필드를 지정하는 것이 좋으며 쿼리의 필드 목록으로 *를 사용하지 말고 SELECT <필드 목록> 쿼리를 사용하는 것이 좋습니다. 이유:

① MySQL은 파싱 과정에서 查询数据字典"*"를 순서대로 모든 컬럼명으로 변환하는데, 이로 인해 리소스와 시간이 많이 소모됩니다
.

② 이용불가覆盖索引

12.4 최적화에 대한 LIMIT 1의 영향

테이블 전체를 스캔하는 SQL 문을 목표로 하며, 결과 세트가 하나만 있다고 확신할 수 있는 경우 LIMIT 1을 추가하면
결과가 발견될 때 스캔을 계속하지 않으므로 쿼리 속도가 빨라집니다.

데이터 테이블이 해당 필드에 대해 고유한 인덱스를 설정한 경우 해당 인덱스를 통해 쿼리할 수 있으며, 테이블 전체를 스캔하지 않는 경우에는 추가할 필요가
없습니다 LIMIT 1.

12.5 COMMIT를 더 많이 사용하세요

가능하다면 프로그램에서 COMMIT를 최대한 많이 사용하십시오. 그러면 프로그램의 성능이 향상되고
COMMIT에 의해 해제된 자원으로 인한 수요가 줄어들 것입니다.

COMMIT에서 릴리스한 리소스:

  • 데이터 복구에 사용된 롤백 세그먼트에 대한 정보
  • 프로그램 명령문에 의해 획득된 잠금
  • 다시 실행/실행 취소 로그 버퍼 공간
  • 위 3가지 리소스에 대한 내부 지출을 관리합니다.

13. 타오바오 데이터베이스의 기본 키는 어떻게 설계되어 있나요?

실용적인 질문에 대해 이야기해 보겠습니다. 타오바오 데이터베이스의 기본 키는 어떻게 설계되어 있나요?

일부 터무니없는 오답은 해마다 여전히 인터넷에 유포되고 있으며, 심지어 소위 MySQL 군사 규정이 되기도 했습니다. 그중 가장 명백한 실수 중 하나는 MySQL의 기본 키 설계에 관한 것입니다.

대부분의 사람들은 자신있게 대답합니다. INT 대신 8바이트 BIGINT를 기본 키로 사용하세요. !

从业务的角度이러한 대답은 기본 키를 고려 하지 않고 데이터베이스 수준에서만 나타납니다 . 기본 키는 자동 증가 ID입니까? 2022년 새해를 맞이하여 건축설계 측면에서 자동증분을 기본키로 활용하는 것이 가능해졌습니다 连及格都拿不到.

13.1 ID 자체 증가 문제

Auto-increment ID를 Primary Key로 사용하므로 간단하고 이해하기 쉬우며, 거의 모든 데이터베이스가 Auto-increment 방식을 지원하지만 구현 방법이 다릅니다. self-increasing ID는 단순함 외에도 다른 단점도 가지고 있는데, 전반적으로 다음과 같은 측면에서 문제가 있습니다.

  1. 별로 신뢰할 수 없음

    자동 증가 ID 역추적에 문제가 있는데, 이는 최신 버전의 MySQL 8.0까지 수정되지 않았습니다.

  2. 별로 안전하지 않음

    외부에 노출된 인터페이스를 통해 해당 정보를 매우 쉽게 추측할 수 있습니다. 예를 들어 /User/1/과 같은 인터페이스를 사용하면
    사용자 ID의 값과 전체 사용자 수를 추측하기가 매우 쉽고 인터페이스를 통해 데이터를 크롤링하는 것도 매우 쉽습니다.

  3. 성능 저하

    자동 증가 ID의 성능이 좋지 않아 데이터베이스 서버 측에서 생성해야 합니다.

  4. 많은 상호작용

    또한 비즈니스는 방금 삽입된 자동 증가 값을 알기 위해 last_insert_id()와 같은 추가 함수를 실행해야 하며, 이를 위해서는 네트워크 상호 작용이 한 번 더 필요합니다. 대규모 동시 시스템에서는 SQL 문이 하나 이상 추가되면 또 다른 성능 오버헤드가 발생합니다.

  5. 지역적 독특성

    가장 중요한 점은 자동 증가 ID가 모든 서버에서 전역적으로 고유하고 고유한 것이 아니라 로컬로 고유하며 현재 데이터베이스 인스턴스에서만 고유하다는 것입니다. 현재 분산 시스템의 경우 이는 악몽입니다.

13.2 기본 키로서의 비즈니스 필드

회원의 정보를 고유하게 식별하기 위해서는 에 会员信息表기본키를 설정해야 합니다. 그렇다면 이상적인 목표를 달성하기 위해 이 테이블의 기본 키를 설정하는 방법은 무엇입니까? 여기서는 비즈니스 분야를 기본 키로 간주합니다.

테이블 데이터는 다음과 같습니다.
여기에 이미지 설명을 삽입하세요.
이 테이블에서 어떤 필드가 더 적합합니까?

  • 카드번호 선택(cardno)

    회원카드 번호(cardno)는 비워둘 수 없고 고유하며 회원 기록을 식별하는 데 사용할 수 있기 때문에 더 적합해 보입니다.

mysql> CREATE TABLE demo.membermaster
-> (
-> cardno CHAR(8) PRIMARY KEY, -- 会员卡号为主键
-> membername TEXT,
-> memberphone TEXT,
-> memberpid TEXT,
-> memberaddress TEXT,
-> sex TEXT,
-> birthday DATETIME
-> );
Query OK, 0 rows affected (0.06 sec)

서로 다른 회원 카드 번호는 서로 다른 회원에 해당하며 "cardno" 필드는 특정 회원을 고유하게 식별합니다. 이 경우, 회원카드번호는 회원과 1:1로 대응되어 시스템이 정상적으로 운영될 수 있습니다.

하지만 실제 상황은 그렇습니다 会员卡号可能存在重复使用. 예를 들어, 장산(Zhang San)은 전직으로 인해 원래 주소에서 이사했고 더 이상 상인의 상점에 가서 구매를 하지 않았으므로(회원카드가 반환됨) 장산은 더 이상 상인의 상점 회원이 아니었습니다. 하지만 판매자는 회원카드를 비워두기 싫어 카드번호 '10000001'이 적힌 회원카드를 왕우에게 보냈다.

시스템 설계 측면에서 이번 변경은 회원 정보 테이블의 카드 번호가 '10000001'인 회원 정보만 수정하는 것이며, 데이터 일관성에는 영향을 미치지 않습니다. 즉, 회원카드번호 "10000001"로 회원정보를 수정하는 경우, 시스템의 각 모듈은 수정된 회원정보를 획득하게 되며, "수정 전 회원정보를 획득하는 모듈도 있고, 수정 전의 모듈을 획득하는 모듈도 있습니다." 수정된 회원정보". 회원정보가 추후에 수정되어 시스템 내 데이터에 불일치가 발생합니다." 그러므로 信息系统层面의 관점에서는 문제가 없다.

그러나 系统的业务层面사용의 관점에서 볼 때 비즈니스에 영향을 미칠 큰 문제가 있습니다.

예를 들어, 모든 판매 흐름 세부 정보를 기록하는 판매 흐름 테이블(trans)이 있습니다. 2020년 12월 1일, 장삼은 가게에서 책을 구입하고 89위안을 썼습니다. 그러면 시스템에는 아래와 같이 Zhang San의 도서 구매 거래 기록이 있습니다.
여기에 이미지 설명을 삽입하세요.
다음으로 2020년 12월 1일의 회원 판매 기록을 쿼리합니다.

mysql> SELECT b.membername,c.goodsname,a.quantity,a.salesvalue,a.transdate
-> FROM demo.trans AS a
-> JOIN demo.membermaster AS b
-> JOIN demo.goodsmaster AS c
-> ON (a.cardno = b.cardno AND a.itemnumber=c.itemnumber);
+------------+-----------+----------+------------+---------------------+
| membername | goodsname | quantity | salesvalue | transdate |
+------------+-----------+----------+------------+---------------------+
| 张三 || 1.000 | 89.00 | 2020-12-01 00:00:00 |
+------------+-----------+----------+------------+---------------------+
1 row in set (0.00 sec)

왕우님 회원카드 '10000001'이 다시 발급되면 회원정보표가 변경됩니다. 결과를 쿼리할 때:

mysql> SELECT b.membername,c.goodsname,a.quantity,a.salesvalue,a.transdate
-> FROM demo.trans AS a
-> JOIN demo.membermaster AS b
-> JOIN demo.goodsmaster AS c
-> ON (a.cardno = b.cardno AND a.itemnumber=c.itemnumber);
+------------+-----------+----------+------------+---------------------+
| membername | goodsname | quantity | salesvalue | transdate |
+------------+-----------+----------+------------+---------------------+
| 王五 || 1.000 | 89.00 | 2020-12-01 00:00:00 |
+------------+-----------+----------+------------+---------------------+
1 row in set (0.01 sec)

이번에 얻은 결과는 다음과 같습니다. Wang Wu는 2020년 12월 1일에 89위안을 지출하여 책을 구입했습니다. 분명히 틀렸어! 결론: 절대로 회원카드 번호를 기본 키로 사용하지 마십시오.

  • 회원 전화번호 또는 ID번호를 선택하세요.

회원의 전화번호를 기본키로 사용할 수 있나요? 안 돼요. 실제 업무에서는 휴대전화번호도 존재하며 被运营商收回타인에게 재발행되기도 한다.

ID번호는 어떻게 되나요? 가능할 것 같습니다. ID 카드는 결코 중복되지 않기 때문에 ID 번호와 사람 사이에는 일대일 대응이 있습니다. 하지만 문제는 ID 번호가 고객의 것이므로 个人隐私고객이 기꺼이 제공하지 않을 수도 있다는 것입니다. 회원에게 ID번호 등록을 의무화하면 많은 고객이 쫓겨날 것입니다. 실제로 고객의 전화번호에도 이런 문제가 있기 때문에 회원정보 테이블을 디자인할 때 ID번호와 전화번호를 모두 비워두는 것을 허용하고 있습니다.

따라서 비즈니스 관련 필드를 기본 키로 사용하지 않는 것이 좋습니다. 결국, 프로젝트 설계 기술자로서 우리 중 누구도
프로젝트의 비즈니스 요구로 인해 프로젝트의 전체 수명 주기 동안 어떤 비즈니스 분야가 반복되거나 재사용될지 예측할 수 없습니다.

경험:

처음 MySQL을 사용하기 시작할 때 많은 사람들이 흔히 범하는 실수는 비즈니스 요구 사항을 이해하고 있다고 가정하고 비즈니스 필드를 기본 키로 사용하는 것입니다. 그러나 실제 상황은 예상치 못한 경우가 많으며 기본 키 설정을 변경하는 데 드는 비용은 매우 높습니다.

13.3 타오바오의 기본 키 디자인

타오바오 전자상거래 사업에서 주문 서비스는 핵심 사업이다. 타오바오 订单表的主键는 어떻게 디자인됐나요? 자체증가 아이디인가요?

타오바오를 열고 주문 정보를 살펴보세요.

여기에 이미지 설명을 삽입하세요.
위 사진을 보시면 아시겠지만 주문번호는 자동증가되는 아이디가 아닙니다! 위의 4개 주문 번호를 자세히 살펴보겠습니다.

1550672064762308113
1481195847180308113
1431156171142308113
1431146631521308113

주문번호는 19자리이고, 주문 마지막 5자리는 모두 동일 08113입니다. 그리고 주문번호의 처음 14자리는 단조롭게 증가합니다
.
대담한 추측은 Taobao의 주문 ID 디자인이 다음과 같아야 한다는 것입니다.

订单ID = 时间 + 去重字段 + 用户ID后6位尾号

이러한 디자인은 전역적으로 고유할 수 있으며 분산 시스템 쿼리에 매우 친숙합니다.

13.4 권장되는 기본 키 디자인

非核心业务: 해당 테이블의 기본키는 알람, 로그, 모니터링, 기타 정보 등 ID를 자동으로 증가시킵니다.

核心业务: 기본 키 디자인은 최소한 전역적으로 고유하고 단조롭게 증가해야 합니다. 전역 고유성은 시스템 간에 고유함을 보장합니다. 단조롭게 증가해도 삽입 중에 데이터베이스 성능에 영향을 미치지 않을 것으로 예상됩니다.

여기서는 가장 간단한 기본 키 디자인인 UUID를 권장합니다.

UUID의 특징:

전역적으로 고유하고 36바이트를 차지하며 데이터가 잘못되어 삽입 성능이 좋지 않습니다.

UUID에 대해 알아보세요:

  • UUID가 전 세계적으로 고유한 이유는 무엇입니까?
  • UUID가 36바이트를 차지하는 이유는 무엇입니까?
  • UUID가 정렬되지 않은 이유는 무엇입니까?

MySQL 데이터베이스의 UUID 구성은 다음과 같습니다.

UUID = 时间+UUID版本(16字节)- 时钟序列(4字节) - MAC地址(12字节)

UUID 값 e0ea12d4-6473-11eb-943c-00155dbaa39d를 예로 들어보겠습니다.

여기에 이미지 설명을 삽입하세요.
UUID가 전 세계적으로 고유한 이유는 무엇입니까?

UUID의 시간 부분은 60비트를 차지하고 TIMESTAMP와 유사한 타임스탬프를 저장하지만 1582-10-15 00:00:00.00부터 현재까지 100ns의 개수를 나타냅니다. UUID 저장의 시간 정확도가 TIMESTAMPE보다 높으며, 시간 차원에서 중복 확률이 1/100ns로 감소함을 알 수 있습니다.

시계 순서는 시계가 다시 설정되어 발생하는 시간 중복 가능성을 방지하기 위한 것입니다. MAC 주소는 전역적으로 고유한 주소로 사용됩니다.

UUID가 36바이트를 차지하는 이유는 무엇입니까?

UUID는 문자열로 저장되며 쓸모없는 "-" 문자열로 설계되어 있으므로 총 36바이트가 필요합니다.

UUID가 무작위이고 순서가 없는 이유는 무엇입니까?

왜냐하면 UUID의 디자인에서는 낮은 비트의 시간이 먼저 배치되고, 이 부분의 데이터는 끊임없이 변화하고 무질서해지기 때문입니다.

개조 UUID

시간의 높은 비트와 낮은 비트가 바뀌면 시간은 단조 증가하고, 단조 증가하게 됩니다. MySQL 8.0은 낮은 시간 비트와 높은 시간 비트의 저장 방법을 변경할 수 있으므로 UUID는 순서화된 UUID입니다.

MySQL 8.0 역시 UUID의 공간 점유 문제를 해결하고, UUID 문자열에서 의미 없는 "-" 문자열을 제거하고, 문자열을 바이너리 형태로 저장함으로써 저장 공간을 16바이트로 줄인다.

위의 기능은 MySQL8.0에서 제공하는 uuid_to_bin 함수를 통해 구현할 수 있습니다. 마찬가지로 MySQL은 변환을 위해 bin_to_uuid 함수도 제공합니다.

SET @uuid = UUID();
SELECT @uuid,uuid_to_bin(@uuid),uuid_to_bin(@uuid,TRUE);
# uuid_to_bin(@uuid) 转成16进制存储
# uuid_to_bin(@uuid,TRUE); 修改成先高位 中位 地位,就可以保证uuid地政了

여기에 이미지 설명을 삽입하세요.
**uuid_to_bin(@uuid,true) 함수를 통해 UUID를 정렬된 UUID로 변환합니다. **전역적으로 고유하고 단조롭게 증가하는 것이 우리가 원하는 기본 키가 아닌가요!

4. UUID 성능 테스트 의뢰

16바이트 순서의 UUID와 이전 8바이트 자동 증가 ID 간의 성능과 저장 공간은 어떻게 비교됩니까?

테스트를 해보면 1억 개의 데이터를 삽입해 보겠습니다. 각 데이터는 500바이트를 차지하고 3개의 보조 인덱스를 포함합니다. 최종 결과는 다음과 같습니다. 위 그림을 보면 1억 개의 데이터를 순서대로 삽입한다는 것을 알 수 있습니다
여기에 이미지 설명을 삽입하세요.
. 가장 최적의 UUID로 빠르고, 실제 비즈니스 사용 시 비즈니스 측에서 주문된 UUID를 생성할 수 있습니다
. 또한 SQL 상호 작용 횟수를 더욱 줄일 수도 있습니다.

또한 주문한 UUID는 자동 증가 ID보다 8바이트가 더 많지만 실제로는 저장 공간을 3G만큼만 늘리는 것이 허용됩니다.

오늘날의 인터넷 환경에서는 자체 증가 ID를 기본 키로 사용하는 데이터베이스 설계는 매우 권장되지 않습니다. 주문된 UUID와 유사한 전역적으로 고유한 구현이 더 권장됩니다.

또한 실제 비즈니스 시스템에서 기본 키는 사용자의 마지막 전화번호, 컴퓨터실 정보 등과 같은 비즈니스 및 시스템 속성을 추가할 수도 있습니다. 이러한 기본 키 설계는 설계자의 수준을 더욱 테스트합니다.

MySQL8.0이 아니면 어떻게 해야 하나요?

필드를 기본 키로 수동으로 할당하세요!

예를 들어 각 머신에서 생성된 데이터를 병합해야 할 경우 기본 키가 중복되는 문제가 발생할 수 있으므로 각 지점의 멤버쉽 테이블의 기본 키를 설계합니다.

본사 MySQL 데이터베이스에 관리정보 테이블이 있을 수 있으며, 이 테이블에 현재 회원번호의 최대값을 기록하는 필드를 추가합니다.

매장은 회원을 추가할 때 먼저 본사 MySQL 데이터베이스에서 최대값을 얻어 이를 기준으로 1을 더한 후 이 값을 신규 회원의 'id'로 사용함과 동시에 현재 회원을 업데이트한다. 본사 MySQL 데이터베이스 관리 정보 테이블의 최대값입니다.

이렇게 각 매장마다 회원 추가 시 동일한 본사 MySQL 데이터베이스에 있는 데이터 테이블 필드를 대상으로 운영함으로써, 각 매장에서 회원 추가 시 회원번호가 충돌하는 문제를 해결합니다.

추천

출처blog.csdn.net/zhufei463738313/article/details/130638643