MySqlデータ操作~接続メソッド、インデックス、トランザクション、関数(2)

1. MySQLデータベース接続方法の紹介【直積結合、内部結合、外部結合-左結合、外部結合-右結合】

デカルト積の接続:

SELECT article.id AS id, article.title AS title, article.state as state, article_cate.title as cate 
FROM article,article_cate WHERE article.cate_id = article_cate.id ORDER BY id ; 

注: ID による順序とは、検索後の ID に基づいて順序付けすることを指します。

内部結合:

SELECT * FROM lesson  INNER JOIN lesson_student ON lesson.id = lesson_student.lesson_id AND lesson_student.student_id =1


#### from 条件后面的跟的+ 表名称  inner josn + 表名称  on + 查询条件 出来的图就是对应顺序不一致

SELECT * FROM lesson_student INNER JOIN lesson ON lesson.id = lesson_student.lesson_id AND lesson_student.student_id =1

注: 内部結合内部結合に関連付けられたテーブル接続条件のテーブル名 ------- ORDER BY フィールド名 ID で並べ替えられます。

外部結合(左結合)

SELECT * FROM lesson LEFT JOIN lesson_student ON lesson.id = lesson_student.lesson_id AND lesson_student.student_id =1

結果:  

外部結合(右結合)

SELECT * FROM lesson RIGHT JOIN lesson_student ON lesson.id = lesson_student.lesson_id AND lesson_student.student_id =1

結果: 

注: 左右のリンクを使用する場合、参照オブジェクトとして使用されるテーブルに応じて、データの表示方法が異なります。具体的には写真をご覧ください...

2.MySQLインデックス

インデックスの確立は、mysql を効率的に実行するために非常に重要であり、インデックスにより MySQL の検索速度が大幅に向上します。

その中で、MySQL の一般的なインデックス タイプには、通常のインデックス、一意のインデックス、フルテキスト インデックス、空間インデックス Sqatial が含まれます。

操作を説明するために、500,000 個のデータを例に挙げます。

ユーザーに挿入(`ユーザー名`) ユーザーからユーザー名を選択

ステップ 1. 500,000 個のデータを含むデータベースを作成します。


SET NAMES utf8mb4;
set names utfmb4;设置字符集


## 第一个语句SET NAMES utf8mb4;用于设置会话字符集为utf8mb4。这是为了确保数据的正确存储和检索,特别是对于包含表情符号等特殊字符的文本数据。


SET FOREIGN_KEY_CHECKS = 0;
set foreign_key_checks = 1; 默认值为1,同时表示打开外键约束检查。
 
##第二个语句SET FOREIGN_KEY_CHECKS = 0;用于关闭外键约束检查。在插入或更新数据时,外键约束会检查关联表中是否存在匹配的键。关闭这个检查可以避免在插入或更新数据时引发外键约束错误。注意,在完成插入或更新操作后,应该重新打开外键约束检查,即将SET FOREIGN_KEY_CHECKS设置回默认值1。


注: これら 2 つのステートメントは、テーブル作成ステートメントを実行する前に実行する必要があります。

-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user`  (
  `id` int(0) NOT NULL AUTO_INCREMENT,
  `username` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  `balance` decimal(10, 2) NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;


-- ----------------------------
-- Records of user
-- ----------------------------

### 照合配置ルール。MySQL バージョン 8.0 以降の場合は、上記のステートメントを使用します。バージョン 8.0 より前のローカルの場合は、次のステートメントを使用してテーブルを作成します。

-- ----------------------------
-- Table structure for user
-- ----------------------------

DROP TABLE IF EXISTS `user`;
CREATE TABLE `user`  (
  `id` int(0) NOT NULL AUTO_INCREMENT,
  `username` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  `balance` decimal(10, 2) NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;


-- ----------------------------
-- Records of user
-- ----------------------------

INSERT INTO `user` VALUES (1, '张三', 0.00);
INSERT INTO `user` VALUES (2, '李四', 200.00);
INSERT INTO `user` VALUES (5, 'wangwu', 100.00);

50万件のデータの作り方について;

### 500,000 個のデータを挿入する手順は次のとおりです。
(-- mysql で関数の作成が許可されているかどうかを確認してください:)
1. SHOW VARIABLES LIKE 'log_bin_trust_function_creators'; 

show variables like 'log_bin_trust_function_createors'

(-- 查看mysql是否允许创建函数:)
1. SHOW VARIABLES LIKE 'log_bin_trust_function_creators'; 


(--コマンド: 関数の作成を許可する 設定: (すべてのセッションに対してグローバルに有効)
2. SET GLOBAL log_bin_trust_function_creators=1;  

set global log_bin_trust_function_creators=1;

(-- 命令开启:允许创建函数设置:(global-所有session都生效)
2. SET GLOBAL log_bin_trust_function_creators=1;  
-- 检查存储过程是否存在
SHOW PROCEDURE STATUS WHERE name = 'insert_user';

-- 如果存在,删除存储过程
DROP PROCEDURE IF EXISTS `insert_user`;

DELIMITER $$
CREATE PROCEDURE  insert_user(START INT, max_num INT)
BEGIN  
	DECLARE i INT DEFAULT 0;   
	#set autocommit =0 把autocommit设置成0  
	SET autocommit = 0;    
	REPEAT  
		SET i = i + 1;  
		INSERT INTO `user` ( id , username, balance ) VALUES ((START+i) ,rand_string(6), rand_num(1,10000));  
		UNTIL i = max_num  
	END REPEAT;  
	COMMIT;  
END$$

-- 插入50万条数据
CALL insert_user(100000,500000); 

具体的な詳細
コード スニペットの詳細な解釈は次のとおりです。

```sql
DELIMITER $$
``
` DELIMITER` ステートメントは、MySQL のステートメント区切り文字を変更し、`$$` に設定するために使用されます。これは、ストアド プロシージャ内のセミコロンとの競合を避けるためです。

```sql
CREATE PROCEDURE insert_emp(START INT, max_num INT)
```
`CREATE PROCEDURE` ステートメントは、`insert_emp` という名前のストアド プロシージャを作成するために使用されます。`START` と `max_num` の 2 つのパラメータを受け入れます。どちらも整数型です。

```sql
BEGIN
```
`BEGIN` はストアド プロシージャの開始を示します。

```sql
DECLARE i INT DEFAULT 0;
``
`DECLARE` ステートメントは、`i` という名前のローカル変数を宣言し、それを 0 に初期化します。この変数は、ループ反復のカウンターを格納するために使用されます。

```sql
SET autocommit = 0;
```
`SET` ステートメントは、`autocommit` の値を 0 に設定します。これにより、トランザクションの自動コミットが無効になり、明示的にコミットされるまでトランザクションは開いたままになります。

```sql
REPEAT
    SET i = i + 1;
    INSERT INTO `user` (id, username, Balance) VALUES ((START + i), rand_string(6), rand_num(1, 10000)); UNTIL i = max_num
END REPEAT;
```
これは繰り返しループ ブロック (REPEAT...UNTIL) です。各ループで、`i` の値がインクリメントされ、指定されたルールに従って生成されたデータが `user` テーブルに挿入されます。ループは、「i」の値が「max_num」と等しくなるまで繰り返されます。

```sql
COMMIT;
```
`COMMIT` ステートメントは、現在のトランザクションをコミットし、以前のすべての挿入操作をデータベースに保持するために使用されます。

```sql
END$$
```
`END` はストアド プロシージャの終了を示します。

要約すると、このストアド プロシージャの機能は、挿入レコード数が `max_num` に達するまで、`START` から始まる増分番号、ランダムに生成されたユーザー名、およびランダムに生成された残高を `user` テーブルに挿入することです。事務を提出する。同時に、トランザクションの一貫性を確保するために、自動コミットおよび手動コミット トランザクションは無効になります。

このコードを実行する前に、「rand_string」関数と「rand_num」関数が存在すること、およびストアド プロシージャがコードのコンテキストで正しく使用されていることを確認する必要があることに注意してください。

その他の詳細な操作については、ドキュメントを参照して自分で実験することができます。

3. MySQL トランザクション:

トランザクション処理を使用すると、データベースの整合性を維持し、 SQL ステートメントのバッチがすべて実行されるかどうかを確認できます。
埋め込む。

1.デモとしてユーザーテーブルを作成します。

## 由于版本不一样创建数据库执行语句有一点差异化:
## collate =  utf8mb4_0900_ai_ci 可能需要改成:utf8mb4_general_ci
## 也就是说排序规则改成 utf8mb4_general_ci 是因为本地的navicat for mysql 工具导致的
## 作者本地使用的工具是 v11.0.10 -企业版本


SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for user
-- ---------------------------- 
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` int(0) NOT NULL AUTO_INCREMENT, 
`username` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL
DEFAULT NULL, 
`balance` decimal(10, 2) NULL DEFAULT NULL, PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;


-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES (1, '熊大', 100.00);
INSERT INTO `user` VALUES (2, '光头强', 100.00);
SET FOREIGN_KEY_CHECKS = 1;


---------------------------------分界线-------------------------------------------

## 高级版本执行以下语句
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for user
-- ---------------------------- 
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` int(0) NOT NULL AUTO_INCREMENT, `username` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL
DEFAULT NULL, `balance` decimal(10, 2) NULL DEFAULT NULL, PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;


-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES (1, '熊大', 100.00);
INSERT INTO `user` VALUES (2, '光头强', 100.00);
SET FOREIGN_KEY_CHECKS = 1;

2. 事例:Xiong Da の口座からBald Qiang の口座に100元が送金された

アイデアを実装する方法:
1. Xiong Daの口座から 100元が引き落とされます
UPDATE user set balance = balance-100 WHERE id=1
2.ハゲ強の口座が100元増加
UPDATE user set balance = balance+100 WHERE id=2
メソッドの実行:
UPDATE user set balance = balance-100 WHERE id=1

UPDATE user set balance = balance+100 WHERE id=2



反馈结果:

[Err] 1064 - You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'UPDATE user set balance = balance+100 WHERE id=2' at line 3
フィードバック結果と問題分析:
1. SQL ステートメントの更新データが 1 つの文で実行される場合、それは確実に実行可能であり、明らかなエラー メッセージはありません。
2. 2 つの SQL 文を使用して、更新時に異なる ID 値の下で他の属性値を更新します このとき、フィードバック結果は err の mysql 構文エラーであり、エラー箇所がどこにあるかを示します。
Xiong Daのアカウントを更新した後、 Bald Qiang のアカウントを更新する準備中にエラーが発生した場合(プログラム エラー、データベースが存在しないなど)
誤接続や異常停電などのエラー)。これにより、データの不整合が発生します。データの整合性を確保するため、現時点では
トランザクションを使用できます。

3. トランザクション使用の概念、またはトランザクション使用のタイミングの導入

 トランザクションの主な概念:

        BEGIN (開始) トランザクションを開始します

        ROLLBACK (ロールバック) トランザクションのロールバック

        COMMIT(コミット)トランザクションの確認

実際の使用時間:
  

前の質問についての詳細な考え方を紹介します。
1. Xiong Da のアカウントが 100 減少します
。 2. Bald Qiang のアカウントが 100 増加します。

次のような相対トランザクションの使用も同様です。


begin;

update user set balance = balance-100 where id=1;

rollback; 只有执行报错时候才可以使用 回滚操作;



上記は、ロールバックを使用してその時点でトランザクションをロールバックするタイミングのデモンストレーションケースです。
対応するコードを添付します。
begin;
update user set balance = balance-100 where id=1;
update user set balance = balance+100 where id=2;
commit;

上記が該当事項となりますが、

4.mysqlロック

Mysql のロックには、 テーブル レベルのロック 行レベルのロックが含まれます。
最も一般的に使用されるテーブルレベルのロック

1. 読み取りロックを追加する

いわゆる読み取りロックは、同時に読み取ることはできますが、同時に書き込むことはできません。読み取りロック期間中は、ロックが解放されるまで書き込み操作を実行できません。
使用するシーン:
        他のトランザクションによる結果セットの更新を防止しながら、結果セットの最新バージョンを読み取ります。
これは主に、データの依存関係が必要な場合にレコードの特定の行が存在するかどうかを確認し、このレコードに対して操作が実行されないようにするために使用されます。
UPDATE または DELETE 操作
テーブル ユーザーに読み取りロックを追加します。lock table user read;
ユーザーの読み取りロックを解除します。テーブルのロックを解除します。

2.書き込みロックを追加する

テーブルをロックしているユーザーのみが読み取りおよび書き込み操作を実行でき、他のユーザーは実行できません(製品在庫に対する同時操作)
使用するシーン:
lock table user write;
unlock tables;
実際のビジネス ニーズに応じて読み取り/書き込みロックを使用します。上記はテーブル レベルのロックの簡単な操作です。

補充:

行レベルのロック: 各操作は、対応する行データをロックします。

行レベルのロックでは、各操作に対応する行データがロックされます。ロックの粒度は最も小さく、ロックの競合の可能性は最も低く、同時実行性は最も高くなります。InnoDB ストレージ エンジンに適用されます。

InnoDB データはインデックスに基づいて編成されており、行ロックは、レコードをロックするのではなく、インデックス上のインデックス エントリをロックすることによって実装されます行レベルのロックの場合、主に次の 3 つのカテゴリに分類されます。

  1. 行ロック(レコード ロック): 単一行レコードをロックして、他のトランザクションが行を更新および削除できないようにします。RC と RR の両方の分離レベルでサポートされます。
  2. Gap Lock : インデックス レコード間のギャップ (このレコードを除く) をロックして、インデックス レコード間のギャップが変更されないようにし、他のトランザクションがこのギャップに挿入してファントム読み取りを引き起こすのを防ぎます。R 分離レベルでサポートされます。
  3. Next- Key Lock : 行ロックとギャップ ロックを組み合わせたもので、データを同時にロックし、データの前のギャップをロックしますRR 分離レベルでサポートされます。

InnoDB は、次の 2 種類の行ロックを実装します。

  • 共有ロック (S): 1 つのトランザクションが行を読み取ることを許可し、他のトランザクションが同じデータ セットに対して排他ロックを取得できないようにします。
  • 排他ロック (X): 排他ロックを取得するトランザクションがデータを更新できるようにし、他のトランザクションが同じデータ セットに対して共有ロックや排他ロックを取得できないようにします。

行ロックでは、共有ロックと共有ロックは相互排他的ではなく、共有ロックと排他的ロックは相互排他的であり、排他的ロックと排他的ロックは相互排他的です。

InnoDB エンジンでは、SQL の実行時に行ロックが追加されます。

SQL 行ロックの種類 説明する
入れる ... 排他ロック 自動ロック
アップデート ... 排他ロック 自動ロック
消去 ... 排他ロック 自動ロック
セレクト(通常) ロックなしで
選択 ... 共有モードでロック 共有ロック SELECT の後に LOCK IN SHARE MODE を手動で追加する必要がある
...更新用に選択してください 排他ロック SELECT の後に FOR UPDATE を手動で追加する必要があります

デフォルトでは、InnoDB は REPEATABLE READ (RR) トランザクション分離レベルで実行され、検索とインデックス スキャンにネクスト キー (ネクスト キー) ロックを使用してファントム リードを防ぎます。

  • 一意のインデックスに対して取得する場合、既存のレコードの同等の一致は行ロック用に自動的に最適化されます。
  • InnoDB の行ロックはインデックスに追加されるロックですインデックス条件によってデータが取得されない場合、InnoDB はテーブル内のすべてのレコードをロックし、テーブル ロックにアップグレードされます

Gap Lock Pro キーロックの説明 (ファントム読み取りを防ぐため):

  1. インデックス (一意のインデックス) に対する同等のクエリは、存在しないレコードをロックするときにギャップ ロックに最適化されます。
  2. インデックス (通常のインデックス) に対する同等のクエリの場合、右へのトラバース時に最後の値がクエリ要件を満たさない場合、ネクスト キー ロックはギャップ ロックに縮退します。
  3. インデックスに対する範囲クエリ (一意のインデックス) - 条件を満たさない最初の値までアクセスします。

具体的な指示:

 -- 首先,准备一张表,id为主键,id分别为 1,3,10,15
 mysql> select * from user_innodb;
 +----+----------+------+
 | id | username | age  |
 +----+----------+------+
 |  1 | 张三     |    1 |
 |  3 | 李四     |    4 |
 | 10 | 王五     |    9 |
 | 15 | 赵六     |   15 |
 +----+----------+------+
 4 rows in set (0.00 sec)
 ​
 ​
 -- 对于1、索引上的等值查询(唯一索引),给不存在的记录加锁时,优化为间隙锁。
 ​
 -- 说明,当开启事务后,如果 此时执行 下面update语句
 update user_innodb set username = '王五' where id = 7
 -- 因为不存在 id = 7 的数据,这是就会给 id = 2 到 id = 9(不包含 4和 10)之间加间隙锁,此时其他客户端想insert (或 update 或 delete)id在2到9之间的数据就会进入阻塞状态。
 ​
 ​
 ​
 -- 对于2、索引上的等值查询(普通索引),向右遍历时最后一个值不满足查询需求时,next-key lock退化为间隙锁。
 -- 首先创建一个 非唯一的普通索引 比如 age
 mysql> create index idx_user_age on user_innodb(age);
 Query OK, 0 rows affected (0.09 sec)
 Records: 0  Duplicates: 0  Warnings: 0
 ​
 -- 查看索引
 mysql> show index from user_innodb;
 +-------------+------------+--------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+
 | Table       | Non_unique | Key_name     | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment | Visible |
 +-------------+------------+--------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+
 | user_innodb |          0 | PRIMARY      |            1 | id          | A         |           3 |     NULL |   NULL |      | BTREE      |         |               | YES     |
 | user_innodb |          1 | idx_user_age |            1 | age         | A         |           4 |     NULL |   NULL | YES  | BTREE      |         |               | YES     |
 +-------------+------------+--------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+
 2 rows in set (0.09 sec)
 ​
 -- 首先开启事务
 -- 当执行下列语句时,若执行的是 select * from user_innodb where age = 4; 不会加锁
 select * from user_innodb where age = 4 lock in share mode; 
 ​
 ​
 -- 查看锁的情况,与表锁中查看意向锁的SQL一致
 mysql> select object_schema,object_name,index_name,lock_type,lock_mode,lock_data from performance_schema.data_locks;
 mysql> select object_schema,object_name,index_name,lock_type,lock_mode,lock_data from performance_schema.data_locks;
 +---------------+-------------+--------------+-----------+-----------+-----------+
 | object_schema | object_name | index_name   | lock_type | lock_mode | lock_data |
 +---------------+-------------+--------------+-----------+-----------+-----------+
 | heima_learn   | user_innodb | NULL         | TABLE     | IS        | NULL      |
 | heima_learn   | user_innodb | idx_user_age | RECORD    | S         | 4, 3      |
 | heima_learn   | user_innodb | PRIMARY      | RECORD    | S         | 3         |
 | heima_learn   | user_innodb | idx_user_age | RECORD    | S,GAP     | 9, 10     |
 +---------------+-------------+--------------+-----------+-----------+-----------+
 ​
 -- 因为 age 是一个普通索引,可能会出现重复的数据,所以说此时会加 3 个共享锁
 -- 第一个  | S         | 4, 3      | 
 -- 这个index_name =idx_user_age,lock_mode中没有GAP  (这里的 4, 3 指的是主键 age 的值为 4,id的值为 3) 的临键锁,表示数据 id = 3 的临键锁,值id = 2,3
 ​
 ​
 -- 第二个 | S         | 3         | 
 -- 第二个的 index_name 为 PRIMARY,表示 给id = 3 (age = 4)的数据加了行锁
 ​
 -- 第三个 | S,GAP     | 9, 10     | 
 -- 这里(9 代表 age = 9, 10 代表 id = 10) index_name = idx_user_age,在 lock_mode 存在 GAP,表示是一个间隙锁,锁住的数据为 id = 4 到 id = 9。
 ​
 -- 这么做的操作就是为了防止出现 幻读
 ​
 ​
 ​
 ​
 -- 对于3、索引上的范围查询(唯一索引)--会访问到不满足条件的第一个值为止。
 ​
 -- 重新开启事务
 -- 执行下列语句
  select * from user_innodb where id >= 10 lock in share mode;
  
  -- 查看锁的情况
 mysql> select object_schema,object_name,index_name,lock_type,lock_mode,lock_data from performance_schema.data_locks;
 +---------------+-------------+------------+-----------+-----------+------------------------+
 | object_schema | object_name | index_name | lock_type | lock_mode | lock_data              |
 +---------------+-------------+------------+-----------+-----------+------------------------+
 | heima_learn   | user_innodb | NULL       | TABLE     | IS        | NULL                   |
 | heima_learn   | user_innodb | PRIMARY    | RECORD    | S         | 10                     |
 | heima_learn   | user_innodb | PRIMARY    | RECORD    | S         | supremum pseudo-record |
 | heima_learn   | user_innodb | PRIMARY    | RECORD    | S         | 15                     |
 +---------------+-------------+------------+-----------+-----------+------------------------+
 4 rows in set (0.00 sec)
 ​
 -- 可以看出加了,id = 10的行锁,id = 15及id > 10 到 id = 15的临键锁,id = 15 及id > 15 的临键锁
 ​
 ​
 -- 当继续执行下面SQL时 
 select * from user_innodb where id >= 9 lock in share mode;
 ​
  
  -- 查看锁的情况
 mysql> select object_schema,object_name,index_name,lock_type,lock_mode,lock_data from performance_schema.data_locks;
 +---------------+-------------+------------+-----------+-----------+------------------------+
 | object_schema | object_name | index_name | lock_type | lock_mode | lock_data              |
 +---------------+-------------+------------+-----------+-----------+------------------------+
 | heima_learn   | user_innodb | NULL       | TABLE     | IS        | NULL                   |
 | heima_learn   | user_innodb | PRIMARY    | RECORD    | S         | 10                     |
 | heima_learn   | user_innodb | PRIMARY    | RECORD    | S         | supremum pseudo-record |
 | heima_learn   | user_innodb | PRIMARY    | RECORD    | S         | 10                     |
 | heima_learn   | user_innodb | PRIMARY    | RECORD    | S         | 15                     |
 +---------------+-------------+------------+-----------+-----------+------------------------+
 --可以看出,此时的锁的情况是 id = 10的行锁,id = 10 到 id > 10 的临键锁,id = 15 的行锁
私のメモがあなたの学習に役立つことを願っています。来て!
引用記事アドレス:

おすすめ

転載: blog.csdn.net/A_LWIEUI_Learn/article/details/132271727