萬向集團年產5000輛新能源客車項目獲國家發改委通過

11{icon} {views}

近日,國家發改委正式批復同意《關於要求核准萬向集團公司年產5000輛新能源客車項目申請報告的請示》,這意味著萬向集團公司年產5000輛新能源客車項目正式通過。
 
該專案建設規模為年產5000輛新能源客車,分步實施。一是改建北塘路廠區,利用原有純電動專用車生產廠房和生產、研發、檢測設備,新增部分工藝、公用設備及工位元器具,實現研發和小批量生產能力。二是在建設一路廠區新建焊裝、塗裝、總裝、底盤等車間及研發大樓和試車跑道,新增各類生產和研發設備。

專案建設地點為浙江省杭州市蕭山經濟技術開發區萬向集團公司現有北塘路廠區和建設一路廠區。
 
據悉,項目總投資20.6億元,其中新增投資17.6億元,利用原有固定資產投資3億元。新增投資包括固定資產投資12.7億元,鋪底流動資金4.8億元。新增投資資金來源為企業自籌10.4億元,申請銀行貸款7.1億元。

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

【其他文章推薦】

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

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

大陸寄台灣空運注意事項

大陸海運台灣交貨時間多久?

Javascript模塊化開發2——Gruntfile.js詳解

7{icon} {views}

一、grunt模塊簡介

grunt插件,是一種npm環境下的自動化工具。對於需要反覆重複的任務,例如壓縮、編譯、單元測試、linting等,自動化工具可以減輕你的勞動,簡化你的工作。grunt模塊根據Gruntfile.js文件中的配置進行任務。
如果在package.json中定義如下命令:

"scripts": {
    "build": "npm install && grunt"
}

因為運行npm run build會先安裝devDependencies中定義的一些模塊,則運行npm run build這個命令相當於做如下操作:

  • npm install grunt-cli -g
  • npm install
  • grunt

二、gruntfile.js的結構:

  • “wrapper” 函數
  • 項目和任務配置
  • 加載 grunt 插件和任務
  • 自定義任務

三、”wrapper” 函數

每一份 Gruntfile.js(和grunt插件)都遵循同樣的格式,你所書寫的Grunt代碼必須放在此函數內:

module.exports = function(grunt){
         //do grunt-related things in here
}

四、項目和任務配置

大部分的Grunt任務都依賴某些配置數據,我們通過grunt.initConfig 方法來配置Grunt任務的參數。
grunt.initConfig 方法的參數是一個JSON對象,你可以在這個配置對象中存儲任意的數據。此外,由於這本身就是JavaScript,你還可以在這裏使用任意的有效的JS代碼。甚至你可以用<% %>模板字符串來引用已經配置過的屬性,例如:

// 項目和任務配置
grunt.initConfig({
  pkg: grunt.file.readJSON('package.json'), //獲取 package.json 中的元數據(js代碼)
  proj:{
    name:'hello',
    description:'a hello demo'
  },
  hello: {
    options: {
      name: '<%= proj.name %>'  //用<% %>模板字符串匹配hello
    },
    srcs: ['1.txt', '2.txt']
  }
});

在grunt.initConfig 方法中配置的屬性,在任務模塊中,可用grunt.config方法進行訪問,例如:

grunt.config("proj.name");

另外,grunt任務模塊會自動根據任務名來提取配置對象中和任務名對應的屬性,比如定義任務hello,則在配置對象對應的屬性”hello”中配置任務執行函數中所需用到的配置和數據。

五、加載grunt插件任務

為了減少重複勞動,我們可以加載已有的插件任務。

1.加載自己私有的grunt插件

可將自己定義的一些task腳本放在同一個目錄下,通過grunt.loadTasks方法從指定目錄加載該目錄下所有的grunt任務腳本。

2.加載在npm中發布的grunt插件

像 grunt-contrib-copy和grunt-contrib-uglify這些常用的任務都已經以grunt插件的形式被開發出來了,且被發布在npm公開庫中,只要在 package.json 文件中將需要使用的插件列在dependency中,並通過npm install安裝之後,就可以直接加載該任務。

// 加載能夠提供"copy"任務的插件。
grunt.loadNpmTasks('grunt-contrib-copy');

3.直接加載所有以”grunt-“打頭的插件

npm上有個load-grunt-tasks插件可以用來加載dependency列表中所有以”grunt-“打頭的插件。
將需要使用的”grunt-“打頭的插件列在dependency中,然後在Gruntfile.js中進行調用。

//Load grunt tasks from NPM packages
load-grunt-tasks

六、自定義任務

1.直接定義任務的行為

grunt.registerTask('hello', 'Show some msg', function() {
  console.log(this.options().name); //輸出hello
});

2.定義為任務列表

可以將一個任務定義為一系列任務的組合,這一系列任務將按照順序執行。

grunt.registerTask('dothings', 'copy and Show some msg', ['copy','hello']);

3.定義默認任務

通過定義 default 任務,可以讓Grunt默認執行一個或多個任務。執行 grunt 命令時如果不指定一個任務的話,將會執行默認任務。如進行下面定義的話執行grunt 相當於執行grunt hello。

grunt.registerTask('default', ['hello']);

4.定義複合任務

registerMultiTask方法可以定義一個複合任務,複合任務將會對grunt.initConfig 方法中配置的相應屬性中除了options外定義的屬性依次作為target:data對進行處理。

module.exports = function(grunt) {
    grunt.initConfig({
        Log: {
            options: {
                sep: ';'
            },
            srcs: ['1.txt', '2.txt'],
            dests: ['d1.txt', 'd2.txt']
        }
    });
    grunt.registerMultiTask("Log", function() {
        var options = this.options({ sep: '&' });
        console.log(this.target); 
        console.log(this.data.join(options.sep));
    });
};

執行grunt Log將會輸出:

Running “Log:srcs” (Log) task
srcs
1.txt;2.txt
Running “Log:dests” (Log) task
dests
d1.txt;d2.txt

定義任務行為Tips

1.任務內部可以執行其他的任務。

grunt.registerTask('mytask', function() {
  grunt.task.run('task1', 'task2');
  // Or:
  grunt.task.run(['task1', 'task2']);
});

2.定義異步任務

grunt.registerTask('mytask', function() {
  var done = this.async();
  //do something
  done();
});

3.當任務失敗時,所有後續任務都將終止

在任務中,當執行失敗,可以return false來表明當前任務執行失敗,一般,多個任務按順序執行,如果有任務失敗時,所有後續任務都將終止。可以通過在命令行后加上–force來使有任務失敗時,後續任務能繼續進行。

4.任務中檢查前置任務狀態

有些任務可以依賴於其他任務的成功執行。通過grunt.task.requires方法來檢查其前置任務是否已經執行,並且沒有失敗。

5.任務中檢查配置屬性

可以用方法grunt.task.requiresConfig指定一個或者多個字符串或者數組的配置屬性為必需的。如果一個或多個必需的配置屬性缺失,就通知系統當前任務失敗。

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

【其他文章推薦】

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

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

大陸寄台灣空運注意事項

大陸海運台灣交貨時間多久?

MySQL InnoDB 鎖

5{icon} {views}

MySQL 原理篇

數據準備:

/*
SQLyog Ultimate v12.09 (64 bit)
MySQL - 5.6.17 : Database - test
*********************************************************************
*/


/*!40101 SET NAMES utf8 */;

/*!40101 SET SQL_MODE=''*/;

/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
CREATE DATABASE /*!32312 IF NOT EXISTS*/`test` /*!40100 DEFAULT CHARACTER SET utf8 */;

USE `test`;

/*Table structure for table `t2` */

DROP TABLE IF EXISTS `t2`;

CREATE TABLE `t2` (
  `id` int(11) NOT NULL,
  `name` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

/*Data for the table `t2` */

insert  into `t2`(`id`,`name`) values (1,'1'),(4,'4'),(7,'7'),(10,'10');

/*Table structure for table `teacher` */

DROP TABLE IF EXISTS `teacher`;

CREATE TABLE `teacher` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(32) NOT NULL,
  `age` int(11) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4;

/*Data for the table `teacher` */

insert  into `teacher`(`id`,`name`,`age`) values (1,'seven11124',18),(2,'qingshan',18);

/*Table structure for table `user_account` */

DROP TABLE IF EXISTS `user_account`;

CREATE TABLE `user_account` (
  `id` int(11) NOT NULL DEFAULT '0',
  `balance` int(11) NOT NULL,
  `lastUpdate` datetime NOT NULL,
  `userID` int(11) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

/*Data for the table `user_account` */

insert  into `user_account`(`id`,`balance`,`lastUpdate`,`userID`) values (1,3200,'2018-12-06 13:27:57',1),(2,50,'2018-12-06 13:28:08',2),(3,1000,'2018-12-06 13:28:22',3);

/*Table structure for table `users` */

DROP TABLE IF EXISTS `users`;

CREATE TABLE `users` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(32) NOT NULL,
  `age` int(11) NOT NULL,
  `phoneNum` varchar(32) NOT NULL,
  `lastUpdate` datetime NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `idx_eq_name` (`name`)
) ENGINE=InnoDB AUTO_INCREMENT=21 DEFAULT CHARSET=utf8mb4;

/*Data for the table `users` */

insert  into `users`(`id`,`name`,`age`,`phoneNum`,`lastUpdate`) values (1,'seven',26,'13666666666','2018-12-07 19:22:51'),(2,'qingshan',19,'13777777777','2018-12-08 21:01:12'),(3,'james',20,'13888888888','2018-12-08 20:59:39'),(4,'tom',99,'13444444444','2018-12-06 20:34:10'),(6,'jack',91,'13444444544','2018-12-06 20:35:07'),(11,'jack1',33,'13441444544','2018-12-06 20:36:19'),(15,'tom2',30,'1344444444','2018-12-08 15:08:24'),(19,'iiii',30,'1344444444','2018-12-08 21:21:47');

/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;

在運行下面的演示案例之前,先把表和數據準備好。

理解表鎖和行鎖

鎖是用於管理不同事務對共享資源的併發訪問。

表鎖與行鎖的區別

  • 鎖定粒度:表鎖 > 行鎖
  • 加鎖效率:表鎖 > 行鎖
  • 衝突概率:表鎖 > 行鎖
  • 併發性能:表鎖 < 行鎖

InnoDB 存儲引擎支持行鎖和表鎖(另類的行鎖),InnoDB 的表鎖是通過對所有行加行鎖實現的。

鎖的類型

  • 共享鎖(行鎖):Shared Locks
  • 排他鎖(行鎖):Exclusive Locks
  • 意向鎖共享鎖(表鎖):Intention Shared Locks
  • 意向鎖排它鎖(表鎖):Intention Exclusive Locks
  • 自增鎖:AUTO-INC Locks

行鎖的算法

  • 記錄鎖:Record Locks
  • 間隙鎖:Gap Locks
  • 臨鍵鎖:Next-key Locks

官網文檔:

共享鎖(Shared Locks)

定義

共享鎖:又稱為讀鎖,簡稱 S 鎖,顧名思義,共享鎖就是多個事務對於同一數據可以共享一把鎖,都能訪問到數據,但是只能讀不能修改。

通過如下代碼,加鎖和釋放鎖:

-- 加鎖
select * from users WHERE id=1 LOCK IN SHARE MODE;

-- 釋放鎖:提交事務 or 回滾事務
commit;
rollback;

演示案例

-- 共享鎖
-- 事務A執行
BEGIN;

SELECT * FROM users WHERE id=1 LOCK IN SHARE MODE;

ROLLBACK;
COMMIT;

-- 事務B執行
SELECT * FROM users WHERE id=1;

UPDATE users SET age=19 WHERE id=1;
  • 事務A手動開啟事務,執行語句獲取共享鎖,注意這裏沒有提交事務
  • 事務B分別執行 SELECT 和 UPDATE 語句,查看執行效果

結論:UPDATE 語句被鎖住了,不能執行。在事務A獲得共享鎖的情況下,事務B可以執行查詢操作,但是不能執行更新操作。

排他鎖(Exclusive Locks)

定義

排它鎖:又稱為寫鎖,簡稱 X 鎖,排他鎖不能與其他鎖並存,如一個事務獲取了一個數據行的排他鎖,其他事務就不能再獲取該行的鎖(共享鎖、排他鎖),只有該獲取了排他鎖的事務是可以對數據行進行讀取和修改。(其他事務要讀取數據可來自於快照)

通過如下代碼,加鎖和釋放鎖:

-- 加鎖
-- delete / update / insert 默認加上X鎖
-- SELECT * FROM table_name WHERE ... FOR UPDATE
-- 釋放鎖:提交事務 or 回滾事務
commit;
rollback;

演示案例

-- 排它鎖
-- 事務A執行
BEGIN;

UPDATE users SET age=23 WHERE id=1;

COMMIT;
ROLLBACK;

-- 事務B執行
SELECT * FROM users WHERE id=1 LOCK IN SHARE MODE;
SELECT * FROM users WHERE id=1 FOR UPDATE;
-- SELECT 可以執行,數據來自於快照
SELECT * FROM users WHERE id=1;
  • 事務A手動開啟事務,執行 UPDATE 語句,獲取排它鎖,注意這裏沒有提交事務
  • 事務B分別執行三條語句,查看執行效果

 

結論:事務B的第一條 SQL 和第二條 SQL 語句都不能執行,都已經被鎖住了,第三條 SQL 可以執行,數據來自於快照,關於這點後面會講到。

行鎖到底鎖了什麼

InnoDB 的行鎖是通過給索引上的索引項加鎖來實現的。

只有通過索引條件進行數據檢索,InnoDB 才使用行級鎖,否則,InnoDB 將使用表鎖(鎖住索引的所有記錄)

通過普通索引進行數據檢索,比如通過下面例子中 UPDATE users SET lastUpdate=NOW() WHERE `name`='seven';該 SQL 會在 name 字段的唯一索引上面加一把行鎖,同時會在該唯一索引對應的主鍵索引上面也會加上一把行鎖,總共會加兩把行鎖。

演示案例

演示之前,先看一下 users 表的結構和數據內容。

-- 案例1
-- 事務A執行
BEGIN;

UPDATE users SET lastUpdate=NOW() WHERE phoneNum='13666666666';

ROLLBACK;

-- 事務B執行
UPDATE users SET lastUpdate=NOW() WHERE id=2;
UPDATE users SET lastUpdate=NOW() WHERE id=1;

-- 案例2
-- 事務A執行
BEGIN;

UPDATE users SET lastUpdate=NOW() WHERE id=1;

ROLLBACK;

-- 事務B執行
UPDATE users SET lastUpdate=NOW() WHERE id=2;
UPDATE users SET lastUpdate=NOW() WHERE id=1;

-- 案例3
-- 事務A執行
BEGIN;

UPDATE users SET lastUpdate=NOW() WHERE `name`='seven';

ROLLBACK;

-- 事務B執行
UPDATE users SET lastUpdate=NOW() WHERE `name`='seven';
UPDATE users SET lastUpdate=NOW() WHERE id=1;
UPDATE users SET lastUpdate=NOW() WHERE `name`='qingshan';
UPDATE users SET lastUpdate=NOW() WHERE id=2;

注意:這裏演示的案例都是在事務A沒有提交之前,執行事務B的語句。

案例1執行結果如下圖所示:

案例2執行結果如下圖所示:

案例3執行結果如下圖所示:

意向共享鎖(Intention Shared Locks)& 意向排它鎖(Intention Exclusive Locks)

意向共享鎖(IS)

表示事務準備給數據行加入共享鎖,即一個數據行加共享鎖前必須先取得該表的 IS 鎖,意向共享鎖之間是可以相互兼容的。

意向排它鎖(IX)

表示事務準備給數據行加入排他鎖,即一個數據行加排他鎖前必須先取得該表的 IX 鎖,意向排它鎖之間是可以相互兼容的

意向鎖(IS 、IX)是 InnoDB 數據操作之前自動加的,不需要用戶干預。

意義:當事務想去進行鎖表時,可以先判斷意向鎖是否存在,存在時則可快速返回該表不能啟用表鎖。

演示案例

-- IS鎖的意義
-- 事務A執行
BEGIN;

UPDATE users SET lastUpdate=NOW() WHERE id=1;

ROLLBACK;

-- 事務B執行
-- 因為沒有通過索引條件進行數據檢索,所以這裏加的是表鎖
UPDATE users SET lastUpdate=NOW() WHERE phoneNum='13777777777';

結論:事務B的 SQL 因為沒有通過索引條件進行數據檢索,所以這裏加的是表鎖,在對錶加鎖之前會查看該表是否已經存在了意向鎖,因為事務A已經獲得了該表的意向鎖了,所以事務B不需要判斷每一行數據是否已經加鎖,可以快速通過意向鎖阻塞當前 SQL 的更新操作。

自增鎖(AUTO-INC Locks)

定義

針對自增列自增長的一個特殊的表級別鎖。

通過如下命令查看自增鎖的默認等級:

SHOW VARIABLES LIKE 'innodb_autoinc_lock_mode';

默認取值1,代表連續,事務未提交 ID 永久丟失。

演示案例

-- 事務A執行
BEGIN;
INSERT INTO users(NAME , age ,phoneNum ,lastUpdate ) VALUES ('tom2',30,'1344444444',NOW());
ROLLBACK;

BEGIN;
INSERT INTO users(NAME , age ,phoneNum ,lastUpdate ) VALUES ('xxx',30,'13444444444',NOW());
ROLLBACK;

-- 事務B執行
INSERT INTO users(NAME , age ,phoneNum ,lastUpdate ) VALUES ('yyy',30,'13444444444',NOW());

事務A執行完后,在執行事務B的語句,發現插入的 ID 數據不再連續,因為事務A獲取的 ID 數據在 ROLLBACK 之後被丟棄了。

臨鍵鎖(Next-Key Locks)

定義

當 SQL 執行按照索引進行數據的檢索時,查詢條件為範圍查找(between and、<、>等)並有數據命中,則此時 SQL 語句加上的鎖為 Next-key locks,鎖住索引的記錄 + 區間(左開右閉)

演示案例

演示之前,先看一下 t2 表的結構和數據內容。

臨鍵鎖(Next-key Locks):InnoDB 默認的行鎖算法。

t2 表中的數據行有4條數據:1,4,7,10,InnoDB 引擎會將表中的數據劃分為:(-∞, 1] (1, 4] (4, 7] (7, 10] (10, +∞),執行如下 SQL 語句:

-- 臨鍵鎖
-- 事務A執行
BEGIN;

SELECT * FROM t2 WHERE id>5 AND id<9 FOR UPDATE;

ROLLBACK

-- 事務B執行
BEGIN;

SELECT * FROM t2 WHERE id=4 FOR UPDATE; -- 可以執行
SELECT * FROM t2 WHERE id=7 FOR UPDATE; -- 鎖住
SELECT * FROM t2 WHERE id=10 FOR UPDATE; -- 鎖住
INSERT INTO `t2` (`id`, `name`) VALUES (9, '9'); -- 鎖住

SELECT * FROM t2 WHERE id>5 AND id<9 FOR UPDATE; 這條查詢語句命中了7這條數據,它會鎖住 (4, 7] 這個區間,同時還會鎖住下一個區間 (7, 10]。

為什麼 InnoDB 選擇臨鍵鎖作為行鎖的默認算法?

防止幻讀。當我們把下一個區間也鎖住的時候,這個時候我們要新增數據,就會被鎖住,這樣就可以防止幻讀。

間隙鎖(Gap Locks)

定義

當 SQL 執行按照索引進行數據的檢索時,查詢條件的數據不存在,這時 SQL 語句加上的鎖即為 Gap locks,鎖住數據不存在的區間(左開右開)

Gap 只在 RR 事務隔離級別存在。因為幻讀問題是在 RR 事務通過臨鍵鎖和 MVCC 解決的,而臨鍵鎖=間隙鎖+記錄鎖,所以間隙鎖只在 RR 事務隔離級別存在。

演示案例

-- 間隙鎖
-- 事務A執行
BEGIN;

SELECT * FROM t2 WHERE id>4 AND id <6 FOR UPDATE;
-- 或者
SELECT * FROM t2 WHERE id=6 FOR UPDATE;

ROLLBACK;

-- 事務B執行
INSERT INTO `t2` (`id`, `name`) VALUES (5, '5');
INSERT INTO `t2` (`id`, `name`) VALUES (6, '6');

 SELECT * FROM t2 WHERE id>4 AND id <6 FOR UPDATE; 這條查詢語句不能命中數據,它會鎖住 (4, 7] 這個區間。

記錄鎖(Record Locks)

定義

當 SQL 執行按照唯一性(Primary key、Unique key)索引進行數據的檢索時,查詢條件等值匹配且查詢的數據是存在,這時 SQL 語句加上的鎖即為記錄鎖 Record Locks,鎖住具體的索引項

演示案例

-- 記錄鎖
-- 事務A執行
BEGIN;

SELECT * FROM t2 WHERE id=4 FOR UPDATE;

ROLLBACK;


-- 事務B執行
SELECT * FROM t2 WHERE id=7 FOR UPDATE;
SELECT * FROM t2 WHERE id=4 FOR UPDATE;

事務A執行 SELECT * FROM t2 WHERE id=4 FOR UPDATE; 把 id=4 的數據行鎖住。

當 SQL 執行按照普通索引進行數據的檢索時,查詢條件等值匹配且查詢的數據是存在,這時 SQL 語句鎖住數據存在區間左開右開)

利用鎖解決事務併發帶來的問題

InnoDB 真正處理事務併發帶來的問題不僅僅是依賴鎖,還有其他的機制,下篇文章會講到,所以這裏只是演示利用鎖是如何解決事務併發帶來的問題,並不是 InnoDB 真實的處理方式。

利用鎖怎麼解決臟讀

在事務B的更新語句上面加上一把 X 鎖,這樣就可以有效的解決臟讀問題。

利用鎖怎麼解決不可重複讀

在事務A的查詢語句上面加上一把 S 鎖,事務B的更新操作將會被阻塞,這樣就可以有效的解決不可重複讀的問題。

利用鎖怎麼解決幻讀

在事務A的查詢語句上面加上一把 Next-key 鎖,通過臨鍵鎖的定義,可以知道這個時候,事務A會把 (-∞,+∞) 的區間數據都鎖住,事務B的新增操作將會被阻塞,這樣就可以有效的解決幻讀的問題。

死鎖

死鎖的介紹

  • 多個併發事務(2個或者以上);
  • 每個事務都持有鎖(或者是已經在等待鎖);
  • 每個事務都需要再繼續持有鎖;
  • 事務之間產生加鎖的循環等待,形成死鎖。

演示案例

-- 事務A執行
BEGIN;

UPDATE users SET lastUpdate = NOW() WHERE id =1;

UPDATE t2 SET `name`='test' WHERE id =1;

ROLLBACK;

-- 事務B執行
BEGIN;

UPDATE t2 SET `name`='test' WHERE id =1;

UPDATE users SET lastUpdate = NOW() WHERE id =1;

ROLLBACK;

事務A和事務B按照上面的執行步驟,最後因為存在相互等待的情況,所以 MySQL 判斷出現死鎖了。

死鎖的避免

  • 類似的業務邏輯以固定的順序訪問表和行。
  • 大事務拆小。大事務更傾向於死鎖,如果業務允許,將大事務拆小。
  • 在同一個事務中,盡可能做到一次鎖定所需要的所有資源,減少死鎖概率。
  • 降低隔離級別,如果業務允許,將隔離級別調低也是較好的選擇
  • 為表添加合理的索引。可以看到如果不走索引將會為表的每一行記錄添加上鎖(或者說是表鎖)

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

【其他文章推薦】

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

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

大陸寄台灣空運注意事項

大陸海運台灣交貨時間多久?

2016 第二屆亞太新能源汽車國際峰會 聚焦智慧網聯電動交通

12{icon} {views}

本次由中國汽車工程研究院支援並由上海領研商務諮詢有限公司主辦的 “2016第二屆亞太新能源汽車國際峰會”將於2016年6月2日(星期四)至3日(星期五)在北京召開。

隨著全球能源危機的加重及汽車排放引起的環境問題日益得到世界各國的重視,智慧化,網聯化和電動化已成為未來全球汽車行業發展的必然趨勢。汽車行業是能源、資源消耗較大的產業,同時汽車排放也是環境污染的重要原因。中國已經步入汽車生產和消費大國,節能、環保、低碳是中國汽車產業面臨的長期任務。

據中汽協資料顯示,2015年新能源汽車生產34.04萬輛,銷售33.1萬輛,同比分別增長3.3倍、3.4倍。根據機動車整車出廠合格證統計,2016年1月,中國新能源汽車生產1.61萬輛,同比增長144%。根據全國乘用車市場訊息聯席會剛發佈的統計資料,2016年1月新能源乘用車銷量達13748輛,較之2015年1月同比增長1.8倍。

2015年11月18日,中國國家發改委發佈了《電動汽車充電基礎設施發展指南(2015-2020年)》 ,明確提出到2020年,中國將新增集中式充換電站1.2萬座,分散式充電樁480萬個,以滿足全國500萬輛電動汽車充電需求。今年5月19日中國務院發佈的《中國製造2025》中提出將“節能與新能源汽車”作為重點發展領域,明確了繼續支持電動汽車、燃料電池汽車發展,掌握汽車低碳化、資訊化、智慧化核心技術等的發展戰略。

面臨重大發展機遇的同時,新能源汽車的大規模商業化仍然還存在一些挑戰和瓶頸,如價格偏高、性能不完善、充電基礎設施不完善、充電標準不統一、關鍵零部件技術尚未成熟等等。

在此背景下,2016第二屆亞太新能源汽車國際峰會以 “助力智慧網聯電動交通”為主題,旨在為全球新能源汽車參與者打造一個瞭解中國新能源汽車發展現狀,商業模式和技術發展趨勢的前瞻性產業交流平臺。   

相信本屆在北京召開的”2016第二屆亞太新能源汽車國際峰會”構建的交流平臺和溝通管道,對於借鑒先進理念、整合政策資源、利用各方智慧,推進新能源汽車產業發展、節能減排等各項工作將會產生深遠影響和積極作用,並為中國綠色交通產業起到巨大的推動作用。

期待您的支持和參與。

更多關於2016ANEVS的資訊,請登錄會議官方網站:

組委會聯繫方式:
連絡人:Fiona Hu           
電  話:+86 21-58560121
郵  箱:

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

【其他文章推薦】

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

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

大陸寄台灣空運注意事項

大陸海運台灣交貨時間多久?

[ASP.NET Core 3框架揭秘] 文件系統[1]:抽象的“文件系統”

5{icon} {views}

ASP.NET Core應用 具有很多讀取文件的場景,比如配置文件、靜態Web資源文件(比如CSS、JavaScript和圖片文件等)以及MVC應用的View文件,甚至是直接編譯到程序集中的內嵌資源文件。這些文件的讀取都需要使用到一個IFileProvider對象。IFileProvider對象構建了一個抽象的文件系統,我們不僅可以利用它提供的統一API來讀取各種類型的文件,還能及時監控目標文件的變化。

一、樹形層次結構

IFileProvider對象為我們構建了一個具有層次化目錄結構的文件系統。由於IFileProvider是一個接口,所以由它構建的是一個抽象化的文件系統,這裏所謂的目錄和文件都是一個抽象的概念。具體的文件可能對應一個物理文件,也可能保存在數據庫中,或者來源於網絡,甚至有可能根本就不存在,其內容需要在讀取時動態生成。目錄也僅僅是組織文件的邏輯容器。為了讓讀者朋友們對這個文件系統有一個大體認識,我們先來演示幾個簡單的實例。

文件系統管理的所有文件以目錄的形式進行組織,一個IFileProvider對象可以視為針對一個根目錄的映射。目錄除了可以存放文件之外,還可以包含子目錄,所以目錄/文件在整體上呈現出樹形化層次化結構。接下來我們將一個IFileProvider對象映射到一個物理目錄,並利用它將所在目錄的結構呈現出來。

我們演示實例是一個普通的控制台程序。我們在演示實例中定義了如下一個IFileManager接口,它利用一個唯一的ShowStructure方法將文件系統的整體結構显示出來。該方法具有一個類型為Action<int, string>的參數負責將文件系統的節點(目錄或者文件)名稱呈現出來。這個Action<int, string>對象的兩個參數分別代表縮進的層級和目錄/文件的名稱。

public interface IFileManager
{
    void ShowStructure(Action<int, string> render);
}

我們定義如下這個FileManager類作為對IFileManager接口的默認實現,它利用只讀_fileProvider字段表示的IFileProvider對象來提取目錄結構。目標文件系統的整體結構通過Render方法以遞歸的方式呈現出來,其中涉及到對IFileProvider對象的GetDirectoryContents方法的調用。該方法返回一個IDirectoryContents對象表示指定目錄的內容,如果對應的目錄存在,我們可以遍歷該對象得到它的子目錄和文件。目錄和文件最終體現為一個IFileInfo對象來,至於IFileInfo對象對應的就是一個目錄還是一個文件,則通過其IsDirectory屬性來區分。

public class FileManager : IFileManager
{
    private readonly IFileProvider _fileProvider;
    public FileManager(IFileProvider fileProvider) => _fileProvider = fileProvider;
    public void ShowStructure(Action<int, string> render)
    {
        int indent = -1;
        Render("");
        void Render(string subPath)
        {
            indent++;
            foreach (var fileInfo in _fileProvider.GetDirectoryContents(subPath))
            {
                render(indent, fileInfo.Name);
                if (fileInfo.IsDirectory)
                {
                    Render($@"{subPath}\{fileInfo.Name}".TrimStart('\\'));
                }
            }
            indent--;
        }
    }        
}

接下來我們構建一個本地物理目錄“c:\test\”,並按照如下圖所示的結構在它下面創建相應的子目錄和文件。我們會將這個目錄映射到一個IFileProvider對象上,並進一步利用它創建出上面這個FileManager對象。我們最終調用這個FileManager對象的ShowStructure方法將目錄結構呈現出來。

整個演示程序體現在如下的代碼片段中。我們針對目錄“c:\test\”創建了一個表示物理文件系統的PhysicalFileProvider對象,並將其註冊到創建的ServiceCollection對象上。除此之外,ServiceCollection對象上還添加了針對IFileManager/FileManager的服務註冊。

class Program
{
    static void Main()
    {
        static void Print(int layer, string name)  => Console.WriteLine($"{new string(' ', layer * 4)}{name}");        
        new ServiceCollection()
            .AddSingleton<IFileProvider>(new PhysicalFileProvider(@"c:\test"))
            .AddSingleton<IFileManager, FileManager>()
            .BuildServiceProvider()
            .GetRequiredService<IFileManager>()
            .ShowStructure(Print);
    }
}

我們最終利用ServiceCollection生成的IServiceProvider對象得到FileManager對象,並調用該對象的ShowStructure方法將PhysicalFileProvider對象映射的目錄結構呈現出來。當我們運行該程序之後,控制台上將呈現出如下圖所示的輸出結果,該結果為我們展示了映射物理目錄的真實結構。(S501)

二、讀取文件內容

前面我們演示了如何利用IFileProvider對象將文件系統的結構完整地呈現出來,接下來我們來演示如何利用它來讀取一個物理文件的內容。我們為IFileManager定義如下一個ReadAllTextAsync方法以異步的方式讀取指定文件內容,方法的參數表示文件的路徑。如下面的代碼片段所示,ReadAllTextAsync方法將指定的文件路徑作為參數調用IFileProvider對象的GetFileInfo方法得到一個IFileInfo對象。我們最終調用這個IFileInfo對象的CreateReadStream方法得到讀取文件的輸出流,進而得到文件的真實內容。

public interface IFileManager
{
    ...
    Task<string> ReadAllTextAsync(string path);
}

public class FileManager : IFileManager
{
    ...
    public async Task<string> ReadAllTextAsync(string path)
    {
        byte[] buffer;
        using (var stream = _fileProvider.GetFileInfo(path).CreateReadStream())
        {
            buffer = new byte[stream.Length];
            await stream.ReadAsync(buffer, 0, buffer.Length);
        }
        return Encoding.Default.GetString(buffer);
    }
}

假設我們依然將FileManager使用的IFileProvider映射為目錄“c:\test\”,現在我們在該目錄中創建一個名為data.txt的文本文件,並在該文件中任意寫入一些內容。接下來我們在Main方法中編寫了如下的程序利用依賴注入的方式得到FileManager對象,並讀取文件data.txt的內容。最終的調試斷言旨在確定通過IFileProvider讀取的確實就是目標文件的真實內容。(S502)

class Program
{
    static async Task Main()
    {
        var content = await new ServiceCollection()
            .AddSingleton<IFileProvider>(new PhysicalFileProvider(@"c:\test"))
            .AddSingleton<IFileManager, FileManager>()
            .BuildServiceProvider()
            .GetRequiredService<IFileManager>()
            .ReadAllTextAsync("data.txt");

        Debug.Assert(content == File.ReadAllText(@"c:\test\data.txt"));
    }
}

三、內嵌文件系統

我們一直在強調由IFileProvider結構構建的是一個抽象的具有目錄結構的文件系統,具體文件的提供方式取決於對具體的IFileProvider對象是怎樣一個類型。我們演示實例定義的FileManager並沒有限定具體使用何種類型的IFileProvider,該對象是在應用中通過依賴注入的方式指定的。由於上面的應用程序注入的是一個PhysicalFileProvider對象,所以我們可以利用它來讀取對應物理目錄下的某個文件。假設現在將這個data.txt直接以資源文件的形式編譯到程序集中,我們就需要使用另一個名為EmbeddedFileProvider的實現類型。現在我們直接將這個data.txt文件添加到控制台應用的項目根目錄下。在默認的情況下,當我們編譯項目的時候這樣的文件並不能成為內嵌到目標程序集的資源文件,我們需要利用VS將該文件的“Build Action”屬性按照如下所示的方式設置為“Embedded resource”。

上圖所示的設置將會體現在項目文件(.csproj文件)上。具體來說,項目文件會以如下的形式添加一個<EmbeddedResource>元素將文件data.txt設置為內嵌到編譯後生成的程序集的內嵌資源文件。

<Project Sdk="Microsoft.NET.Sdk">
  ...
  <ItemGroup>
      <EmbeddedResource Include="data.txt"/>   
  </ItemGroup>
</Project>

我們編寫了如下的程序來演示針對內嵌於程序集中的資源文件的讀取。我們首先得到當前入口程序集,並利用它創建了一個EmbeddedFileProvider對象,它代替原來的PhysicalFileProvider對象被註冊到ServiceCollection之中。我們接下來採用了完全一致的編程方式得到FileManager對象並利用它讀取內嵌文件data.txt的內容。為了驗證讀取的目標文件準確無誤,我們採用直接讀取資源文件的方式得到了內嵌文件data.txt的內容,並利用一個調試斷言確定兩者的一致性。(S503)

class Program
{
    static async Task Main()
    {
        var assembly = Assembly.GetEntryAssembly();

        var content1 = await new ServiceCollection()
            .AddSingleton<IFileProvider>(new EmbeddedFileProvider(assembly))
            .AddSingleton<IFileManager, FileManager>()
            .BuildServiceProvider()
            .GetRequiredService<IFileManager>()
            .ReadAllTextAsync("data.txt");

        var stream = assembly.GetManifestResourceStream($"{assembly.GetName().Name}.data.txt");
        var buffer = new byte[stream.Length];
        stream.Read(buffer, 0, buffer.Length);
        var content2 = Encoding.Default.GetString(buffer);

        Debug.Assert(content1 == content2);
    }
}

四、監控文件的變化

在文件讀取場景中,確定加載到內存中的數據與源文件的一致性並自動同步是一個很常見的需求。比如說我們將配置定義在一個JSON文件中,應用啟動的時候會讀取該文件並將其轉換成對應的Options對象。在很多情況下,如果我們改動了配置文件, 最新的配置數據只有在應用重啟之後才能生效。如果我們能夠以一種高效的方式對配置文件進行監控,並在其發生改變的情況下嚮應用發送通知,那麼應用就能在不用重啟的情況下重新讀取配置文件,進而實現Options對象承載的內容和原始配置文件完全同步。

對文件系統實施監控並在其發生改變時發送通知也是IFileProvider對象提供的核心功能之一。接下來我們依然使用前面這個程序來演示如何使用PhysicalFileProvider對某個物理文件實施監控,並在目標文件的內容發生改變的時候重新讀取新的內容。

class Program
{
    static async Task Main()
    {
        using (var fileProvider = new PhysicalFileProvider(@"c:\test"))
        {
            string original = null;
            ChangeToken.OnChange(() => fileProvider.Watch("data.txt"), Callback);
            while (true)
            {
                File.WriteAllText(@"c:\test\data.txt", DateTime.Now.ToString());
                await Task.Delay(5000);
            }

            async void Callback()
            {
                var stream = fileProvider.GetFileInfo("data.txt").CreateReadStream();
                {
                    var buffer = new byte[stream.Length];
                    await stream.ReadAsync(buffer, 0, buffer.Length);
                    string current = Encoding.Default.GetString(buffer);
                    if (current != original)
                    {
                        Console.WriteLine(original = current);
                    }
                }
            }
        }
    }
}

如上面的代碼片段所示,我們針對目錄“c:\test”創建了一個PhysicalFileProvider對象,並調用其Watch方法對指定的文件data.txt實施監控。該方法的返回一個IChangeToken對象,我們正是利用這個對象接收文件改變的通知。我們調用ChangeToken的靜態方法OnChange針對這個對象註冊了一個回調實現對源文件的重新讀取和显示,當源文件發生改變的時候,註冊的回調會自動執行。我們以每隔5秒的間隔對文件data.txt作一次修改,而文件的內容為當前時間。所以當我們的程序啟動之後,每隔5秒鐘當前時間就會以如下圖的方式呈現在控制台上。

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

【其他文章推薦】

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

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

大陸寄台灣空運注意事項

大陸海運台灣交貨時間多久?

企鵝出沒澳南島嶼 不時迷路跑到本土

7{icon} {views}

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

南半球的澳洲靠近南極洲附近的島嶼,經常會有野生企鵝出沒,有些企鵝迷路,就會不小心跑到澳洲本土的海灘上,吸引民眾圍觀。野生動物專家呼籲,千萬不要餵食企鵝,否則可能有害健康,也會妨礙牠們回歸大自然。

澳洲南部靠近南極洲的島嶼,總共有八種企鵝出沒,專家近年來發現經常有迷路的企鵝,跑到不該出現的澳洲本土岸邊。在伯斯附近的瑪格麗特河小鎮,就有一處企鵝保育基地,專門收容這些需要幫助的企鵝。

專家說,一般民眾如果發現落單或迷路的企鵝,可以趕快通知野生動物管理人員,但最好不要亂餵食。莫爾解釋,「在牠們獲得照料前,你不需要給牠們水或食物,只有照護人員知道什麼是適當的食物。」

生態保育
國際新聞
澳洲
企鵝

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

【其他文章推薦】

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

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

大陸寄台灣空運注意事項

大陸海運台灣交貨時間多久?

※避免吃悶虧無故遭抬價!台中搬家公司免費估價,有契約讓您安心有保障!

MySQL InnoDB MVCC

32{icon} {views}

MySQL 原理篇

MVCC

MVCC 的定義

MVCC(Multiversion concurrency control):多版本併發控制,併發訪問(讀或寫)數據庫時,對正在事務內處理的數據做多版本的管理。以達到用來避免寫操作的堵塞,從而引發讀操作的併發問題。

MVCC 邏輯流程

插入

MySQL 在每一行數據中都會默認添加一些隱藏列 DB_TRX_IDDB_ROLL_PT。

上面圖中的執行步驟如下:

  1. 手動開啟事務,從 InnoDB 引擎中獲取一個全局事務ID(1)
  2. 然後往 teacher 表中插入兩條數據,同時設置數據行的版本號為當前事務ID,刪除版本號為 NULL

思考:如果事務是自動提交的(SET AUTOCOMMIT = NO),且未手動開啟事務,執行如下兩條 SQL,插入的數據會是什麼樣子的?

INSERT INTO teacher (NAME, age) VALUE ('seven', 18) ;

INSERT INTO teacher (NAME, age) VALUE ('qingshan', 19) ;

因為事務是自動提交的,所以兩條插入語句會分別獲取事務ID,所以這裏插入的數據行的版本號是1和2。

刪除

上面圖中的執行步驟如下:

  1. 手動開啟事務,從 InnoDB 引擎中獲取一個全局事務ID(22)
  2. 然後執行一條刪除語句,InnoDB 會找到這條記錄,把它的刪除版本號設置為當前事務ID

修改

上面圖中的執行步驟如下:

  1. 手動開啟事務,從 InnoDB 引擎中獲取一個全局事務ID(33)
  2. 然後執行一條修改語句,InnoDB 會找到這條記錄,copy 一份原數據插入到表中,將新行數據的數據行的版本號的值設置為當前事務ID,將原行數據的刪除版本號的值設置為當前事務ID

查詢

上面圖中的執行步驟如下:

  1. 手動開啟事務,從 InnoDB 引擎中獲取一個全局事務ID(44)
  2. 根據數據查詢規則的描述
    1. 查找數據行版本早於當前事務版本的數據行,發現表中三行數據都滿足條件
    2. 查找刪除版本號要麼為 NULL,要麼大於當前事務版本號的記錄,發現只有最後一條數據滿足條件(1, seven, 19)

案例分析

數據準備:

CREATE TABLE `teacher` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(32) NOT NULL,
  `age` int(11) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4;

INSERT  INTO teacher(id,NAME,age) VALUES (1,'seven',18);
INSERT  INTO teacher(id,NAME,age) VALUES (2,'qingshan',20);

案例一

-- 事務A執行
BEGIN;                                     -- 1
SELECT * FROM teacher;                       -- 2
COMMIT;

--事務B執行
BEGIN;                                     -- 3
UPDATE teacher SET age =28 WHERE id=1;     -- 4
COMMIT;

案例一的執行步驟是:1,2,3,4,2,執行效果如下圖所示:

雖然在執行 3,4 步驟的時候更新 id=1 的數據,但是根據 MVCC 的查詢邏輯流程,再次執行2,獲取到的數據依然和第一次一樣。

案例二

-- 事務A執行
BEGIN;                                     -- 1
SELECT * FROM teacher;                       -- 2
COMMIT;

--事務B執行
BEGIN;                                     -- 3
UPDATE teacher SET age =28 WHERE id=1;     -- 4
COMMIT;

案例二的執行步驟是:3,4,1,2,執行效果如下圖所示:

根據 MVCC 的查詢邏輯流程,執行1,2,獲取到的數據是事務B未提交的數據,這個是有問題的。

分析了案例一和案例二,發現 MVCC 不能解決案例二的問題,InnoDB 會使用 Undo log 解決案例二的問題。

Undo Log

Undo Log 的定義

Undo:意為取消,以撤銷操作為目的,返回指定某個狀態的操作。

Undo Log:數據庫事務提交之前,會將事務修改數據的鏡像(即修改前的舊版本)存放到 undo 日誌里,當事務回滾時,或者數據庫奔潰時,可以利用 undo 日誌,即舊版本數據,撤銷未提交事務對數據庫產生的影響。。

  • 對於 insert 操作,undo 日誌記錄新數據的 PK(ROW_ID),回滾時直接刪除;
  • 對於 delete/update 操作,undo 日誌記錄舊數據 row,回滾時直接恢復;
  • 他們分別存放在不同的buffer里。

Undo Log 是為了實現事務的原子性而出現的產物。

 

Undo Log 實現事務原子性:事務處理過程中,如果出現了錯誤或者用戶執行了 ROLLBACK 語句,MySQL 可以利用 Undo Log 中的備份將數據恢復到事務開始之前的狀態。

InnoDB 發現可以基於 Undo Log 來實現多版本併發控制。

Undo Log 在 MySQL InnoDB 存儲引擎中用來實現多版本併發控制。

 

Undo Log 實現多版本併發控制:事務未提交之前,Undo Log 保存了未提交之前的版本數據,Undo Log 中的數據可作為數據舊版本快照供其他併發事務進行快照讀。

分析下圖中 SQL 的執行過程。

  • 事務A手動開啟事務,執行更新操作,首先會把更新命中的數據拷貝到 Undo Buffer 中
  • 事務B手動開啟事務,執行查詢操作,會讀取 Undo Log 中數據返回,進行快照度

當前讀和快照讀

快照讀

SQL 讀取的數據是快照版本,也就是歷史版本,普通的 SELECT 就是快照讀。

InnoDB 快照讀,數據的讀取將由 cache(原本數據)+ Undo Log(事務修改過的數據)兩部分組成。

當前讀

SQL 讀取的數據是最新版本,通過鎖機制來保證讀取的數據無法通過其他事務進行修改。

UPDATE 、DELETE 、INSERT 、SELECT … LOCK IN SHARE MODE 、SELECT … FOR UPDATE 都是當前讀,這些操作在《MySQL InnoDB 鎖》這篇文章中有過演示,事務A執行這些 SQL,會阻塞事務B的 SQL 執行。

在 InnoDB 引擎裏面,快照讀通過 MVCC 解決幻讀的問題,當前讀通過 Next-Key Locks 解決幻讀的問題。

Redo Log

Redo Log 的定義

Redo:顧名思義就是重做。以恢復操作為目的,重現操作。

Redo Log:指事務中操作的任何數據,將最新的數據備份到一個地方(Redo Log)。

Redo Log 的持久化:不是隨着事務的提交才寫入的,而是在事務的執行過程中,便開始寫入 Redo Log 中,具體的落盤策略可以進行配置。

Redo Log 是為了實現事務的持久性而出現的產物。

Redo Log 實現事務持久性:防止在發生故障的時間點,尚有臟頁未寫入表的 IBD 文件中,在重啟 MySQL 服務的時候,根據 Redo Log 進行重做,從而達到事務的未入磁盤數據進行持久化這一特性。

根據下圖分析 Redo Log 的執行流程

InnoDB 不是每一次提交事務都把數據從緩存區持久化到硬盤的,因為每次提交事務都把數據持久化到硬盤,效率很低,每一次持久化都需要執行 IO 操作。

InnoDB 會把每次數據變化會先進入 Redo Buffer 中,事務提交了,會根據策略把新的數據寫入 Redo Log 中,InnoDB 就會認為這次事務提交成功了,數據並不一定馬上就進入表的 IBD 文件中。

疑問:持久化到 Redo Log 中和持久化到表的 IBD 文件一樣都是 IO 操作,為什麼要設計 Redo Log 呢?

其實是因為持久化到 Redo Log 中是順序 IO 的操作,而持久化到表的 IBD 文件中是一個隨機 IO 的操作,比如我們需要更新 id=1 和 id=8 的數據,如果是 Redo Log,就只需要把更新的數據順序存入 Redo Log 中;但如果是表的 IBD 文件,就需要先找到 id=1 和 id=8 的兩個不連續的磁盤文件地址,再做持久化操作,影響數據庫服務的併發性能。

Redo Log 的持久化配置

指定 Redo Log 記錄在 {datadir}/ib_logfile1 和 ib_logfile2 兩個文件中,可以通過 innodb_log_group_home_dir配置指定目錄存儲。

一旦事務成功提交且數據持久化到表的 IBD 文件中之後,此時 Redo Log 中的對應事務數據記錄就失去了意義,所 以 Redo Log 的寫入是日誌文件循環寫入的過程,也就是覆蓋寫的過程。

  • 指定 Redo Log 日誌文件組中的數量 innodb_log_files_in_group 默認為2
  • 指定 Redo Log 每一個日誌文件最大存儲量 innodb_log_file_size 默認48M
  • 指定 Redo Log 在 cache/buffer 中的 buffer 池大小 innodb_log_buffer_size 默認16M

Redo Buffer 持久化到 Redo Log 的策略,通過設置 Innodb_flush_log_at_trx_commit 的值:

  • 取值0:每秒提交 Redo buffer -> Redo Log OS cache -> flush cache to disk,可能丟失一秒內的事務數據。
  • 取值1(默認值):每次事務提交執行 Redo Buffer -> Redo Log OS cache -> flush cache to disk,最安全,性能最差的方式
  • 取值2:每次事務提交執行 Redo Buffer -> Redo log OS cache 再每一秒執行 -> flush cache to disk 操作

一般建議選擇取值2,因為 MySQL 掛了最多損失一次事務提交的數據,整個服務期掛了才會損失一秒的事務提交數據。

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

【其他文章推薦】

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

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

大陸寄台灣空運注意事項

大陸海運台灣交貨時間多久?

※避免吃悶虧無故遭抬價!台中搬家公司免費估價,有契約讓您安心有保障!

鮪魚業刺網混獲最大苦主 研究:印度洋海豚數量減少近90%

7{icon} {views}

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

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

【其他文章推薦】

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

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

大陸寄台灣空運注意事項

大陸海運台灣交貨時間多久?

※避免吃悶虧無故遭抬價!台中搬家公司免費估價,有契約讓您安心有保障!

併發編程-深入淺出AQS

26{icon} {views}

AQS是併發編程中非常重要的概念,它是juc包下的許多併發工具類,如CountdownLatch,CyclicBarrier,Semaphore 和鎖, 如ReentrantLock, ReaderWriterLock的實現基礎,提供了一個基於int狀態碼和隊列來實現的併發框架。本文將對AQS框架的幾個重要組成進行簡要介紹,讀完本文你將get到以下幾個點:

  1. AQS進行併發控制的機制是什麼

  2. AQS獨佔和共享模式是如何實現的

  3. 同步隊列和條件等待隊列的區別,和數據出入隊原則

一,AQS基本概念

AQS(AbstractQueuedSynchronizer)是用來構建鎖或者其他同步組件的基礎框架,它使用了一個int成員變量來表示狀態,通過內置的FIFO(first in,first out)隊列來完成資源獲取線程的排隊工作。

隊列可分為兩種,一種是同步隊列,是程序執行入口出處的等待隊列;而另一種則是條件等待隊列,隊列中的元素是在程序執行時在某個條件上發生等待。

1.1 獨佔or共享模式

AQS支持兩種獲取同步狀態的模式既獨佔式和共享式。顧名思義,獨佔式模式同一時刻只允許一個線程獲取同步狀態,而共享模式則允許多個線程同時獲取。

1.2 同步隊列

當一個線程嘗試獲取同步狀態失敗時,同步器會將這個線程以及等待狀態等信息構造成一個節點加入到等待隊列中,同時會阻塞當前線程,當同步狀態釋放時,會把首節點中的線程喚醒,使其再次嘗試重複獲取同步隊列。

1.3 條件隊列

AQS內部類ConditionObject來實現的條件隊列,當一個線程獲取到同步狀態,但是卻通過Condition調用了await相關的方法時,會將該線程封裝成Node節點並加入到條件隊列中,它的結構和同步隊列相同。

二,獨佔or共享模式

AQS框架中,通過維護一個int類型的狀態,來進行併發控制,線程通常通過修改此狀態信息來表明當前線程持有此同步狀態。AQS則是通過保存修改狀態線程的引用來實現獨佔和共享模式的。

/**
 * 獲取同步狀態
 */
public final void acquire(int arg) {
    //嘗試獲取同步狀態, 如果嘗試獲取到同步狀態失敗,則加入到同步隊列中
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}
/**
 * 嘗試獲取同步狀態【子類中實現】,因為aqs基於模板模式,僅提供基於狀態和同步隊列的實 
 * 現思路,具體的實現由子類決定
 */
protected final boolean tryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        // 如果當前狀態值為0,並且等待隊列中沒有元素,執行修改狀態值操作
        if (!hasQueuedPredecessors() &&
            compareAndSetState(0, acquires)) {
            // 修改狀態值成功,記錄當前持有同步狀態的線程信息
            setExclusiveOwnerThread(current);
            return true;
        }
        // 如果當前線程已經持有同步狀態,繼續修改同步狀態【重入鎖實現原理】
    } else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        if (nextc < 0)
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}

/**
 * 根據傳入的模式以及當前線程信息創建一個隊列的節點並加入到同步隊列尾部
 */
private Node addWaiter(Node mode) {
    Node node = new Node(Thread.currentThread(), mode);
    // Try the fast path of enq; backup to full enq on failure
    Node pred = tail;
    if (pred != null) {
        node.prev = pred;
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
    enq(node);
    return node;
}
/**
 * 同步隊列中節點,嘗試獲取同步狀態
 */
final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        // 自旋(死循環)
        for (;;) {
            // 只有當前節點的前驅節點是頭節點時才會嘗試執行獲取同步狀態操作
            final Node p = node.predecessor();
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

獨佔式是如何控製得?

當修改狀態信息成功后,如果執行的是獨佔式操作,AQS的具體實現類中會保存當前線程的信息來聲明同步狀態已被當前線程佔用,此時其他線程再嘗試獲取同步狀態會返回false。

三,同步隊列

3.1 隊列中保存那些信息?

同步隊列節點中主要保存着線程的信息以及模式(共享or獨佔)。

3.2 何時執行入隊操作?

/**
 * 獲取同步狀態
 */
public final void acquire(int arg) {
    //嘗試獲取同步狀態, 如果嘗試獲取到同步狀態失敗,則加入到同步隊列中
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

復用上文中的代碼,不難看出再獲取同步狀態失敗后,會執行入隊操作。

3.3 何時執行出隊操作?

當線程獲取同步狀態失敗時,會被封裝成Node節點加入到等待隊列中,此時所有節點都回進入自旋過程,首先判斷自己prev是否時頭節點,如果是則嘗試獲取同步狀態。
被阻塞線程的喚醒主要以靠前驅節點的出隊或阻塞線程被中斷來實現。

/**
 * 同步隊列中節點,嘗試獲取同步狀態
 * 
 * 1. 當一個線程獲取到同步狀態時,會將當前線程構造程Node並設置為頭節點
 * 2. 並將原始的head節點設置為null,以便於垃圾回收
 */
final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            final Node p = node.predecessor();
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

四,條件等待隊列

條件變量(ConidtionObject)是AQS中的一個內部類,用來實現同步隊列機制。同步隊列復用了等待隊列中Node節點,所以同步隊列到等待隊列中不需要進行額外的轉換。

4.1 什麼時候執行入隊操作?

當線程獲取到同步狀態,但是在臨界區中調用了await()方法,此時該線程會被加入到對應的條件隊列匯總。
ps: 臨界區,加鎖和釋放鎖之間的代碼區域

/**
 * ConditionObject中的await方法,調用后使得當前執行線程加入條件等待隊列
 */
public final void await() throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    Node node = addConditionWaiter();
    // -----省略代碼------
}
/**
 * 添加等待線程
 */
private Node addConditionWaiter() {
    Node t = lastWaiter;
    // -----省略代碼------
    // 將當前線程構造程條件隊列節點,並加入到隊列中
    Node node = new Node(Thread.currentThread(), Node.CONDITION);
    if (t == null)
        firstWaiter = node;
    else
        t.nextWaiter = node;
    lastWaiter = node;
    return node;
}

4.2 什麼時候執行出隊操作?

當對應的Conditioni調用signial/signalAll()方法時回選擇從條件隊列中出隊列,同步隊列是通過自旋的方式獲取同步狀態,而條件隊列中的節點則通過通知的方式出隊。條件隊列中的節點被喚醒後會加入到入口等待隊列中。

/**
 * 喚醒當前條件等到隊列中的所有等待線程
 */
public final void signalAll() {
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    Node first = firstWaiter;
    if (first != null)
        doSignalAll(first);
}
/**
 * 遍歷隊列,將元素從條件隊列 加入到 同步隊列
 */
private void doSignalAll(Node first) {
    lastWaiter = firstWaiter = null;
    do {
        Node next = first.nextWaiter;
        first.nextWaiter = null;
        transferForSignal(first);
        first = next;
    } while (first != null);
}
final boolean transferForSignal(Node node) {
    // -----省略代碼------
    // 執行入隊操作,將node添加到同步隊列中
    Node p = enq(node);
    int ws = p.waitStatus;
    if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
        LockSupport.unpark(node.thread);
    return true;
}

五,總結

  1. 使用Node實現的FIFO隊列,可以用於構建鎖或者其他同步裝置的基礎框架
  2. 利用一個int類型的屬性表示狀態
  3. 使用模板方法模式,子類可以通過繼承它來管理狀態實現各種併發工具
  4. 可以同時實現獨佔和共享模式

本文對AQS的基本原理進行的簡要的描述,對於子類的公平性和非公平行實現,中斷,隊列中節點的等待狀態,cas等操作沒有進行探討,感興趣的小夥伴可以進行源碼閱讀或者查閱相關資料。

六,Q&A

Question1: 在java中通常使用synchronized來實現方法同步,AQS中通過CAS保證了修改同步狀態的一致性問題,那麼對比synchronized,cas有什麼優勢不同與優勢呢?你還知道其他無鎖併發的策略嗎?

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

【其他文章推薦】

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

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

大陸寄台灣空運注意事項

大陸海運台灣交貨時間多久?

※避免吃悶虧無故遭抬價!台中搬家公司免費估價,有契約讓您安心有保障!

日本男童「免農藥除草大作戰」 農夫大讚:非常有效

10{icon} {views}

摘錄自2020年3月24日中時報導

一名日本網友在推特表示,她在家中庭院內種植秋葵,但只要一到夏季,雜草就會生長得特別快,而她就讀小學的兒子看到這種情況,便主動說要以「除草」為主題,當作暑假作業來做研究,而且要「不使用農藥」。

兒子在三年間嘗試過多種方法,都無法完全清除雜草。某一天他突然靈機一動,發現土壤的軟硬度是導致雜草生長的關鍵,於是他決定每天在土壤上跑步。從今年的1月5日起到3月1日,不論晴雨,他每天都在土壤上奔跑,每次跑30分鐘,沒想到短短2個月後,雜草就不再生長了。

也有農民認同此方法「身為一名農民,這是非常有效的除草方法,可在雜草種子發芽時挪動土壤,在雜草生根前徹底將其消滅。」

環境經濟
農林漁牧業
國際新聞
日本
除草

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

【其他文章推薦】

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

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

大陸寄台灣空運注意事項

大陸海運台灣交貨時間多久?

※避免吃悶虧無故遭抬價!台中搬家公司免費估價,有契約讓您安心有保障!