SQL 최적화 21 콤보 + 마인드 맵

1. 쿼리 SQL에 select *를 사용하지 말고 특정 필드를 사용하십시오.

1. 카운터 예

SELECT * FROM user

2, 정례

SELECT id,username,tel FROM user

3. 이유

  1. 리소스를 절약하고 네트워크 오버헤드를 줄입니다.
  2. 커버링 인덱스를 사용하여 반환 테이블을 줄이고 쿼리 효율성을 높일 수 있습니다.

注意:为节省时间,下面的样例字段都用*代替了。

2. 조건을 연결하기 위해 where 절에서 또는 사용을 피하십시오.

1. 카운터 예

SELECT * FROM user WHERE id=1 OR salary=5000

2, 정례

(1) 유니온 올 사용

SELECT * FROM user WHERE id=1 
UNION ALL
SELECT * FROM user WHERE salary=5000

(2) 두 개의 SQL 쓰기 분리

SELECT * FROM user WHERE id=1

SELECT * FROM user WHERE salary=5000

3. 이유

  1. 사용 or하면 인덱스가 무효화되어 전체 테이블 스캔이 발생할 수 있습니다.
  2. 인덱스가 없는 or경우 인덱스로 salary이동한 것으로 가정 id하지만 salary쿼리 조건에 도달하면 전체 테이블 스캔을 수행해야 합니다.
  3. 즉, 전체 프로세스에는 전체 테이블 스캔 + 인덱스 스캔 + 병합의 세 단계가 필요합니다. 처음에 전체 테이블 스캔을 수행하는 경우 직접 스캔하여 수행할 수 있습니다.
  4. 옵티마이저가 있지만 mysql효율성 및 비용 고려 사항으로 인해 or특정 조건에서는 인덱스가 여전히 실패할 수 있습니다.

3. 문자열 유형 대신 숫자 값을 사용해보십시오.

1, 정례

  1. 기본 키(id): primary key숫자 유형을 먼저 사용 int하고,tinyint
  2. 성별(성별): 여성은 0, 남성은 1이며 데이터베이스에 부울 유형이 없으므로 mysql사용하는 것이 좋습니다.tinyint

2. 이유

  1. 엔진은 쿼리 및 연결을 처리할 때 문자열의 각 문자를 하나씩 비교하기 때문에;
  2. 디지털 유형의 경우 한 번만 비교하면 됩니다.
  3. 문자는 쿼리 및 조인 성능을 저하시키고 스토리지 오버헤드를 증가시킵니다.

넷째, char 대신 varchar를 사용하십시오.

1. 카운터 예

`address` char(100) DEFAULT NULL COMMENT '地址'

2, 정례

`address` varchar(100) DEFAULT NULL COMMENT '地址'

3. 이유

  1. varchar가변 길이 필드는 데이터 내용의 실제 길이에 따라 저장되며 저장 공간이 작아 저장 공간을 절약할 수 있습니다.
  2. char선언된 크기에 따라 보관하고, 부족한 경우 공백을 채우십시오.
  3. 둘째, 쿼리의 경우 상대적으로 작은 필드에서 검색하는 것이 더 효율적입니다.

5. 기술적 확장, char과 varchar2의 차이점은?

1. char길이는 고정되어 있지만 varchar2길이는 변경될 수 있습니다.

예를 들어, 문자열 “101”을 저장 char(10)한다는 것은 저장하는 문자가 10바이트(널 문자 7개 포함)를 차지한다는 것을 의미합니다 varchar2(10). 최대값, 10자 미만으로 저장할 경우 실제 길이로 저장합니다.

2. char효율은 에 비해 약간 높습니다 varchar2.

3. 언제 char, 언제 사용 varchar2합니까?

char그리고 varchar2한 쌍의 모순된 통일성, 둘은 보완적이며 효율성 비율은 공간 절약 varchar2보다 약간 나쁠 것입니다. 효율성을 얻으려면 약간의 공간을 희생해야 합니다. 이것은 우리가 데이터베이스 디자인에서 흔히 말하는 것입니다." 공간에 대한 효율성".charchar

varchar2char공간은 절약 되지만 varchar2컬럼이 자주 수정되고 수정된 데이터의 길이가 매번 다르면 "행 마이그레이션" 현상이 발생하여 중복 I/O가 발생하므로 데이터베이스 설계에서 피해야 합니다. . , 이 경우 char대신 사용하는 varchar2것이 좋습니다 . char빈칸도 자동으로 채워주기 때문에 필드로 가면 자동으로 채워지는데 insert, char빈칸 select은 지워지지 않으니 char쿼리를 입력할 때 꼭 기억해두셔야 trim하는 것이 이 글을 쓰게 된 이유입니다. .

개발자 가 필드 rpad()와 비교할 수 있는 유형으로 바인드 변수를 변환하는 트릭을 사용하는 경우(물론, 바인드 변수를 채우는 것이 데이터베이스 열을 char자르는 것보다 낫습니다 . 열에 함수를 적용 하면 열에서 기존 인덱스를 사용할 수 없음), 시간 경과에 따른 열 길이 변경을 고려해야 할 수 있습니다. 필드 크기가 변경되면 필드 너비를 수정해야 하므로 앱이 영향을 받습니다.trimtrim

위와 같은 이유로 고정 너비 저장 공간으로 인해 테이블 ​​및 관련 인덱스가 평소보다 훨씬 커지고 바인드 변수 문제가 발생할 수 있으므로 char 유형은 어떤 경우에도 피해야 합니다.

6. null 대신 기본값을 사용하십시오.

1. 카운터 예

SELECT * FROM user WHERE age IS NOT NULL

2, 정례

SELECT * FROM user WHERE age>0

3. 이유

  1. 인덱스가 사용되지 않거나is null 사용되지 않는다는 의미는 아니며 버전 및 쿼리 비용과 관련이 있습니다.is not nullmysql
  2. mysql옵티마이저가 인덱스를 사용하는 비용이 인덱스를 사용하지 않는 비용보다 높다는 것을 발견하면 인덱스를 포기할 것입니다. 이러한 조건 !=,<>,is null,is not null은 종종 인덱스 를 무효화하는 것으로 간주됩니다.
  3. 사실, 일반적으로 쿼리 비용이 높고 옵티마이저가 자동으로 인덱스를 포기하기 때문입니다.
  4. 값을 기본값으로 바꾸면 null인덱스로 갈 수 있는 경우가 많으며 동시에 의미도 비교적 명확하다.

7. where 절에서 != 또는 <> 연산자를 사용하지 마십시오.

1. 카운터 예

SELECT * FROM user WHERE salary!=5000

SELECT * FROM user WHERE salary<>5000

2. 이유

  1. 인덱스를 무효화할 가능성이 있는 !=사용<>
  2. where절 에서 !=OR <>연산자 를 사용 하지 않도록 해야 합니다 . 그렇지 않으면 엔진이 인덱스 사용을 포기하고 전체 테이블 스캔을 수행합니다.
  3. 비즈니스 우선 순위를 달성하기 위해 실제로 방법이 없습니다. 사용할 수 밖에 없습니다. 사용이 불가능한 것은 아닙니다.

여덟, 내부 조인, 왼쪽 조인, 오른쪽 조인, 내부 조인 사용 우선 순위

세 가지 조인의 결과가 같으면 내부 조인을 선호하고, 왼쪽 조인을 사용할 경우 왼쪽 테이블은 가능한 한 작아야 합니다.

  • 내부 조인 내부 조인은 두 테이블에서 정확히 일치하는 결과 집합만 유지합니다.
  • 왼쪽 조인은 오른쪽 테이블에 일치하는 레코드가 없더라도 왼쪽 테이블의 모든 행을 반환합니다.
  • 오른쪽 조인은 왼쪽 테이블에 일치하는 레코드가 없더라도 오른쪽 테이블의 모든 행을 반환합니다.

왜요?

  • 내부 조인이 동등 조인인 경우 반환되는 행 수가 상대적으로 적으므로 성능이 상대적으로 더 좋습니다.
  • 왼쪽 조인을 사용하면 왼쪽 테이블의 데이터 결과가 가능한 한 작고 조건이 최대한 왼쪽에 배치되므로 반환되는 행 수가 상대적으로 적을 수 있습니다.
  • 이것은 mysql 최적화 원칙입니다. 즉, 작은 테이블은 큰 테이블을 구동하고 작은 데이터 세트는 큰 데이터 세트를 구동하므로 성능이 향상됩니다.

아홉, 문별 그룹화의 효율성 향상

1. 카운터 예

먼저 그룹화한 다음 필터링

select job, avg(salary) from employee 
group by job
having job ='develop' or job = 'test';

2, 정례

먼저 필터링하고 나중에 그룹화

select job,avg(salary) from employee 
where job ='develop' or job = 'test' 
group by job;

3. 이유

명령문을 실행하기 전에 불필요한 레코드를 필터링할 수 있습니다.

10. 테이블을 비울 때 먼저 truncate를 사용하십시오.

truncate table기능적으로는 where절이 delete. 둘 다 테이블의 모든 행을 삭제합니다. truncate table그러나 delete보다 빠르고 적은 시스템 및 트랜잭션 로그 리소스를 사용합니다.

delete이 명령문은 한 번에 하나의 행을 삭제하고 삭제된 각 행에 대해 트랜잭션 로그에 항목을 기록합니다. truncate table테이블 데이터를 저장하는 데 사용된 데이터 페이지를 해제하여 데이터를 삭제하며, 해제된 페이지는 트랜잭션 로그에만 기록됩니다.

truncate table테이블의 모든 행을 삭제하지만 테이블 구조와 해당 열, 제약 조건, 인덱스 등은 변경되지 않은 상태로 둡니다. 새 행 식별에 사용되는 카운트 값은 이 열의 시드로 재설정됩니다. ID 개수 값을 유지하려면 대신 DELETE를 사용하십시오. 테이블 정의 및 해당 데이터를 삭제하려면 drop table문 .

foreign key제약 조건 에서 참조하는 테이블 truncate table의 경우 where절 DELETE 문을 사용할 수 없습니다. truncate table기록 되지 않았기 때문에 트리거를 활성화할 수 없습니다.

truncate table인덱싱된 뷰에 참여하는 테이블에는 사용할 수 없습니다.

11. delete 또는 update 문을 실행하고 제한을 추가하거나 일괄 삭제합니다.

1. 잘못된 SQL 작성 비용 감소

테이블 데이터를 지우는 것은 사소한 일이 아닙니다. 한 손 떨림이 사라지고 데이터베이스를 삭제하고 도망 가십니까? 제한을 추가하면 실수로 삭제한 경우 데이터의 일부만 잃게 되며, 이는 binlog 로그를 통해 빠르게 복구할 수 있습니다.

2. SQL 효율성이 더 높을 가능성이 높습니다.

SQL limit 1에 추가된 첫 번째 항목이 대상 return에 도달하면 그렇지 않은 경우 limit테이블을 계속 스캔합니다.

3. 긴 거래를 피하라

delete실행 중에 age인덱스가 추가되면 MySQL은 모든 관련 행에 쓰기 잠금 및 간격 잠금을 추가하고 모든 실행 관련 행을 잠그게 되며, 삭제 횟수가 많을 경우 관련 서비스의 사용 불가에 직접적인 영향을 미치게 된다. .

4. 데이터 양이 많으면 CPU를 채우기 쉽습니다.

많은 양의 데이터를 삭제하는 경우 레코드 수를 제한하는 제한을 추가하지 마십시오. 채우기 쉽기 때문에 cpu삭제 속도가 느려지고 느려집니다.

5. 잠금 테이블

한 번에 너무 많은 데이터를 삭제하면 잠금 테이블 및 잠금 대기 시간 초과 오류가 발생할 수 있으므로 일괄 작업을 권장합니다.

12. UNION 연산자

UNION중복 레코드는 테이블 연결 후 필터링되므로 테이블 연결 후 결과 집합이 정렬되고 중복 레코드가 삭제되고 결과가 반환됩니다.
대부분의 실제 응용 프로그램에서는 중복 레코드가 생성되지 않으며 가장 일반적인 것은 프로세스 테이블과 이력 테이블 UNION입니다. 처럼:

select username,tel from user
union
select departmentname from department

이 SQL은 런타임 시 두 테이블의 결과를 먼저 가져온 다음 정렬 공간을 사용하여 중복 레코드를 삭제하고 마지막으로 결과 집합을 반환합니다. 테이블의 데이터 양이 많을 경우 디스크 정렬이 발생할 수 있습니다.
권장 솔루션: 연산자 대체 UNION ALL사용UNIONUNION ALL

13, 일괄 삽입 성능 개선

1. 복수 제출

INSERT INTO user (id,username) VALUES(1,'哪吒编程');

INSERT INTO user (id,username) VALUES(2,'妲己');

2. 일괄 제출

INSERT INTO user (id,username) VALUES(1,'哪吒编程'),(2,'妲己');

3. 이유

기본적으로 새로 추가된 SQL은 트랜잭션 제어를 통해 각 트랜잭션이 열리고 커밋되는 반면 일괄 처리는 한 번에 열리고 커밋되는 트랜잭션으로 효율성이 크게 향상되어 일정 규모에 도달합니다.

14. 너무 많은 테이블 연결과 너무 많은 인덱스가 있어서는 안 됩니다. 일반적으로 5 이내

1. 테이블 연결이 너무 많지 않아야 하며 일반적으로 5개 미만이어야 합니다.

  1. 연결된 테이블 수가 많을수록 컴파일 시간과 오버헤드가 커집니다.
  2. 매번 연관 메모리에 임시 테이블이 생성됩니다.
  3. 연결 테이블은 더 읽기 쉬운 더 작은 실행으로 분할되어야 합니다.
  4. 데이터를 가져오기 위해 많은 테이블을 조인해야 하는 경우 잘못된 설계를 의미합니다.
  5. Ali의 사양에서는 여러 테이블이 있는 세 개 이하의 테이블을 쿼리하는 것이 좋습니다.

2. 인덱스는 너무 많지 않아야 하며 일반적으로 5보다 작아야 합니다.

  1. 인덱스가 많을수록 좋습니다. 쿼리의 효율성은 향상되지만 삽입 및 업데이트의 효율성은 떨어집니다.
  2. 인덱스는 데이터를 저장할 수 있는 테이블로 이해될 수 있으며 해당 데이터는 공간을 차지합니다.
  3. 인덱스 테이블의 데이터를 정렬하고 정렬하는 데 시간이 걸립니다.
  4. insert때때로 인덱스를 재구축하는 것이 가능할 수 update있으며, 데이터의 양이 많을 경우 재구축은 레코드를 재정렬하므로 특정 상황에 따라 인덱스 구축을 신중하게 고려해야 합니다.
  5. 테이블의 인덱스 개수는 5개를 넘지 않아야 한다. 인덱스가 너무 많으면 인덱스가 존재하는지 여부를 고려해야 한다.

15. 인덱싱된 열에 내장 함수를 사용하지 마십시오.

1. 카운터 예

SELECT * FROM user WHERE DATE_ADD(birthday,INTERVAL 7 DAY) >=NOW();

2, 정례

SELECT * FROM user WHERE  birthday >= DATE_ADD(NOW(),INTERVAL 7 DAY);

3. 이유

인덱싱된 열에 내장 함수를 사용하면 인덱스가 무효화됩니다.

16. 종합지수

정렬할 때 인덱스의 한 열만 정렬해야 하는 경우에도 복합 인덱스의 열 순서에 따라 정렬해야 합니다. 그렇지 않으면 정렬 성능이 저하됩니다.

create index IDX_USERNAME_TEL on user(deptid,position,createtime);
select username,tel from user where deptid= 1 and position = 'java开发' order by deptid,position,createtime desc; 

실제로 deptid= 1 and position = 'java开发'조건 하여 createtime 기준으로 내림차순으로 정렬하지만 createtime desc 기준 쓰기 순서의 성능은 좋지 않습니다.

세븐틴(Seventeen), 종합지수의 가장 왼쪽 특징

1. 복합 인덱스 생성

ALTER TABLE employee ADD INDEX idx_name_salary (name,salary)

2. 종합지수의 가장 왼쪽 특성을 만족, 일부만 있어도 종합지수가 효력을 발휘한다.

SELECT * FROM employee WHERE NAME='哪吒编程'

3. 왼쪽의 필드가 나타나지 않으면 가장 왼쪽의 특성이 만족되지 않고 인덱스가 유효하지 않은 것입니다.

SELECT * FROM employee WHERE salary=5000

4. 모든 복합지수를 사용하며, 이름과 급여는 왼쪽 순으로 나타나며 지수가 적용된다.

SELECT * FROM employee WHERE NAME='哪吒编程' AND salary=5000

5. 맨 왼쪽 기능을 위반하지만, MySQL은 SQL 실행시 최적화하고, 최하위 계층은 최적화

SELECT * FROM employee WHERE salary=5000 AND NAME='哪吒编程'

6. 이유

종합지수는 관절지수라고도 하며, (k1,k2,k3)과 같은 관절지수를 생성하면 (k1), (k1,k2), (k1,k2,k3) 3개의 지수를 생성하는 것과 같다. 이것이 가장 왼쪽 일치 원리입니다.

조인트 인덱스는 맨 왼쪽 원칙을 충족하지 않으며 인덱스는 일반적으로 실패합니다.

열여덟, like 문 최적화

프로그래머가 즐겨 사용하는 퍼지 쿼리 는 인덱스 likelike무효화할 가능성이 높습니다.

1. 카운터 예

select * from citys where name like '%大连' (不使用索引)
select * from citys where name like '%大连%' (不使用索引)

2, 정례

select * from citys where name like '大连%' (使用索引)

3. 이유

  • 먼저 퍼지 쿼리를 피하고 꼭 사용해야 하는 경우 전체 퍼지 쿼리를 사용하는 대신 올바른 퍼지 쿼리, 즉 like ‘…%’인덱스를 사용하도록 해야 합니다.
  • 왼쪽 블러 like ‘%...’는 인덱스를 직접 사용할 수 없지만 사용할 reverse + function index수 있는 형태는 like ‘…%’;
  • Full Fuzzy 쿼리는 최적화할 수 없으므로 꼭 사용해야 하는 경우 검색 엔진을 사용하는 것이 좋습니다.

19. SQL 실행 계획을 분석하기 위해 Explain을 사용하십시오.

1, 유형

  1. 시스템: 테이블에는 기본적으로 사용되지 않는 행이 하나만 있습니다.
  2. const: 테이블은 최대 하나의 데이터 행과 일치할 수 있으며 기본 키 쿼리가 더 자주 트리거됩니다.
  3. eq_ref: 이전 테이블의 각 행 조합에 대해 이 테이블의 행을 읽습니다. 이것은 아마도 const 유형을 제외하고 가장 좋은 조인 유형일 것입니다.
  4. ref: 이전 테이블의 각 행 조합에 대해 인덱스 값이 일치하는 모든 행을 이 테이블에서 읽습니다.
  5. range: 행을 선택하는 인덱스를 사용하여 주어진 행 범위만 검색합니다. =, <>, >, >=, <, <=, IS NULL, <=>, BETWEEN 또는 IN 연산자를 사용하여 키워드 열을 상수와 비교할 때 범위를 사용할 수 있습니다.
  6. index: 이 조인 유형은 인덱스 트리만 스캔된다는 점을 제외하고는 ALL과 동일합니다. 인덱스 파일은 일반적으로 데이터 파일보다 작기 때문에 일반적으로 ALL보다 빠릅니다.
  7. 모두: 전체 테이블 스캔;
  8. 성능 순위: 시스템 > const > eq_ref > ref > 범위 > 인덱스 > 모두.
  9. 실제 SQL 최적화에서는 ref 또는 범위 수준에 마침내 도달합니다.

2. 추가 공통 키워드

  • 인덱스 사용: 테이블에 다시 쿼리할 필요 없이 인덱스 트리에서만 정보를 얻습니다.
  • Where 사용: WHERE 절은 다음 테이블과 일치하거나 클라이언트로 전송되는 행을 제한하는 데 사용됩니다. 특별히 테이블에서 모든 행을 가져오거나 확인하지 않는 한 Extra 값이 Using where가 아니고 테이블 조인 유형이 ALL 또는 인덱스인 경우 쿼리에 약간의 오류가 발생할 수 있습니다. 쿼리 양식이 필요합니다.
  • 임시 사용: mysql은 일반적으로 쿼리 에 다른 상황에서 열을 나열할 수 있는 GROUP BYORDER BY절이 포함되어 있을 때 결과를 보유하기 위해 임시 테이블을 빌드합니다.

20. 기타 최적화 방법

1. 테이블을 디자인할 때 모든 테이블과 필드에 해당 주석을 추가합니다.

2. SQL 작성 형식, 키워드의 크기는 일정해야 하며 들여쓰기를 사용해야 합니다.

3. 중요한 데이터를 수정하거나 삭제하기 전에 먼저 백업하십시오.

4. 대신에 존재하는 것을 사용하는 것이 좋은 선택인 경우가 많습니다.

5. where 뒤에 있는 필드의 경우 데이터 유형의 암시적 변환에 주의하십시오.

인덱스가 사용되지 않음

SELECT * FROM user WHERE NAME=110

(1) 작은 따옴표를 추가하지 않으면 문자열과 숫자를 비교하고 유형이 일치하지 않기 때문에
(2) MySQL은 암시적 유형 변환을 수행하고 숫자 유형으로 변환한 다음 비교합니다.

6. 모든 열을 다음과 같이 정의하십시오.NOT NULL

NOT NULL열은 공간 효율적이며 NULLNULL은 .
NULL열은 널 포인터 문제에 주의해야 하고 NULL열을 계산하고 비교할 때는 널 포인터 문제에 주의해야 합니다.

7. 유사 삭제 설계

8. 데이터베이스 및 테이블의 문자 집합은 가능한 한 UTF8을 사용합니다.

(1) 글자 깨짐 문제를 피할 수 있다.
(2) 다른 문자 집합의 비교 및 ​​변환으로 인한 인덱스 무효화 문제를 피할 수 있습니다.

9, 테이블에서 개수(*) 선택;

조건이 없는 이러한 카운트는 전체 테이블 스캔을 유발하고 비즈니스 의미가 없으므로 피해야 합니다.

10. 다음 위치에 있는 필드에 대한 표현식 연산을 피하십시오.

(1) SQL 구문 분석 중 필드가 표현식과 관련된 경우 전체 테이블 스캔이 수행됩니다
. (2) 필드가 깨끗하고 표현식이 없으며 인덱스가 적용됩니다.

11. 임시 테이블 정보

(1) 시스템 테이블 자원의 소비를 줄이기 위해 임시 테이블의 빈번한 생성 및 삭제를 피하십시오;
(2) 새로운 임시 테이블을 생성할 때 한 번에 많은 양의 데이터가 삽입되는 경우 create 대신 select into를 사용할 수 있습니다. 많은 수의 로그가 발생하지 않도록 테이블 ;
(3) 데이터 양이 많지 않은 경우 시스템 테이블의 리소스를 완화하기 위해 먼저 테이블을 생성한 다음 삽입해야 합니다.
(4) 다음을 사용하는 경우 임시 테이블의 경우 저장 프로시저 끝에서 모든 임시 테이블을 명시적으로 삭제해야 합니다. 먼저 테이블을 자른 다음 테이블을 삭제하면 시스템 테이블이 장기간 잠기는 것을 방지할 수 있습니다.

12. 성별과 같이 반복 데이터가 많은 필드에는 인덱스가 적합하지 않으며, 필드 정렬을 위해서는 인덱스를 생성해야 한다.

13. 중복 제거 고유 필터 필드가 적음

  1. 고유한 명령문 이 cpu없는 명령문보다 시간이 더 오래 걸립니다.distinct
  2. 많은 필드를 쿼리할 때 사용되는 경우 distinct데이터베이스 엔진은 데이터를 비교하고 중복 데이터를 필터링합니다.
  3. cpu그러나 이 비교 및 ​​필터링 프로세스는 시간 과 같은 시스템 리소스를 차지합니다.

14. 대규모 트랜잭션 작업을 피하고 시스템 동시성 기능을 개선하십시오.

Innodb15. 모든 테이블은 스토리지 엔진 을 사용해야 합니다.

Innodb"트랜잭션 지원, 행 수준 잠금 지원 및 더 나은 복구성"은 높은 동시성에서 성능이 더 좋으므로 특별한 요구 사항(즉 Innodb, 열 스토리지, 스토리지 공간 데이터, 등), 모든 테이블은 Innodb스토리지 엔진 을 사용해야 합니다 .

16. 커서 사용을 피하십시오

커서의 효율성이 떨어지기 때문에 커서가 조작하는 데이터가 10,000행을 초과하면 다시 쓰기를 고려해야 합니다.

스물하나, 마인드맵

"MySql Database Advanced Practice" 사본 5부 보내기

여기에 이미지 설명 삽입

더 높은 품질의 기사: Nezha Essence 기사 요약

рекомендация

отblog.csdn.net/guorui_java/article/details/126542005
рекомендация