讀《MySQL必知必會》我學到了什麼?

前言

最近在寫項目的時候發現自己的SQL基本功有些薄弱,遂上知乎查詢MYSQL關鍵字,期望得到某些高贊答案的指點,於是乎發現了

https://www.zhihu.com/question/34840297/answer/272185020 這位老兄的建議的書單,根據他的建議首先拜讀了《MYSQL必知必會》這本書,整體講的很基礎,頁數也不多一共 253 頁,適合基礎比較薄弱的同學進行食用。然後循序漸進,閱讀更深層次的書籍進行自我提升。這裏記載了自己在閱讀的過程中記錄的一些關鍵內容,分享給大家。書本 PDF 可以在上面的知乎鏈接獲取,或者點擊 http://www.notedeep.com/note/38/page/282 前往老哥的深度筆記進行下載。

閱讀心得

SQL語句和大小寫

SQL語句不區分大小寫,並且在 Windows 環境下,4.1.1版本之後(現在常用的都是 5.6/5.7/8.0+),MYSQL表名,字段名也是不區分大小寫的,因此我們在命名的時候建議使用單個單詞_單個單詞的形式命名,如:mysql_crash_course user_role

這裏附上阿里代碼規範的一條強制要求:

【強制】表名、字段名必須使用小寫字母或数字,禁止出現数字開頭,禁止兩個下劃線中間只出現数字。數據庫字段名的修改代價很大,因為無法進行預發布,所以字段名稱需要慎重考慮。
說明: MySQL 在 Windows 下不區分大小寫,但在 Linux 下默認是區分大小寫。因此,數據庫名、表名、字段名,都不允許出現任何大寫字母,避免節外生枝。

不能部分使用DISTINCT

  • DISTINCT 關鍵字應用於所有列而不僅是前置它的列。如果給出 SELECT DISTINCT vend_id,prod_price ,除非指定的兩個列都不同,否則所有行都將被檢索出來。
  • DISTINCT 不會返回其後面跟的所有字段都相同的列。即兩行中SELECT查詢的任何一個字段都相同才會被去重。不能通過DISTINCT加括號 () 等方式來對單個字段進行去重。

比如項目中有這麼一個需求:

需要分頁查詢綁了某收費標準的房屋,因為是房屋列表查詢,我們默認相同ID的房屋只出現一次,錯誤的SQL如下:

房屋表

收費標準表

SELECT DISTINCT h.id, h.num, s.name 
FROM house h 
LEFT JOIN standard s 
ON h.id = s.room_id 
WHERE s.name = '物業費' OR s.name = '暖氣費';

這條語句並不能返回想要的結果,即每套房屋只出現一次,因為不同的收費標準名稱不一樣,DISTINCT 不能對部分查詢條件去重。可以看到房號為1-001的記錄出現了兩次。

不過其實按照需求的描述我這裏僅查詢房屋的信息,對於查詢結果來說同一條記錄的房屋信息肯定完全相同,因此 DISTINCT 在我的業務中滿足要求。而有其他業務需要此關鍵字的時候,請大家慎重使用,切記不能部分使用該關鍵字

區分大小寫和排序順序

在對文本性的數據進行排序時,A與a相同嗎?a位於B之前還是位於Z之後?

在創建字段時可以指定字符集,一般使用 utf8mb4, 此時可以選擇相應的排序規則。

  1. utf8mb4_general_ci ci即大小寫不敏感,排序時忽略大小寫,A a 視作相同
  2. utf8mb4_bin / 帶 cs 的即大小寫敏感,相應的升序排列的話, A~Z 在前,小a~z在後
    相應的,在設置大小寫敏感后,查詢條件 where cs = ‘a’ 只能查找到表中該字段為小寫 a 的行。而不敏感,即ci時,A,a都可以被查詢出來。

BETWEEN關鍵字的注意事項

在區間查詢時,我們最關注的不應該是區間內的能否被匹配到,因為這是肯定的。而區間的邊界能否被匹配才是我們應該注意的知識點,BETWEEN AND 關鍵字匹配區間時,包含左右邊界條件。如下面的 SQL 執行結果如下:

SELECT prod_name, prod_price 
FROM products p
WHERE p.`prod_price` BETWEEN 5.99 AND 10

NULL 與不匹配

在通過過濾選擇出不具有特定值的行時,你可能希望返回具有 NULL 值的行。但是,不行。常見的錯誤會發生在is_xxx 字段上,我經常有這個毛病,

對於 is_delete 字段,我認為為 null 或者 0 都是未刪除的房屋,所以當我使用

SELECT * FROM house WHERE is_delete = '0';

查詢未刪除的房屋時,我只能查到 id 為 3 的房屋,這顯然與我的預期是不符的,解決辦法是where後面加 or is_delete is null ,或者給 is_delete 列默認值 0;建議使用後者。

這裏附上阿里代碼手冊的一條強制項目:

【強制】表達是與否概念的字段,必須使用 is_xxx 的方式命名,數據類型是 unsigned tinyint(1 表示是,0 表示否)。說明:任何字段如果為非負數,必須是 unsigned 。

解釋:tinyint 相當於 Java 中的 byte,取值範圍 -128 ~ 127 ,用來表達是否長度已經足夠,也可以用來表示人的年齡。而 unsigned 表示無符號的,對於確定為非負數的字段,使用 unsigned 可以將取值範圍擴大一倍。

AND 和 OR 的計算次序

舉個例子:假如需要列出價格為10美元(含)以上且由 1002 或 1003 製造的所有產品。下面的 SELECT 語句使用 AND 和 OR 操作符的組合建立了一個WHERE 子句:

SELECT *
FROM products
WHERE vend_id = 1002 OR vend_id = 1003 AND prod_price >= 10;

查詢結果如下:

而被我用紅框標註的行很顯然不是我們需要的行,為什麼會這樣呢?原因在於計算的次序。SQL(像多數語言一樣)在處理 OR 操作符前,優先處理 AND 操作符。當SQL看到上述 WHERE 子句時,它理解為由供應商 1003 製造的任何價格為10美元(含)以上的產品,或者由供應商 1002 製造的任何產品,而不管其價格如何。換句話說,由於 AND 在計算次序中優先級更高,操作符被錯誤地組合了。解決方法就是加 ();正確的 SQL 如下:

SELECT *
FROM products
WHERE (vend_id = 1002 OR vend_id = 1003) AND prod_price >= 10;

建議在任何時候使用具有 AND 和 OR 操作符的 WHERE 子句,都應該使用圓括號明確地分組操作符。不要過分依賴默認計算次序,即使它確實是你想要的東西也是如此。使用圓括號沒有什麼壞處,它能消除歧義。

UNION 組合查詢

  • 利用 UNION ,可給出多條SELECT 語句,將它們的結果組合成單個結果集。
  • UNION 中的每個查詢必須包含相同的列、表達式或聚集函數。
  • 在使用UNION 時,重複的行被自動取消。這是 UNION 的默認行為,但是如果需要,可以改變它。事實上,如果想返回所有匹配行,可使用 UNION ALL 而不是 UNION 。
  • 在用 UNION 組合查詢時,只能使用一條 ORDER BY 子句,它必須出現在最後一條 SELECT 語句之後

INSERT語句總是使用列的列表

一般不要使用沒有明確給出列的列表的 INSERT 語句。使用列的列表能使SQL代碼繼續發揮作用,即使表結構發生了變化。實際開發中有可能由於業務的需要,對錶結構進行修改,添加/刪除某一列。這時如果代碼中使用的SQL語句是沒有明確列表的插入語句就會報錯。當然一般我們使用逆向工程生成的 insertSelective(POJO) 並不存在這個問題,因為它對應生成的 SQL 會為我們生成列的列表。

小心使用更新和刪除語句

MySQL 沒有撤銷按鈕,因此在使用 UPDATE / DELETE 時一定要加上 WHERE 條件,並且在執行更新/刪除操作之前先進行 SELECT 操作,開啟事務。在執行結束后核對影響的行數和 SELECT 查詢出來的行數一致后再 COMMIT;

另外,使用 ALTER TABLE 要極為小心,應該在進行改動前做一個完整的備份(模式和數據的備份)。數據庫表的更改不能撤銷,如果增加了不需要的列,可能不能刪除它們。類似地,如果刪除了不應該刪除的列,可能會丟失該列中的所有數據。

視圖的規則和限制

  • 與表一樣,視圖必須唯一命名(不能給視圖取與別的視圖或表相同的名字)。
  • 對於可以創建的視圖數目沒有限制。
  • 為了創建視圖,必須具有足夠的訪問權限。這些限制通常由數據庫管理人員授予。
  • 視圖可以嵌套,即可以利用從其他視圖中檢索數據的查詢來構造一個視圖。
  • ORDER BY 可以用在視圖中,但如果從該視圖檢索數據 SELECT 中也含有 ORDER BY ,那麼該視圖中的 ORDER BY 將被覆蓋。
  • 視圖不能索引,也不能有關聯的觸發器或默認值。
  • 視圖可以和表一起使用。例如,編寫一條聯結表和視圖的 SELECT語句。

其中視圖不能索引這一點要格外注意,在我開發過程中遇到過這樣一個視圖:使用 UNION 連結了好幾張表進行查詢,對查詢的結果使用 WHERE 條件再過濾,這裏雖然對於被連接的表對於 WHERE 條件后的字段都建立了索引,但是使用 UNION 連結生成視圖的臨時表並不能擁有索引。因此查詢的效率會很慢!所以建議在 UNION 查詢中將 WHERE 條件放在每個 SELECT 語句中。

改善性能的一些建議

  • 首先,MySQL(與所有DBMS一樣)具有特定的硬件建議。在學習和研究MySQL時,使用任何舊的計算機作為服務器都可以。但
    對用於生產的服務器來說,應該堅持遵循這些硬件建議。

  • 一般來說,關鍵的生產DBMS應該運行在自己的專用服務器上。

  • MySQL是用一系列的默認設置預先配置的,從這些設置開始通常是很好的。但過一段時間后你可能需要調整內存分配、緩衝區大小等。(為查看當前設置,可使用 SHOW VARIABLES; 和 SHOWSTATUS; )

  • MySQL一個多用戶多線程的DBMS,換言之,它經常同時執行多個任務。如果這些任務中的某一個執行緩慢,則所有請求都會執
    行緩慢。如果你遇到顯著的性能不良,可使用 SHOW PROCESSLIST显示所有活動進程(以及它們的線程ID和執行時間)。你還可以用KILL 命令終結某個特定的進程(使用這個命令需要作為管理員登錄)。

  • 總是有不止一種方法編寫同一條 SELECT 語句。應該試驗聯結、並、子查詢等,找出最佳的方法。

  • 使用 EXPLAIN 語句讓MySQL解釋它將如何執行一條 SELECT 語句。

  • 一般來說,存儲過程執行得比一條一條地執行其中的各條MySQL語句快。但存儲過程一般難以調試和擴展,並且沒有移植性,因此阿里代碼規約裏面強制禁止使用存儲過程

  • 應該總是使用正確的數據類型。

  • 決不要檢索比需求還要多的數據。換言之,不要用 SELECT * (除非你真正需要每個列)。

  • 有的操作(包括 INSERT )支持一個可選的 DELAYED 關鍵字,如果使用它,將把控制立即返回給調用程序,並且一旦有可能就實際執行該操作。

    ​ 延遲插入,當插入和查詢併發執行時,插入被放入等待隊列中。直至所有查詢執行完畢后執行插入。並且MYSQL會在收到插入請求后直接返回給客戶端狀態信息,既是INSERT語句還在隊列中

  • 在導入數據時,應該關閉自動提交。你可能還想刪除索引(包括FULLTEXT 索引),然後在導入完成后再重建它們。

  • 必須索引數據庫表以改善數據檢索的性能。確定索引什麼不是一件微不足道的任務,需要分析使用的 SELECT 語句以找出重複的WHERE 和 ORDER BY 子句。如果一個簡單的 WHERE 子句返回結果所花的時間太長,則可以斷定其中使用的列(或幾個列)就是需要索引的對象。

  • 你的 SELECT 語句中有一系列複雜的 OR 條件嗎?通過使用多條SELECT 語句和連接它們的 UNION 語句,你能看到極大的性能改進。

  • 索引改善數據檢索的性能,但損害數據插入、刪除和更新的性能。如果你有一些表,它們收集數據且不經常被搜索,則在有必要之前不要索引它們。(索引可根據需要添加和刪除。)

  • LIKE 很慢。一般來說,最好是使用 FULLTEXT 而不是 LIKE 。但是 MYSQL FULLTEXT 對漢字並不友好,如果需要使用全文索引,建議使用搜索引擎 ES,可以參考我的另一篇博客: https://www.cnblogs.com/keatsCoder/p/11341835.html

  • 數據庫是不斷變化的實體。一組優化良好的表一會兒后可能就面目全非了。由於表的使用和內容的更改,理想的優化和配置也會改變。

  • 最重要的規則就是,每條規則在某些條件下都會被打破。

實際開發過程中,我們需要根據業務的需要,開啟慢查詢日誌,然後針對慢SQL,不斷地進行 EXPLAIN 與修改SQL和索引,以求達到 ref 級別,至少達到 range 級別。這就需要強大的內功支持而不是每次都通過百度來解決,希望閱讀此篇文章的你和我一起不斷修鍊。加油!

常用的函數

文本處理函數

日期和時間處理函數

數值處理函數

聚合函數

  • 雖然 MAX() 一般用來找出最大的數值或日期值,但MySQL允許將它用來返回任意列中的最大值,包括返迴文本列中的最大值。在用於文本數據時,如果數據按相應的列排序,則 MAX() 返回最後一行。MIN() 函數類似
  • MAX() 函數忽略列值為 NULL 的行。MIN() 函數類似,SUM() 也是

附錄

由於數中所附的附件內容(建表語句即數據插入語句)需要外網才能訪問,為了方便大家使用。這裏我已經下載下來附在了這篇博客裏面。如果不需要這些語句,可以直接通過網頁右邊的目錄跳躍到下一章進行瀏覽

建表語句

########################################
# MySQL Crash Course MYSQL必知必會建表語句
# http://www.forta.com/books/0672327120/
# 提供者博客園:后青春期的Keats 複製請註明出處
########################################


########################
# Create customers table
########################
CREATE TABLE customers
(
  cust_id      int       NOT NULL AUTO_INCREMENT,
  cust_name    char(50)  NOT NULL ,
  cust_address char(50)  NULL ,
  cust_city    char(50)  NULL ,
  cust_state   char(5)   NULL ,
  cust_zip     char(10)  NULL ,
  cust_country char(50)  NULL ,
  cust_contact char(50)  NULL ,
  cust_email   char(255) NULL ,
  PRIMARY KEY (cust_id)
) ENGINE=InnoDB;

#########################
# Create orderitems table
#########################
CREATE TABLE orderitems
(
  order_num  int          NOT NULL ,
  order_item int          NOT NULL ,
  prod_id    char(10)     NOT NULL ,
  quantity   int          NOT NULL ,
  item_price decimal(8,2) NOT NULL ,
  PRIMARY KEY (order_num, order_item)
) ENGINE=InnoDB;


#####################
# Create orders table
#####################
CREATE TABLE orders
(
  order_num  int      NOT NULL AUTO_INCREMENT,
  order_date datetime NOT NULL ,
  cust_id    int      NOT NULL ,
  PRIMARY KEY (order_num)
) ENGINE=InnoDB;

#######################
# Create products table
#######################
CREATE TABLE products
(
  prod_id    char(10)      NOT NULL,
  vend_id    int           NOT NULL ,
  prod_name  char(255)     NOT NULL ,
  prod_price decimal(8,2)  NOT NULL ,
  prod_desc  text          NULL ,
  PRIMARY KEY(prod_id)
) ENGINE=InnoDB;

######################
# Create vendors table
######################
CREATE TABLE vendors
(
  vend_id      int      NOT NULL AUTO_INCREMENT,
  vend_name    char(50) NOT NULL ,
  vend_address char(50) NULL ,
  vend_city    char(50) NULL ,
  vend_state   char(5)  NULL ,
  vend_zip     char(10) NULL ,
  vend_country char(50) NULL ,
  PRIMARY KEY (vend_id)
) ENGINE=InnoDB;

###########################
# Create productnotes table
###########################
CREATE TABLE productnotes
(
  note_id    int           NOT NULL AUTO_INCREMENT,
  prod_id    char(10)      NOT NULL,
  note_date datetime       NOT NULL,
  note_text  text          NULL ,
  PRIMARY KEY(note_id),
  FULLTEXT(note_text)
) ENGINE=MyISAM;


#####################
# Define foreign keys
#####################
ALTER TABLE orderitems ADD CONSTRAINT fk_orderitems_orders FOREIGN KEY (order_num) REFERENCES orders (order_num);
ALTER TABLE orderitems ADD CONSTRAINT fk_orderitems_products FOREIGN KEY (prod_id) REFERENCES products (prod_id);
ALTER TABLE orders ADD CONSTRAINT fk_orders_customers FOREIGN KEY (cust_id) REFERENCES customers (cust_id);
ALTER TABLE products ADD CONSTRAINT fk_products_vendors FOREIGN KEY (vend_id) REFERENCES vendors (vend_id);

數據語句

########################################
# MySQL Crash Course MYSQL必知必會數據語句
# http://www.forta.com/books/0672327120/
# 提供者博客園:后青春期的Keats 複製請註明出處
########################################


##########################
# Populate customers table
##########################
INSERT INTO customers(cust_id, cust_name, cust_address, cust_city, cust_state, cust_zip, cust_country, cust_contact, cust_email)
VALUES(10001, 'Coyote Inc.', '200 Maple Lane', 'Detroit', 'MI', '44444', 'USA', 'Y Lee', 'ylee@coyote.com');
INSERT INTO customers(cust_id, cust_name, cust_address, cust_city, cust_state, cust_zip, cust_country, cust_contact)
VALUES(10002, 'Mouse House', '333 Fromage Lane', 'Columbus', 'OH', '43333', 'USA', 'Jerry Mouse');
INSERT INTO customers(cust_id, cust_name, cust_address, cust_city, cust_state, cust_zip, cust_country, cust_contact, cust_email)
VALUES(10003, 'Wascals', '1 Sunny Place', 'Muncie', 'IN', '42222', 'USA', 'Jim Jones', 'rabbit@wascally.com');
INSERT INTO customers(cust_id, cust_name, cust_address, cust_city, cust_state, cust_zip, cust_country, cust_contact, cust_email)
VALUES(10004, 'Yosemite Place', '829 Riverside Drive', 'Phoenix', 'AZ', '88888', 'USA', 'Y Sam', 'sam@yosemite.com');
INSERT INTO customers(cust_id, cust_name, cust_address, cust_city, cust_state, cust_zip, cust_country, cust_contact)
VALUES(10005, 'E Fudd', '4545 53rd Street', 'Chicago', 'IL', '54545', 'USA', 'E Fudd');


########################
# Populate vendors table
########################
INSERT INTO vendors(vend_id, vend_name, vend_address, vend_city, vend_state, vend_zip, vend_country)
VALUES(1001,'Anvils R Us','123 Main Street','Southfield','MI','48075', 'USA');
INSERT INTO vendors(vend_id, vend_name, vend_address, vend_city, vend_state, vend_zip, vend_country)
VALUES(1002,'LT Supplies','500 Park Street','Anytown','OH','44333', 'USA');
INSERT INTO vendors(vend_id, vend_name, vend_address, vend_city, vend_state, vend_zip, vend_country)
VALUES(1003,'ACME','555 High Street','Los Angeles','CA','90046', 'USA');
INSERT INTO vendors(vend_id, vend_name, vend_address, vend_city, vend_state, vend_zip, vend_country)
VALUES(1004,'Furball Inc.','1000 5th Avenue','New York','NY','11111', 'USA');
INSERT INTO vendors(vend_id, vend_name, vend_address, vend_city, vend_state, vend_zip, vend_country)
VALUES(1005,'Jet Set','42 Galaxy Road','London', NULL,'N16 6PS', 'England');
INSERT INTO vendors(vend_id, vend_name, vend_address, vend_city, vend_state, vend_zip, vend_country)
VALUES(1006,'Jouets Et Ours','1 Rue Amusement','Paris', NULL,'45678', 'France');


#########################
# Populate products table
#########################
INSERT INTO products(prod_id, vend_id, prod_name, prod_price, prod_desc)
VALUES('ANV01', 1001, '.5 ton anvil', 5.99, '.5 ton anvil, black, complete with handy hook');
INSERT INTO products(prod_id, vend_id, prod_name, prod_price, prod_desc)
VALUES('ANV02', 1001, '1 ton anvil', 9.99, '1 ton anvil, black, complete with handy hook and carrying case');
INSERT INTO products(prod_id, vend_id, prod_name, prod_price, prod_desc)
VALUES('ANV03', 1001, '2 ton anvil', 14.99, '2 ton anvil, black, complete with handy hook and carrying case');
INSERT INTO products(prod_id, vend_id, prod_name, prod_price, prod_desc)
VALUES('OL1', 1002, 'Oil can', 8.99, 'Oil can, red');
INSERT INTO products(prod_id, vend_id, prod_name, prod_price, prod_desc)
VALUES('FU1', 1002, 'Fuses', 3.42, '1 dozen, extra long');
INSERT INTO products(prod_id, vend_id, prod_name, prod_price, prod_desc)
VALUES('SLING', 1003, 'Sling', 4.49, 'Sling, one size fits all');
INSERT INTO products(prod_id, vend_id, prod_name, prod_price, prod_desc)
VALUES('TNT1', 1003, 'TNT (1 stick)', 2.50, 'TNT, red, single stick');
INSERT INTO products(prod_id, vend_id, prod_name, prod_price, prod_desc)
VALUES('TNT2', 1003, 'TNT (5 sticks)', 10, 'TNT, red, pack of 10 sticks');
INSERT INTO products(prod_id, vend_id, prod_name, prod_price, prod_desc)
VALUES('FB', 1003, 'Bird seed', 10, 'Large bag (suitable for road runners)');
INSERT INTO products(prod_id, vend_id, prod_name, prod_price, prod_desc)
VALUES('FC', 1003, 'Carrots', 2.50, 'Carrots (rabbit hunting season only)');
INSERT INTO products(prod_id, vend_id, prod_name, prod_price, prod_desc)
VALUES('SAFE', 1003, 'Safe', 50, 'Safe with combination lock');
INSERT INTO products(prod_id, vend_id, prod_name, prod_price, prod_desc)
VALUES('DTNTR', 1003, 'Detonator', 13, 'Detonator (plunger powered), fuses not included');
INSERT INTO products(prod_id, vend_id, prod_name, prod_price, prod_desc)
VALUES('JP1000', 1005, 'JetPack 1000', 35, 'JetPack 1000, intended for single use');
INSERT INTO products(prod_id, vend_id, prod_name, prod_price, prod_desc)
VALUES('JP2000', 1005, 'JetPack 2000', 55, 'JetPack 2000, multi-use');



#######################
# Populate orders table
#######################
INSERT INTO orders(order_num, order_date, cust_id)
VALUES(20005, '2005-09-01', 10001);
INSERT INTO orders(order_num, order_date, cust_id)
VALUES(20006, '2005-09-12', 10003);
INSERT INTO orders(order_num, order_date, cust_id)
VALUES(20007, '2005-09-30', 10004);
INSERT INTO orders(order_num, order_date, cust_id)
VALUES(20008, '2005-10-03', 10005);
INSERT INTO orders(order_num, order_date, cust_id)
VALUES(20009, '2005-10-08', 10001);


###########################
# Populate orderitems table
###########################
INSERT INTO orderitems(order_num, order_item, prod_id, quantity, item_price)
VALUES(20005, 1, 'ANV01', 10, 5.99);
INSERT INTO orderitems(order_num, order_item, prod_id, quantity, item_price)
VALUES(20005, 2, 'ANV02', 3, 9.99);
INSERT INTO orderitems(order_num, order_item, prod_id, quantity, item_price)
VALUES(20005, 3, 'TNT2', 5, 10);
INSERT INTO orderitems(order_num, order_item, prod_id, quantity, item_price)
VALUES(20005, 4, 'FB', 1, 10);
INSERT INTO orderitems(order_num, order_item, prod_id, quantity, item_price)
VALUES(20006, 1, 'JP2000', 1, 55);
INSERT INTO orderitems(order_num, order_item, prod_id, quantity, item_price)
VALUES(20007, 1, 'TNT2', 100, 10);
INSERT INTO orderitems(order_num, order_item, prod_id, quantity, item_price)
VALUES(20008, 1, 'FC', 50, 2.50);
INSERT INTO orderitems(order_num, order_item, prod_id, quantity, item_price)
VALUES(20009, 1, 'FB', 1, 10);
INSERT INTO orderitems(order_num, order_item, prod_id, quantity, item_price)
VALUES(20009, 2, 'OL1', 1, 8.99);
INSERT INTO orderitems(order_num, order_item, prod_id, quantity, item_price)
VALUES(20009, 3, 'SLING', 1, 4.49);
INSERT INTO orderitems(order_num, order_item, prod_id, quantity, item_price)
VALUES(20009, 4, 'ANV03', 1, 14.99);

#############################
# Populate productnotes table
#############################
INSERT INTO productnotes(note_id, prod_id, note_date, note_text)
VALUES(101, 'TNT2', '2005-08-17',
'Customer complaint:
Sticks not individually wrapped, too easy to mistakenly detonate all at once.
Recommend individual wrapping.'
);
INSERT INTO productnotes(note_id, prod_id, note_date, note_text)
VALUES(102, 'OL1', '2005-08-18',
'Can shipped full, refills not available.
Need to order new can if refill needed.'
);
INSERT INTO productnotes(note_id, prod_id, note_date, note_text)
VALUES(103, 'SAFE', '2005-08-18',
'Safe is combination locked, combination not provided with safe.
This is rarely a problem as safes are typically blown up or dropped by customers.'
);
INSERT INTO productnotes(note_id, prod_id, note_date, note_text)
VALUES(104, 'FC', '2005-08-19',
'Quantity varies, sold by the sack load.
All guaranteed to be bright and orange, and suitable for use as rabbit bait.'
);
INSERT INTO productnotes(note_id, prod_id, note_date, note_text)
VALUES(105, 'TNT2', '2005-08-20',
'Included fuses are short and have been known to detonate too quickly for some customers.
Longer fuses are available (item FU1) and should be recommended.'
);
INSERT INTO productnotes(note_id, prod_id, note_date, note_text)
VALUES(106, 'TNT2', '2005-08-22',
'Matches not included, recommend purchase of matches or detonator (item DTNTR).'
);
INSERT INTO productnotes(note_id, prod_id, note_date, note_text)
VALUES(107, 'SAFE', '2005-08-23',
'Please note that no returns will be accepted if safe opened using explosives.'
);
INSERT INTO productnotes(note_id, prod_id, note_date, note_text)
VALUES(108, 'ANV01', '2005-08-25',
'Multiple customer returns, anvils failing to drop fast enough or falling backwards on purchaser. Recommend that customer considers using heavier anvils.'
);
INSERT INTO productnotes(note_id, prod_id, note_date, note_text)
VALUES(109, 'ANV03', '2005-09-01',
'Item is extremely heavy. Designed for dropping, not recommended for use with slings, ropes, pulleys, or tightropes.'
);
INSERT INTO productnotes(note_id, prod_id, note_date, note_text)
VALUES(110, 'FC', '2005-09-01',
'Customer complaint: rabbit has been able to detect trap, food apparently less effective now.'
);
INSERT INTO productnotes(note_id, prod_id, note_date, note_text)
VALUES(111, 'SLING', '2005-09-02',
'Shipped unassembled, requires common tools (including oversized hammer).'
);
INSERT INTO productnotes(note_id, prod_id, note_date, note_text)
VALUES(112, 'SAFE', '2005-09-02',
'Customer complaint:
Circular hole in safe floor can apparently be easily cut with handsaw.'
);
INSERT INTO productnotes(note_id, prod_id, note_date, note_text)
VALUES(113, 'ANV01', '2005-09-05',
'Customer complaint:
Not heavy enough to generate flying stars around head of victim. If being purchased for dropping, recommend ANV02 or ANV03 instead.'
);
INSERT INTO productnotes(note_id, prod_id, note_date, note_text)
VALUES(114, 'SAFE', '2005-09-07',
'Call from individual trapped in safe plummeting to the ground, suggests an escape hatch be added.
Comment forwarded to vendor.'
);

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

※自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

比亞迪計畫在廣州增城設廠 組裝K9電動大巴

日前,有媒體報導稱,比亞迪高層與廣州市長洽談在廣州增城投資建廠事宜,屆時增城將雲集廣汽本田、北汽和比亞迪三大汽車品牌,成為廣州汽車產業最為集中的區域。

比亞迪向媒體確認,其總裁王傳福及高級副總裁吳經勝此前一段時間的確曾赴廣州,與廣州市市長陳建華洽談在廣州投資建廠事宜。此項目與比亞迪在陝西、雲南等地的模式類似,依舊是為其公交電動化做準備。目前比亞迪仍在積極籌備還有天津、昆明、武漢等地的建廠項目,也是其在新能源推廣的地方保護背景下的無奈之舉。

比亞迪的電動大巴在國內具有較強的優勢,目前已經出口到荷蘭、巴西、美國等地,目前在國內由於地方保護,只有在當地合資設廠,是打入當地市場的一個有效市場。而電動大巴的組裝線投資不大,也是比亞迪較能接受的方式。

為了推動電動車的發佈,比亞迪率先推出了“零元購車、零成本、零排放”城市公交電動化解決方案,為加速公交電動化進程開闢一條現實可行的道路。在深圳、西安、寶雞、韶關、荷蘭、新加坡、美國、丹麥、德國、英國倫敦等地成功實現電動車的規模化、商業化運營。

比亞迪總部所在廣州運行的電動公交大巴超過千輛,成為電動大巴運行最多的城市。而百公里之外的廣州,目前擁有超過1萬輛的公交大巴,其中新能源車為2000輛,少數為電動大巴,主要廣汽客車品牌;計程車約有3萬輛,電動車計程車幾乎為零,這給比亞迪很大的期望值。

比亞迪也在做廣州的工作。在2012廣州國際馬拉松賽上,作為本屆馬拉松比賽獨家汽車贊助商,比亞迪旗下的城市多功能SUV車型S6、T動力智慧新典範G6以及純電動車e6成為賽事的指定用車。

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

※自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

Jetpack Compse 實戰 —— 全新的開發體驗

公眾號回復 Compose 獲取安裝包

項目地址:

經過前段時間的 Android Dev Summit ,相信你已經大概了解了 Jetpack Compose 。如果你還沒有聽說過,可以閱讀這篇文章 。總而言之,Compose 是一個 顛覆性聲明式 UI 框架 ,它的口號就是 消滅 xml 文件 !

儘管 Jetpack Compose 還只是預覽版,API 可能發生變化,缺乏足夠的控件支持,甚至不是那麼穩定,但這阻止不了我這顆好奇的心。我在第一時間就上手擼了一款 Compose 版本 Wanandroid 應用,功能也比較簡單,僅僅包括首頁,廣告和最新項目,類似於 Android 原生頁面的 Viewpager + TabLayout 。下面的 gif 展示了應用的基本頁面:

可以看出來頁面並不是那麼流暢,View 的復用應該是個問題,甚至我也沒發現應該怎麼做下拉刷新。那麼,Compose 給我們帶來了什麼呢?在解答這個問題之前,我想先來說說 Android 應用架構問題。

荒蕪年代 —— MVC

在我剛入行的時候,可以說是 Android 開發的黃金時代,也可以說是開發者的荒蕪時代。一方面,毫不誇張的說,基本會寫 xml 都能謀得一份工作。另一方面,對於開發者來說,遠遠沒有現在的規範的開發架構。沒記錯的話,我當年的主力開發框架是 xUtils 2 ,一個類庫大包干,從 布局和 ID 綁定,網絡請求,到圖片展示,ORM 操作,一應俱全。當時的 布局和 ID 綁定還是運行時反射,而不是編譯期註解。很長一段時間以來,Android 連一個官方的網絡庫都沒有。

在架構方面,很多人都是一個 Activity 擼到死,我真的見過上千行zouguolai的 MainActivity 。並且覺得這就是 MVC 架構,實體類 Entity 就是 Model 層,Activity/Fragment 就是 Controller 層,布局文件就是 View 層。

但這當真是 MVC 嗎?其實並不是。不管是 MVCMVP,還是 MVVM,都應該遵循一個最起碼的原則,表現層和業務層分離 ,也就是 Android 官網給出的 中強調的 分離關注點Activity/Fragment 既要承擔視圖層的任務,展示和更新 UI,又要處理業務層邏輯,獲取數據等。這並不符合架構設計的基本原則。

正確的 MVC 模式中,Model 層不僅包含實體類 Entity,更重要的作用是處理業務邏輯。View 層負責處理視圖邏輯。而 Controller 就是 Model 和 View 之間的橋樑。橋怎麼建,其實並沒有標準,根據你自己的需求就可以了。

引用一張阮一峰老師的圖,大致是這麼個意思,但是也不一定就完全都是單向依賴。

從荒蕪時代走過來,MVC 總算有點分層的味道在裏面了,分離了視圖層和業務層。但是 View 層和 Model 層的依賴關係,造成代碼耦合,終將導致 Activity 日益臃腫。那麼有沒有辦法將 View 層和 Model 層徹底分離,做到視圖層和模型層完全分離呢? MVP 就應運而生了。

青銅年代 —— MVP

依舊是阮一峰老師的圖片:

相較於 MVC ,MVP 用 Presenter 層 代替了 Controller 層 ,且 View 層Model 層 完全分離,依靠 Presenter 進行通信 。

想象一個獲取用戶信息的場景。IView 接口中定義了一系列視圖層接口 ,View 層(Activity)實現 IView 接口中相應視圖邏輯。 View 層通過持有的 Presenter 處理業務邏輯,即請求用戶信息。一般情況下,Presenter 也不直接處理業務邏輯,而是通過 Model 層,例如數據倉庫 Repository, 來獲取數據,避免 Presenter 重蹈覆轍,日漸臃腫。同時,Presenter 層也是持有 VIew 的,獲取用戶信息之後再轉發給 View 。

總結一下,MVP 中 View 和 Model 完全解耦,通過 Presenter 通信。View 和 Presenter 共同處理視圖層邏輯,Model 層負責業務邏輯。

在 Github 上 Android 官方的架構示例 中 MVP 作為主分支堅挺了很久。我最初也是根據這個官方示例改造了自己的 MVP 架構,並且使用了很長時間。但是 MVP 作為一款面向接口編程的架構,隨着業務的複雜程度不斷加大,有種遍地都是接口的既視感,實在顯得有點繁瑣。

另外一點,Presenter 的職責邊界不夠清晰,它除了承擔調用 Model 層獲取業務邏輯之外,還要控制 View 層處理 UI。用下面一段代碼錶示一下:

class LoginPresenter(private val mView: LoginContract.View) : LoginContract.Presenter {

    ......

    override fun login(userName: String, passWord: String) {
        CoroutineScope(Dispatchers.Main).launch {
            val result = WanRetrofitClient.service.login(userName, passWord).await()
            with(result) {
                if (errorCode == -1)  mView.loginError(errorMsg) else mView.login(data)
            }
        }
    }
}

一旦 View 層發生任何變化,Presenter 層也要做出相應改動。雖然 View 和 Model 之間解耦了,但是 View 和 Presenter 卻耦合了。理想情況下,Presenter 層應該僅負責數據的獲取,View 層自動觀察數據的變化。於是,MVVM 來了。

黃金時代 —— MVVM

Google 官圖鎮樓 。

MVP 風光早已不在, Android 官方的架構示例 的主分支已經切換到 MVVM 。在 Android 的 MVVM 架構中,ViewModel 是重中之重,它一方面通過數據倉庫 Repository 獲取數據,另一方面根據獲取的數據更新 View 層的 Activity/Fragment。等等,這句話怎麼聽着這麼耳熟,Presenter 不也是幹了這些事嗎?的確,它們乾的事情都差不多,但是實現上完全不一樣。

以我的開源項目 中的 LoginViewModel 為例:

class LoginViewModel(val repository: LoginRepository) : BaseViewModel() {

    private val _uiState = MutableLiveData<LoginUiModel>()
    val uiState: LiveData<LoginUiModel>
        get() = _uiState


    fun loginDataChanged(userName: String, passWord: String) {
        emitUiState(enableLoginButton = isInputValid(userName, passWord))
    }

    // ViewModel 只處理視圖邏輯,數據倉庫 Repository 負責業務邏輯
    fun login(userName: String, passWord: String) {
        viewModelScope.launch(Dispatchers.Default) {
            if (userName.isBlank() || passWord.isBlank()) return@launch

            withContext(Dispatchers.Main) { showLoading() }

            val result = repository.login(userName, passWord)

            withContext(Dispatchers.Main) {
                if (result is Result.Success) {
                    emitUiState(showSuccess = result.data,enableLoginButton = true)
                } else if (result is Result.Error) {
                    emitUiState(showError = result.exception.message,enableLoginButton = true)
                }
            }
        }
    }

    private fun showLoading() {
        emitUiState(true)
    }

    private fun emitUiState(
            showProgress: Boolean = false,
            showError: String? = null,
            showSuccess: User? = null,
            enableLoginButton: Boolean = false,
            needLogin: Boolean = false
    ) {
        val uiModel = LoginUiModel(showProgress, showError, showSuccess, enableLoginButton,needLogin)
        _uiState.value = uiModel
    }

    data class LoginUiModel(
            val showProgress: Boolean,
            val showError: String?,
            val showSuccess: User?,
            val enableLoginButton: Boolean,
            val needLogin:Boolean
    )
}

可以看到,ViewModel 中是沒有 View 的引用的,View 通過可觀察的 LIveData 來觀察數據變化,基於觀察者模式做到和 ViewModel 完全解耦。

數據驅動視圖 ,這是 Jetpack MVVM 推崇的一個重要原則。其基本數據流如下所示 :

  • 數據層 Repository 負責從不同數據源獲取和整合數據,基本負責所有的業務邏輯
  • ViewModel 持有 Repository,獲取數據並驅動 View 層更新
  • View 持有 ViewModel,觀察 LiveData 攜帶的數據,數據驅動 UI

曾經和一些開發者討論過這樣一個問題,** 不使用 DataBinding 還算是 MVVM 嗎 ?** 我認為 MVVM 的核心從來不在於 DataBinding 。DataBinding 只是可以幫助我們將 數據驅動視圖 做到極致,順便還可以雙向綁定。

要說到對 Jetpack MVVM 中最不滿意的一塊,那非 DataBinding 莫屬了。在我狹隘的認為 DataBinding 就是一個在 xml 裏面寫邏輯代碼的反人類的庫時,我是堅決反對在任何項目中引入它的。固執己見的時候就容易走進誤區,在閱讀 KunminX 的 之後,正如這篇文章名字一樣,真香。

香的確是香,一切能讓我早下班的都是好東西。在我的某次提交日誌上,我寫下了 消滅 Adapter 幾個字,那時我剛用 DataBinding 消滅了大部分 RecyclerView 的 Adapter 。可是在提交之後,我的良心惴惴不安,我追究還是在 xml 文件里寫邏輯代碼了,難道這真的不反人類嗎?

未來可期 —— Jetpack Compose

現在你應該可以理解我對 Jetpack Compose 的執念了。拋去其他特性,在我看來,它完美的解決了 數據驅動視圖 的問題,我再也不需要使用 DataBinding 了。

簡單代碼展示一下 Compose 的用法。下面的代碼描繪的是首頁 Tab 下的文章列表。

@Composable
fun MainTab(articleUiModel: ArticleViewModel.ArticleUiModel?) {

    VerticalScroller {
        FlexColumn {
            inflexible {
                HeightSpacer(height = 16.dp)
            }
            flexible(1f) {
                articleUiModel?.showSuccess?.datas?.forEach {
                    ArticleItem(article = it)
                }

                articleUiModel?.showError?.let { toast(App.CONTEXT, it) }
wenjian
                articleUiModel?.showLoading?.let { Progress() }
            }
        }
    }
}

這種寫法叫做 聲明式編程 ,會用 Flutter 的同學應該很熟悉。方法參數 ArticleUiModel 就是數據實體類,直接根據數據 ArticleUiModel 構建 UI 。說的大白話一點,就是給你長方形的長和寬了,讓你畫個長方形出來。最後加上 @Compose 註解,就是一個可用的 UI 組件了。仔細看代碼,裏面還用了兩個 UI 組件 ,ArticleItemProgress ,代碼就不貼出來了。分別是文章列表的 item 項目 和加載進度條。

那麼,數據如何更新呢?最簡單的方式是使用 @Model 註解。

@Model
data class ArticleUiModel(){
  ......
}

對,就是這麼簡單。@Model 註解會自動把你的數據類變成可觀察對象,只要 ArticleUIModel 發生變化,UI 就會自動更新。

但是我在實際開發中結合 LiveData 使用時,好像表現的不是那麼正常。後來在 Medium 上無意中看到了解決方案,針對 LiveData 做了特殊處理 :

// general purpose observe effect. this will likely be provided by LiveData. effect API for
// compose will also simplify soon.
fun <T> observe(data: LiveData<T>) = effectOf<T?> {
    val result = +state<T?> { data.value }
    val observer = +memo { Observer<T> { result.value = it } }

    +onCommit(data) {
        data.observeForever(observer)
        onDispose { data.removeObserver(observer) }
    }

    result.value
}wenjian

在 Activity/Fragment 中觀測 LiveData 即可:

class MainActivity : BaseVMActivity<ArticleViewModel>() {

    override fun initView() {
        setContent {
           +observe(mViewModel.uiState)
            WanandroidApp(mViewModel)
        }
    }

    override fun initData() {
       mViewModel.getHomeArticleList()
    }
}

這樣 View 層就可以自動觀察 LiveData 所包含的值了。

沒有 xml,沒有 DataBinding,一切看起來稱心如意多了。但就是 UI 體驗有那麼一點糟心,你可以在公眾號後台回復 Compose 安裝體驗一下。由於還是早期的預覽版,這也是可以理解的。我相信,等到發布 Release 版本的時候,一定足以完全代替原聲的 View 體系。

本文並沒有詳細介紹 Jetpack Compose 的詳細使用過程和其他特性,更多信息我推薦下面兩篇文章:

最後

正如 Android 官網 Jetpack 介紹頁所說,Jetpack 可以幫助開發者更輕鬆的編寫優質應用。的確,隨着應用架構的規範,我們只需要把精力放在需要的代碼上,加速開發,消除樣板代碼,減少崩潰和內存泄露,構建高質量的強大應用。我想不出來有任何理由不使用 Jetpack 來構建你的應用。而 Compose 必將稱為 Jetpack 中極其重要的一塊拼圖。

Jetpack Compse ,未來可期 !

添加我的微信,加入技術交流群。

公眾號後台回復 “compose”, 獲取最新安裝包。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

※自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

養猛獸當寵物歪風 巴政客名流炫耀引效尤

摘錄自2020年2月10日公視報導

在巴基斯坦,擁有野生猛獸被視為身分地位的象徵。政客名流習慣炫耀自己豢養的獅子老虎來凸顯優越感。近年來一般百姓也掀起這股把大貓當寵物的風潮,通常把牠們養在屋頂鐵籠子裡,不但對野生動物是種折磨,也違反法規。

在當地購買幼獅的價格,一隻折合台幣超過13萬,漢姆斯跟朋友砸下所有積蓄合買,就養在頂樓,當成貓狗一樣的逗弄寵愛。平常房屋管理員負責照顧這些獅子,雖然看起來很鎮定,面對猛獸還是提防三分。

追根究底,野生動物根本就不該非法進口拿來當寵物,但相關法規約束力薄弱,公權力對亂象也都視而不見。這些野生獅子老虎,多數來自非洲或西伯利亞,都是持有原產國的證明許口而進口。顯示不肖業者早有門路管道規避灰暗不明的法規限制,還大剌剌宣稱只要48小時,就可以把動物送到全國各地的買家手上。

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

※自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

武漢肺炎源頭 WHO:與菊頭蝠有關聯

摘錄自2020年2月13日公視報導

世界衛生組織WHO發表的疫情報告,指出越來越多證據顯示,武漢肺炎的病毒跟蝙蝠亞種菊頭蝠有關。另外,中國專家近來表示疫情可能在4月結束,WHO也反駁現在預測還太早。

WHO新報告指出,武漢肺炎病毒來自蝙蝠的可能性越來越高。WHO食安與人畜共同傳染病部門彼得表示:「過去我們在蝙蝠族群中發現非常相似的病毒,所以我們認為病毒一開始也是來自蝙蝠。」

WHO表示,武漢肺炎的新型冠狀病毒,跟蝙蝠亞種菊頭蝠有關,牠們常見於中國南部,廣泛分布在亞洲、非洲、歐洲。不過病毒的傳播途徑仍不清楚,可能是某種中間宿主動物,傳染給人類。

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

※自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

比亞迪「元」汽油版、插電版 Q1同步上市

據報導,比亞迪全新小型SUV——「元」將於農曆新年後正式上市,與「宋」不同的是,比亞迪「元」汽油版、插電版將同步上市。

外觀方面,比亞迪「元」採用了時尚的雙色車身設計,再配合小巧造型,整體顯得很精緻。細節方面,新車延續了比亞迪最新家族式前臉,設計十分個性。

尺寸方面,新車長寬高分別為4320/1765/1650mm,軸距為2520mm。尾部方面,該車採用了「小書包」的外掛式備胎,這也為後備箱節省了更多的儲物空間。

內飾方面,比亞迪「元」採用了全黑內飾設計,同時各處點綴有銀色裝飾,整體內飾設計和「宋」一樣顯得很精緻,且相比之前的車型有了大幅提升。細節方面,新車配備了三幅多功能方向盤,空調出風口採用了十字造型設計,很有未來感。新車同時配備了中控多媒體螢幕、一鍵啟動、自動空調等等。

動力方面,據之前消息來看比亞迪「元」燃油版將提供1.5L自然吸氣發動機和1.5T渦輪增壓發動機可選,最大功率分別為109Ps和154Ps。此外,插電式混動版則會搭載由1.5L發動機與兩台電動機組成的混動系統,純電模式下的最大續航里程為70公里,破百時間僅需4.9秒。

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

※自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

重返英國有望 河狸不只會築壩治水 還能抗污染、救動物

環境資訊中心綜合外電;姜唯 編譯;林大利 審校

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

※自行創業 缺乏曝光? 下一步”網站設計“幫您第一時間規劃公司的門面形象

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

※試算大陸海運運費!

Google在Nature上發表的關於量子計算的最新進展的論文(Quantum supremacy using a programmable superconducting processor 譯)— 附論文

Google 的研究人員於2019年10月23號發表在Nature自然》《科學《細胞》雜誌都是國際頂級期刊,貌似在上面發文3篇左右,就可以評院士了)上,關於量子計算方面(基於 Sycamore芯片)的具有里程碑式進展的論文,受到國內外同行及媒體的廣泛關注,包括中科大量子科學家 — 潘建偉及其團隊。特朗普的女兒Ivanka Trump(左一)也發twitter表示祝賀,如下圖:

Even as Ivanka praised the parties involved in this new quantum computing achievement, a number of social media users appeared critical of her assessment of the Trump administration’s role in this endeavour.

IBM表示不服,Google不care。下面讓我們逐字逐句來看他們的論文吧,對於爭論的事情,自己下功夫搞清楚。

 

Quantum supremacy using a programmable superconducting processor

基於可編程的超導處理器實現的量子霸權 

 相關資源:https://doi.org/10.1038/s41586-019-1666-5

 接收日期:2019年7月20日

 核准日期:2019年9月20日

 在線發布:2019年10月23日

 

Abstract

引言

量子計算機吹牛逼說,對於特定的計算任務,基於量子處理器的計算機,其速度相較於經典處理器呈指數級增長。根本的挑戰在於構建一個能夠在海量的計算空間上運行量子算法的高保真處理器。我們的報告是關於,一個基於53量子比特實現的可編程的超導量子芯片,在253(約1016)的計算狀態空間創建了一個量子態的故事。

我們用經典模擬驗證了重複實驗測量結果的採樣概率分佈。我們的Sycamore處理器採樣一個量子電路100萬次,大約花了200秒——我們的基準測試表明,同樣的任務最先進的超級計算機大約需要花費10000年。相較於所有已知的經典算法,對於這個特定的計算任務,用實驗實現的量子優越性在速度方面的顯著提升,預示着一個期待已久的計算範式。

 

Main

正文

早在20世紀80年代,有鑒於經典計算機在模擬大型量子系統時的高昂成本,理查德·費曼(Richard Feynman)就提出量子計算機將是解決物理、化學問題的有效工具。將費曼的設想付諸實現,構成了重大的實驗和理論挑戰。

首先,一個能夠在足夠巨大的計算空間(hibert)進行計算並且以低錯誤率提供量子加速的量子系統,工程上是否可行?其次,我們能否構建一個對於經典計算機很難但是對於量子計算機比較簡單的問題?通過在我們的超導量子處理器上運算這樣的一個基準任務,我們解決了這2個問題。我們的實驗實現了量子優越性,這是全面實現量子計算征程的里程碑。

在實現里程碑的過程中,我們證明了量子加速在現實世界是可達到的,也沒有被任何未知的物理定律所排除。量子優越性也預示着嘈雜的中型量子(NISQ,筆者:嘈雜意味着不穩定,噪音嚴重)技術時代的到來。我們論證的基準任務,已經立即用於生成可認證的隨機數(S. Aaronson,手稿正則準備中);這個新的計算能力的最初應用領域可能包括優化機器學習、材料科學和化學。然而,實現完全意義的量子計算(例如,Shor的分解算法)仍有待於技術的飛越以製造支持容錯邏輯的量子比特。

為了達成量子優越性,我們取得了一系列的技術進步,從而為糾錯鋪平了道路。我們研製了可以同時執行跨兩個維度量子矩陣的快速高保真門。我們使用了強大的新工具:交叉熵基準,在組件和系統層面對處理器進行了校準和基準測試。最後,為了精確預測整個系統的性能,我們使用了組件級的保真度,從而進一步證明當擴展到大型系統時量子信息的行為符合預期。

 

A suitable computational task

合適的計算任務

為了證明量子優越性,我們在採樣量子電路的偽隨機輸出任務中,比較我們的量子處理器和最新的經典計算機。隨機電路是基準測試的一個合適選擇,因為它門不具有結構,因此可以有限地保證計算硬度。我們設計的電路通過重複應用單量子和雙量子邏輯運算實現了一組量子的糾纏。採樣量子電路的輸出生成了一串比特串,例如{0000101, 1011100, …}。由於量子干擾的存在,比特串的概率分佈類似於在激光散射中的光干擾產生的強模型的斑點,因此有些比特串比其它的更容易出現。隨着量子比特的數量(寬度)和門循環數量(深度)的增加,概率分佈之經典計算的難度呈指數級增加。

我們使用稱為交叉熵基準測試的方法來驗證量子處理器是否正常工作,該方法將通過比較實驗觀察的每個比特串的頻率與通過經典計算機的模擬計算得出對應的理想概率。對於給定的電路,我們收集測得的比特串{xi}並且計算線性交叉熵基準的保真度(另請參見),這是我們測得的比特串的模擬概率的平均值:

FXEBY= 2n<P(xi)>i  ‐ 1

其中n是量子比特的總數,P(xi) 是為理想的量子電路計算的位串 xi 的概率,並且平均值超過了觀察到的比特串。直觀地講,FXEB和我們採樣高概率的比特串的頻率相關。當量子電路沒有錯誤的時候,其概率分佈呈指數分佈 (請參見),從這個分佈採樣將使得FXEB = 1。另一方面,從均勻分佈採樣將得到:P(xi)i = 1/2,FXEB  = 1。FXEB的值介於0和1之間,表示電路運行時沒有錯誤發生的概率。概率(P(xi) )必須從經典模擬量子電路得到,因此在至高無上的量子優越性上面計算FXEB十分棘手。然而,通過某些簡化的電路,我們可以估計出在寬和深量子電路上滿載運行的處理器的定量保真度。

 我們的目標是通過足夠寬和深的電路實現足夠高的FXEB,這樣經典計算的成本將高的難以承受。這是一個艱巨的任務,因為我們的邏輯門並不完美,我們打算構造的量子態對錯誤也很敏感。在算法運行過程中,單個比特或相位的翻轉將徹底重構斑點圖案並且導致保真度逼近0(請參見)。因此,為了宣稱量子的優越性,我們需要一個能夠以非常低的錯誤率運行程序的量子處理器。

 

Building a high-fidelity processor

構建高保真處理器

我們設計了一個名為“Sycamore”的量子處理器,它由54個特蘭蒙量子比特的二維陣列組成,其中每個量子位可調耦合到一個矩形格子的四個最近的相鄰接點。選擇這個連接是為了與使用表層代碼的糾錯向前兼容。這個設備的一項關鍵性系統進步是它實現了單量子比特和雙量子比特運算的高保真度,不單單是孤立的,而且可以在許多量子比特上同時進行門運算和現實計算。我們接下來討論重點,也請參見。

在一個超導量子比特里,傳導电子會凝聚成宏觀量子態,這樣電流和電壓會机械地呈現出量子態。我們的處理器使用特蘭蒙量子比特,可以將其視為擁有5-7 G赫茲主頻的非線性超導諧振器。其量子比特被編碼為諧振電路的兩個最低量子本徵態。每個特蘭蒙都有兩個控制器:一個微波驅動器來激發量子比特,以及一個磁通量控制器來調製頻率。每個量子比特被連接到用於讀出量子比特狀態的線性諧振器。如圖1所示,每個量子比特同時通過一個新的可調耦合器連接到其相鄰的量子比特。我們的耦合器設計允許我們快速將量子比特—量子比特耦合從完全關停調整到40 M赫茲。1個量子比特無法正常運轉,所以這個設備用了53個量子比特和86個耦合器。

           圖.1 : Sycamore 處理器

為了金屬化和約瑟夫森連接,處理器用鋁製造,並使用銦製造兩個硅晶片之間的凸點。芯片用引線粘合到超導電路板上,並在稀釋冰箱中冷卻至20 mK以下,以將環境熱能降低到大大低於量子比特能。處理器通過濾波器和衰減器連接到處於室溫的电子設備,該設備可合成控制信號。使用頻率復用技術可以同時讀取所有量子比特的狀態。我們用兩級低溫放大器來增強信號,該信號被数字化(在1 G赫茲頻率時為8比特)並在室溫下通過数字化實現解復用。為了完全控制量子處理器,我們總共設計了277個數模轉換器(在1G赫茲頻率時為14比特)

 我們通過驅動25納秒的微波脈衝來執行單量子比特門,該微波脈衝會以量子頻率共振,同時關閉量子比特-量子比特耦合。脈衝經過整形,從而最大程度地避免了過渡到更高的特蘭蒙狀態。由於兩級系統缺陷,門的性能會隨頻率產生很大的變化,雜散微波模式會與控制線和讀出諧振器相耦合,量殘餘的雜散耦合於量子比特、磁通噪聲和脈衝失真。有鑒於此,我們優化了單量子比特操作頻率以減免這些錯誤機制。

我們使用上述交叉熵基準測試協議對單量子比特門的性能進行基準測試,降低到單量子比特級別(n = 1),以測量在單量子比特門期間發生錯誤的概率。在每個量子比特上,我們應用數量可變的m個隨機選擇的門,並在許多序列上測量FXEB的平均值;隨着m的增加,誤差會累積、FXEB的平均值會下降。我們用[1 − e1 /(1 − 1 / D2)] m對該衰減建模,其中e1是Pauli誤差概率。在這種情況下,狀態(希爾伯特)的空間量綱,D = 2n,等於2,它校正了誤差與理想態部分重疊的去極化模型。該過程類似於更典型的隨機基準測試,但支持非Clifford門的集合,並且可以將消退相干誤差與相干控制誤差區分開。然後,我們重複了所有量子比特同時執行單量子比特門的實驗(圖2),而錯誤率僅僅表現出微小的增長,表明我們設備的微波干擾率很低。

         圖.2 : 全系統的 Pauli 和 測量錯誤

我們通過持續打開20 M赫茲耦合12 納秒,並使相鄰的量子位共振來執行類似iSWAP的兩個量子比特糾纏門,從而允許量子比特可以交換激勵。在此期間,量子比特還經歷了受控相位(CZ)的交互作用,該交互作用來自於更高級別的特蘭蒙。優化每對量子比特的兩個量子比特門限頻率軌跡,是為了減少在優化單量子比特工作頻率時所要考慮的相同錯誤機制。

為了表徵和量化兩個量子比特門,我們運行了m個周期的兩個量子比特電路,每個周期在每個雙量子比特上都包含一個隨機選擇的單量子比特門,緊跟着固定的兩個量子比特門。通過使用FXEB作為成本函數,我們學習了兩個量子單位的參數(例如iSWAP和CZ交互的數量)。經過這次優化,我們從值為 m 的FXEB的衰變中提取每周期錯誤e2c,並減去兩個單量子比特的錯誤e1來分離出兩個量子比特錯誤e2。我們發現e2的平均值為0.36%。 另外,我們在為整個矩陣同步運行雙量子比特電路的同時重複執行相同的過程。在為諸如,色散漂移和串擾等,考慮影響而更新單一參數后,我們發現e2的平均值為0.62%。

對於整個實驗,我們在同步操作期間用兩個量子比特元測量每一對,而不是所有對的標準門,生成量子電路。典型的兩個量子比特門是一個全iSWAP,並且擁有1/6的全CZ。絕不使用單獨校準的門來限制演示的通用性。例如,1個量子比特門和任意給定對中的兩個唯一的量子比特門可以組成可控NOT(CNOT)門。高保真“教科書似的門”,例如CZ或iSWAP ,的製作正在緊鑼密鼓地進行。

最後,我們通過使用標準色散測量對量子比特讀數進行了基準測試。在0和1狀態下的平均測量誤差如圖2a所示。
我們還通過讓每個量子比特隨機的處於0或1的狀態,然後測量所有量子比特以獲得正確結果的概率,來測量同時運行所有量子比特時的錯誤。我們發現,同時讀出僅僅會導致每個量子比特測量誤差的適度增加。

找到了單個門的錯誤率和讀數后,我們可以將量子電路的保真度建模為所有門和測量的0錯誤操作概率的乘積。我們最大的隨機量子電路有53個量子比特,1113個單量子比特門,430個雙量子比特門,每個量子比特一個亮度,我們估計其總保真度為0.2%。由於FXEB的不確定度為1 /Ns-√1/ Ns(其中Ns是樣本數),因此這個保真度應該可以通過數百萬次的測量來分辨。我們的模型推測,糾纏越來越大的系統不會引入超出我們在單比特和兩比特級別上測量的誤差之外的其他錯誤源。 在下一節中,我們將了解該假設的成立情況。

 

Fidelity estimation in the supremacy regime

優越性的逼真度估算

我們的偽隨機量子電路生成器的門序列如 圖3 所示。此算法的一個周期由應用於所有量子的單量子(從{√X, √Y, √Z}隨機選擇),緊跟着的成對的量子比特上的兩個量子比特門組成。組成“優越性電路”的門序列旨在最小化為創造一個高糾纏態的電路深度,而這正是計算複雜度和經典硬度所需。

                   圖.3 : 量子優越性電路的控制操作

儘管我們無法在至高無上的體系中計算FXEB,但是我們可以通過降低電路的複雜度的三個變體來評估它。在“貼片電路”中,我們移除掉了兩個量子比特門的一部分(佔兩個量子比特門總數的一小部分),將電路分割成兩個空間上隔離的,沒有相互作用的量子比特補丁。然後我們用可以輕鬆計算出保真度的補丁的乘積作為總的保真度。在“消除電路”中,我們沿切片僅去除了最初的兩個量子比特門的一小部分,允許補丁之間的糾纏,這在維持了仿真可行性的同時更緊密地模擬了整個實驗。最終,我們也可以運行同我們的優越性電路有着相同門數的全“驗證電路”,但卻與在傳統上容易模擬的多的兩电子門序列有着不同的模式(也請參見)。比較這些三個變體讓我們能夠在接近優越性制度的過程中追蹤系統保真度。

我們首先檢查補丁版本和刪節版本的驗證電路是否能與多達53量子比特的完整驗證電路產生相同的保真度,如圖4所示。每個數據點,我們通常在10個電路實例中採集 Ns = 5 × 106的總樣本,每個實例的區別僅在於在每個周期中單個量子門的選擇不同。我們也显示FXEB的預測值該值是通過將單量子和雙量子比特門的0錯誤率和測量值相乘而得到的(也請參見)儘管在計算複雜度和糾纏存在巨大差異,這個預測值、補丁及消除的保真度都對應的全電路的保真度吻合的很好。這讓我們對消隱電路可以用於準確估計更為複雜電路的保真度充滿信心。

                圖.4 : 量子優越性的證明

保真度仍可以直接被驗證的最大電路有53個量子比特和一個簡化處理過的門電路。100萬個內核以0.8%的保真度對這些隨機電路進行採樣需要花費130秒,相較於單核,量子處理器有百萬倍的加速。

我們現在繼續對計算最複雜電路進行基準測試,這個只是2個比特門的重排列。在圖4中,我們显示了通過不斷增加深度,針對53量子比特的全優越性電路的補丁版和消隱版本測得的FXEB。對於有53個量子比特和20個周期的最大電路,我們在10個電路實例上搜集了 Ns = 30 × 106

樣本,對於消隱電路得到的FXEB = (2.24±0.21)×10−3。基於5σ的置信度,我們斷定在量子處理器上運行這些電路的平均保真度至少大於0.1%。我們預期 圖4b的全部數據應具有近似的保真度,但是由於仿真時間(紅色数字)需要很長時間才能檢查,我們將數據歸檔(參見“數據可用性”部分)。這部分數據因此處於量子至上的狀態。

 

The classical computational cost

經典計算的成本

我們在經典計算機的實驗中模擬量子電路有2個目的:(1))通過使用可能簡化的電路計算FXEB來驗證我們的量子處理器和基準測試方法(圖4a),(2)估算FXEB以及對最困難的電路進行採樣的經典成本(圖4b)。對多達43個量子比特,我們使用Schrödinger算法,該算法模擬了完整量子態的演化;在Jülich超級計算機(100,000核、250 TB)運行了最大的樣例。超過此大小,則沒有足夠的隨機存取存儲器(RAM)來存儲量子的狀態了。對於更多的量子比特,我們使用運行在Google數據中心的混合Schrödinger-Feynman算法來計算單個比特串的幅度。在使用類似費曼路徑積分的方法連接它們之前,該算法將電路拆分為兩個量子比特補丁,並使用Schrödinger方法有效地模擬每個補丁。儘管具有更高的內存效率,但隨着路徑深度與連接補丁的門的數量呈指數增長,隨着電路深度的增加,Schrödinger-Feynman算法的計算量也呈指數增長。

為了估算優越性電路的經典計算成本(圖4中的灰色数字),我們在Summit超級計算機以及Google集群上都運行了部分量子電路的仿真,從而推斷出其全部成本。在此推斷中,我們通過擴展FXEB的驗證成本來認定採樣的計算成本,例如,一個0.1%減少了約1000的花費。在當今世界上功能最強大的Summit超級計算機上,我們使用了一個受費曼路徑積分啟發的方法,該方法在低深度下效率最高。當m = 20時,張量無法合理地放入節點內存中,因此我們只能在m=14時測量運行時間,因此我們估計以1%的保真度採樣300萬個比特串將需要一年。

在谷歌雲服務器上,我們預估使用Schrödinger-Feynman算法以0.1%的保真度在m = 20時運行相同的任務將耗費50萬億個核/小時,並消耗1皮瓦時的能量。從這個角度來看,對量子處理器上的電路採樣三百萬次需要600秒,而採樣時間受控制硬件通信的限制;實際上,量子處理的凈量子處理器的凈時間僅為30秒左右。所有電路的比特串樣本都已在線存檔(請參見“數據可用性”部分),以激勵開發和測試更高級的驗證算法。

有人可能會懷疑算法創新可以在多大程度上增強經典模擬的效果。我們的假設基於複雜理論的認知,即算法任務的成本是電路大小的指數。的確,在過去的幾年中,模擬算法已經得到了穩步的提升。我們預計最終將實現比報告里提到的更低的仿真成本,但是我們也期望更大型的量子處理器在硬件方面的改進將持續超越它們。

 

Verifying digital error model

驗證数字錯誤模型

基於量子錯誤校正理論的一個關鍵假設是—量子態錯誤可以考慮数字化和本地化。基於這樣的一個数字模型,演化量子態中的所有錯誤都可能通過散布在電路中的一組局部保利誤差(位翻轉或相位翻轉)來表徵。由於持續振幅是量子力學的基礎,所以需要測試量子系統中的錯誤是否可以被視為離散的和呈概率分佈的。我們實驗的觀察結果證明該模型對我們處理器確實是有效的。我們系統的保真度可以通過一個簡單的模型很好地預測,在該模型中,每個門各自的特徵保真度相乘起來(圖4)。

為了能成功被数字化錯誤模型描述,系統的相關雙指數級得很小才行。我們通過選擇隨機化和解相關錯誤的電路,優化控制以最大程度地減少系統錯誤和泄漏以及設計比相關噪聲源(如1 / f磁通噪聲)運行得更快的門,從而在我們的實驗中達成了這一點。通過在高達253的希爾伯特空間對預測性不相關的誤差模型的演示,可以表明我們可以構建一個系統,在該系統中量子資源(例如糾纏)不會過於脆弱。

 

The future

未來

基於超導量子比特的量子處理器現在可以處理,量綱為253 ≈ 9 ×1015的希爾伯特(Hilbert)空間的計算,超出了當今最快的經典超級計算機的上限。據我們所知,此次試驗標記了只能在量子處理器運行的第一個計算。量子處理器因此構建了量子優越性的制度。我們希望他們的計算能力將繼續以雙指數級的比率增長:模擬量子電路的經典成本隨着計算量的增加而呈指數級的增長,而硬件的提升將可能遵循量子處理器當量的摩爾定律,即每隔幾年此計算量就翻倍。為了支撐雙指數級的增長率並最終提供運算著名的,如Shor 或者Grover ,量子算法所需的計算量,量子誤差修正的工程學將成為關注的焦點。

Bernstein 和 Vazirani 闡述的擴展自Church–Turing的論文,斷言任何合理的模型都可以由圖靈機有效的模擬。

 

Data availability

數據可用性

用於本次研究形成和分析的數據庫可在我么公開的樹妖(Dryad)倉庫上獲得 (https://doi.org/10.5061/dryad.k6t1rj8)。

 

在線內容

 任何方法、額外參考、自然研究的報告摘要、源數據、擴展數據、補充信息、確認書、同行評審信息;作者貢獻和利益衝突的詳細信息; 以及數據和代碼可用性均可在 https://doi.org/10.1038/s41586-019-1666-5 得到

 

初次嘗試翻譯,錯誤之處必不在少,歡迎批評指正

附:

1)英文論文下載:  

2)

 

*****************************************************************************************************

精力有限,想法太多,專註做好一件事就行

  • 我只是一個程序猿。5年內把代碼寫好,技術博客字字推敲,堅持零拷貝和原創
  • 寫博客的意義在於鍛煉邏輯條理性,加深對知識的系統性理解,鍛煉文筆,如果恰好又對別人有點幫助,那真是一件令人開心的事

*****************************************************************************************************

 

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

※自行創業 缺乏曝光? 下一步”網站設計“幫您第一時間規劃公司的門面形象

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

※試算大陸海運運費!

甲烷排放嚴重低估 研究:石油天然氣危害氣候 比預期還糟

環境資訊中心綜合外電;姜唯 編譯;林大利 審校

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

※自行創業 缺乏曝光? 下一步”網站設計“幫您第一時間規劃公司的門面形象

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

※試算大陸海運運費!

堆肥葬更環保 美各州搶立法通過

摘錄自2020年2月17日台灣醒報報導

堆肥葬即將成為新的喪葬趨勢!Recompose公司16日發表聲明,藉由6位志願者遺體的實驗測試,證實利用遺體堆肥不會生成有害物質。當前美國多州正考慮將堆肥葬合法化。

根據華盛頓州立大學教授卡彭特・伯格斯表示,在人體分解的最後階段加入混合木屑等營養材料,能讓微生物發揮更大效用。最後再加溫至攝氏55度消毒殺菌,分解後的土壤即可作為堆肥使用。

據《科學新聞》週刊報導,Recompose公司創始人卡特李娜・斯派德在報告中特別表明,堆肥葬的有機降解相對於火葬,可以減少1.4噸的碳排放量,只使用火化所需能源的8分之 1。而相對於土葬,能夠大幅縮小土地使用空間,考慮土葬的棺材用料和成本,也降低碳足跡與喪葬費用。雖然堆肥葬不如火葬費用便宜,但廣泛運用後價格會有變低趨勢。

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

※自行創業 缺乏曝光? 下一步”網站設計“幫您第一時間規劃公司的門面形象

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

※試算大陸海運運費!