MYSQL事务隔离级别

Mysql有四种事务隔离级别,分别是:
Read Uncommitted
Read Committed
Repeatable Read
Serializable

关于这四个隔离级别的介绍:

未提交读(READ UNCOMMITTED)。另一个事务修改了数据,但尚未提交,而本事务中的SELECT会读到这些未被提交的数据(脏读)。

提交读(READ COMMITTED)。本事务读取到的是最新的数据(其他事务提交后的)。问题是,在同一个事务里,前后两次相同的SELECT会读到不同的结果(不重复读)。

可重复读(REPEATABLE READ)。在同一个事务里,SELECT的结果是事务开始时时间点的状态,因此,同样的SELECT操作读到的结果会是一致的。但是,会有幻读现象(稍后解释)。

串行化(SERIALIZABLE)。读操作会隐式获取共享锁,可以保证不同事务间的互斥。

这四个级别逐渐增强,每个级别解决一个问题

脏读,最容易理解。另一个事务修改了数据,但尚未提交,而本事务中的SELECT会读到这些未被提交的数据。

不重复读。解决了脏读后,会遇到,同一个事务执行过程中,另外一个事务提交了新数据,因此本事务先后两次读到的数据结果会不一致。

幻读。解决了不重复读,保证了同一个事务里,查询的结果都是事务开始时的状态(一致性)。但是,如果另一个事务同时提交了新数据,本事务再更新时,就会“惊奇的”发现了这些新数据,貌似之前读到的数据是“鬼影”一样的幻觉。

可以通过select @@tx_isolation来查看当前隔离级别

通过set [ global | session ] transaction isolation level Read uncommitted | Read committed | Repeatable | Serializable来修改事务隔离级别

如果选择global,意思是此语句将应用于之后的所有session,而当前已经存在的session不受影响。

如果选择session,意思是此语句将应用于当前session内之后的所有事务。

如果什么都不写,意思是此语句将应用于当前session内的下一个还未开始的事务。

采用Arduino IDE对ESP8266进行编程

话说ESP8266真是个神奇的芯片,不仅可以使用NodeMCU和micropython等固件,还可以直接支持采用Arduino IDE环境进行编程和烧写。本身又有wifi和gpio,又便宜。简直是折腾神器。

采用Arduino编程的最大好处是,很多库可以直接引用。不用再写一些很底层的代码去驱动外设。这大大提高的开发效率,并且像上篇文章里写的,采用lua语言的NodeMCU还是有一些局限性的。比如不支持微秒级的延时。

要采用Arduino对ESP8266进行编程,首先要有一个1.6.4版本以上的Arduino IDE

其次在设置里需要把additional board manager URLs 设置为http://arduino.esp8266.com/stable/package_esp8266com_index.json

这样在工具里的开发板选择里就有了NodeMCU1.0选项。

下载完工具链就可以直接编写了。

下面是用Arduino写的gp2y1010au0f粉尘检测来测AQI的代码。测试通过

/*
   NodeMCU连接夏普GP2Y1010AU0F空气质量传感器检测PM2.5
*/
/* 定义引脚 */
#define PIN_DATA_OUT A0 //连接空气质量传感器模拟量输出的IO口, NodeMCU只有A0可以作为ADC
#define PIN_LED_VCC 5 //空气质量传感器中为内部Led供电的引脚
/* 定义时间 */
const int DELAY_BEFORE_SAMPLING = 280; //采样前等待时间
const int DELAY_AFTER_SAMPLING = 40; //采样后等待时间
const int DELAY_LED_OFF = 9680; //间隔时间
/**
   读取输出电压
*/
double getOutputV() {
  digitalWrite(PIN_LED_VCC, LOW);
  delayMicroseconds(DELAY_BEFORE_SAMPLING);
  double analogOutput = analogRead(PIN_DATA_OUT);
  delayMicroseconds(DELAY_AFTER_SAMPLING);
  digitalWrite(PIN_LED_VCC, HIGH);
  delayMicroseconds(DELAY_LED_OFF);
  //Arduino模拟量读取值的范围为0~1023,以下换算为0~5v
  double outputV = analogOutput / 1024 * 5;
  return outputV;
}
/**
   根据输出电压计算灰尘密度
*/
double getDustDensity(double outputV) {
  //输出电压和灰尘密度换算公式: ug/m3 = (V - 0.9) / 5 * 1000
  double ugm3 = (outputV - 0.9) / 5 * 1000;
  //去除检测不到的范围
  if (ugm3 < 0) {
    ugm3 = 0;
  }
  return ugm3;
}
/**
   根据灰尘密度计算AQI
   环境空气质量指数(AQI)技术规定(试行)](http://kjs.mep.gov.cn/hjbhbz/bzwb/dqhjbh/jcgfffbz/201203/t20120302_224166.htm
*/
double getAQI(double ugm3) {
  double aqiL = 0;
  double aqiH = 0;
  double bpL = 0;
  double bpH = 0;
  double aqi = 0;
  //根据pm2.5和aqi对应关系分别计算aqi
  if (ugm3 >= 0 && ugm3 <= 35) {
    aqiL = 0;
    aqiH = 50;
    bpL = 0;
    bpH = 35;
  } else if (ugm3 > 35 && ugm3 <= 75) {
    aqiL = 50;
    aqiH = 100;
    bpL = 35;
    bpH = 75;
  } else if (ugm3 > 75 && ugm3 <= 115) {
    aqiL = 100;
    aqiH = 150;
    bpL = 75;
    bpH = 115;
  } else if (ugm3 > 115 && ugm3 <= 150) {
    aqiL = 150;
    aqiH = 200;
    bpL = 115;
    bpH = 150;
  } else if (ugm3 > 150 && ugm3 <= 250) {
    aqiL = 200;
    aqiH = 300;
    bpL = 150;
    bpH = 250;
  } else if (ugm3 > 250 && ugm3 <= 350) {
    aqiL = 300;
    aqiH = 400;
    bpL = 250;
    bpH = 350;
  } else if (ugm3 > 350) {
    aqiL = 400;
    aqiH = 500;
    bpL = 350;
    bpH = 500;
  }
  //公式aqi = (aqiH - aqiL) / (bpH - bpL) * (desity - bpL) + aqiL;
  aqi = (aqiH - aqiL) / (bpH - bpL) * (ugm3 - bpL) + aqiL;
  return aqi;
}
/**
   根据aqi获取级别描述
*/
String getGradeInfo(double aqi) {
  String gradeInfo;
  if (aqi >= 0 && aqi <= 50) {
    gradeInfo = String("Perfect");
  } else if (aqi > 50 && aqi <= 100) {
    gradeInfo = String("Good");
  } else if (aqi > 100 && aqi <= 150) {
    gradeInfo = String("Mild polluted");
  } else if (aqi > 150 && aqi <= 200) {
    gradeInfo = String("Medium polluted");
  } else if (aqi > 200 && aqi <= 300) {
    gradeInfo = String("Heavily polluted");
  } else if (aqi > 300 && aqi <= 500) {
    gradeInfo = String("Severely polluted");
  } else {
    gradeInfo = String("Broken roof!!!");
  }
  return gradeInfo;
}
void setup() {
  Serial.begin(115200);
  pinMode(PIN_DATA_OUT, INPUT); //定义为输入(ADC读取模拟量)
  pinMode(PIN_LED_VCC, OUTPUT); //定义为输出
}
void loop() {
  double outputV = getOutputV(); //采样获取输出电压
  double ugm3 = getDustDensity(outputV); //计算灰尘浓度
  double aqi = getAQI(ugm3); //计算aqi
  String gradeInfo = getGradeInfo(aqi); //计算级别
  //打印到串口
  Serial.println(String("outputV=") + outputV + "\tug/m3=" + ugm3 + "\tAQI=" + aqi + "\tgradeInfo=" + gradeInfo);
  //间隔1秒执行下次检测
  delay(1000);
}

采用NodeMCU和GP2Y1010AU0F检测空气质量

夏普GP2Y1010AU0F传感器用于检测空气中的灰尘浓度。可以检测非常细小的灰尘,例如香烟烟雾(粒径0.1~2um)。
该传感器中心有一个孔洞,可使空气自由穿过。内部有一个LED向孔洞进行照射。当空气中的灰尘穿过孔洞时,光线反射到接收端,通过放大电路将反射光强放大并转化为输出电压。通过测量输出电压并进行响应的换算,即可得知空气中灰尘的浓度。

GP2Y1010AU0F引脚连接

(1)V-LED + 150ohm电阻 + 220uF电容->VCC
(2)LED-GND->GND
(3)LED->D1(GPIO 5)
(4)S-GND->GND
(5)VO->A0
(6)VCC->VCC

其中需要加150ohm电阻和220uF电容,连接方式如下图中红框所示:

connect

可以直接使用NodeMCU的adc即A0接口进行采样。先对GPIO 5拉低产生一个采样脉冲,读取A0的adc数据,即为输出电压。转换后可得粉尘浓度。代码如下

pin1 = 1
gpio.mode(pin1, gpio.OUTPUT) 
tmr.alarm(1, 2000, 1, function()
    gpio.write(pin1,gpio.LOW)
    adcv = adc.read(0)
    gpio.write(pin1,gpio.HIGH)
    outv = adcv/ 1024 * 5
    ugm3 = (outv - 0.9) / 5 * 1000
    if (ugm3 >= 0 and ugm3 <= 35) then
	    aqiL = 0
	    aqiH = 50
	    bpL = 0
	    bpH = 35
		aqi = (aqiH - aqiL) / (bpH - bpL) * (ugm3 - bpL) + aqiL
	elseif (ugm3 > 35 and ugm3 <= 75) then
	    aqiL = 50
	    aqiH = 100
	    bpL = 35
	    bpH = 75
		aqi = (aqiH - aqiL) / (bpH - bpL) * (ugm3 - bpL) + aqiL
	elseif (ugm3 > 75 and ugm3 <= 115) then
	    aqiL = 100
	    aqiH = 150
	    bpL = 75
	    bpH = 115
	    aqi = (aqiH - aqiL) / (bpH - bpL) * (ugm3 - bpL) + aqiL
	elseif (ugm3 > 115 and ugm3 <= 150) then
	    aqiL = 150
	    aqiH = 200
	    bpL = 115
	    bpH = 150
	    aqi = (aqiH - aqiL) / (bpH - bpL) * (ugm3 - bpL) + aqiL
	elseif (ugm3 > 150 and ugm3 <= 250) then
	    aqiL = 200
	    aqiH = 300
	    bpL = 150
	    bpH = 250
	    aqi = (aqiH - aqiL) / (bpH - bpL) * (ugm3 - bpL) + aqiL
	elseif (ugm3 > 250 and ugm3 <= 350) then
	    aqiL = 300
	    aqiH = 400
	    bpL = 250
	    bpH = 350
	    aqi = (aqiH - aqiL) / (bpH - bpL) * (ugm3 - bpL) + aqiL
	elseif (ugm3 > 350) then
	    aqiL = 400
	    aqiH = 500
	    bpL = 350
	    bpH = 500
	    aqi = (aqiH - aqiL) / (bpH - bpL) * (ugm3 - bpL) + aqiL
	else
		aqi = 0
	end
	print(aqi)
end)

其中adcv/ 1024 * 5和(outv - 0.9) / 5 * 1000为1024级采样精度转换为电压和相应电压下的粉尘浓度换算,具体可以查阅datasheet得到。如果换算后为负数,即可认为浓度太低检测不到。

下方的aqi换算公式为国家标准环境空气质量指数(AQI)技术规定(试行)( HJ 633—2012 2016-01-01实施)。

实际运行时发现数据不稳定,时有时无。查阅资料发现LED开启过程中有一个上升期。当LED开启持续0.28ms时,对输出电压进行采样最为准确。而NodeMCU的elua脚本并不支持微秒级别的延时。于是考虑其他方法。

查阅资料发现在不更换其他芯片单片机的情况下,ESP8266这块芯片可以采用Arduino的IDE进行C语言程序的编写。下一篇文章会具体写一下如何采用Arduino IDE环境对ESP8266进行编程。

使用NodeMCU和AM2302(DHT22)制作的在线温湿度监控系统

NodeMCU可以用了,而手头上还有一块AM2302(DHT22)的温湿度测量模块。于是就想折腾一个可以实时监控温湿度并且上传到服务器,存到数据库供随时查看变化的温湿度监控系统。

首先查阅了NodeMCU的文档,初步确定了使用wifi模块连接wifi,http模块负责利用post上传数据,net模块来建立本地服务器供实时查看,tmr模块来做定时器定时获取数据,DHT模块来读取DHT22的数据,所以重新编译了内置了DHT Module的NodeMCU固件刷入。

首先在服务器上建立了一个数据库的两个数据表来存储相应数据,其中station表存储不同的NodeMCU模块的IP和上线时间,以供未来扩展多个监测点的时候使用,而data表存储具体的温度湿度和数据采集时间。

硬件连接首先是AM2302(DHT22)的VCC和GND分别接NodeMCU的3.3和GND输出。其次是DATA和NodeMCU的D5口相连,即可。

这里定义dhtpin=5

首先通过

wifi.setmode(wifi.STATION)
wifi.sta.config("wifi SSID","password")
wifi.sta.connect()

连接wifi,不要忘记设定dns地址

net.dns.setdnsserver("114.114.114.114", 0)

并且做了一个定时器,每隔一秒确认是否连接成功,连接成功以后会像服务器发起post请求把自己的IP和station ID汇报给服务器,代表监测站上线成功。其中监测站id即station变量我以地理位置+chipid来定义。

tmr.alarm(2, 1000, 1, function()
if wifi.sta.status()==5 then
http.post('URL',
      'Content-Type: application/json\r\n',
      '{"station":"'..station..'","ip":"'..wifi.sta.getip()..'","pass":"password"}',
      function(code, data)
        if (code < 0) then
          print("HTTP request failed")
        else
            tmr.stop(2)
          print(code, data)
        end
      end)
 else
    print("connecting")
 end
end)

json中的pass是为了服务器做简单的安全性校验用。
接下来是建立一个简单的服务器以供本地实时查询,代码如下

srv = net.createServer(net.TCP)
srv:listen(80, function(conn)
    conn:on("receive", function(sck, payload)
        gpio.write(led,gpio.LOW)
        status, temp, humi, temp_dec, humi_dec = dht.read(dhtpin)
        if status == dht.OK then
            sck:send("HTTP/1.0 200 OK\r\nContent-Type: text/html\r\n\r\n<h1>DHT Temperature:"..temp..";".."Humidity:"..humi.."</h1>")
            gpio.write(led,gpio.HIGH)
        end
        end)
    conn:on("sent", function(sck) sck:close() end)
end)

其中status, temp, humi, temp_dec, humi_dec = dht.read(dhtpin)为DHT模块读取语句

最后是建立另一个定时器,定时采集温湿度参数并且发送给服务器,代码为一分钟间隔采集。

tmr.alarm(1, 60000, 1, function()
    status, temp, humi, temp_dec, humi_dec = dht.read(dhtpin)
    if status == dht.OK then
           http.post('url',
          'Content-Type: application/json\r\n',
          '{"temp":"'..temp..'","humi":"'..humi..'","station":"'..station..'","pass":"tms"}',
          function(code, data)
            if (code < 0) then
              print("HTTP request failed")
            else
              print(code, data)
            end
          end)
    end
end)

至此客户端的代码就算告一段落了。服务端代码无非是数据库插入,查询,echarts展示。

效果参见http://tms.im/Mo/tms.php

成品图
TH

折腾NodeMCU

从淘宝买回来一个CP2102+ESP8266的小模块准备刷NodeMCU固件玩下,却发现插上电脑以后没有任何反应,本来应该电脑提示发现新硬件,但是试了多次均没有。

装驱动,找文档,看了好久都没有类似这种现象的发生,找淘宝店家也不理我。只好自己折腾下。

nodemcu

经过测试发现,flash按钮和复位按钮是有效的,于是推测是USB转UART芯片CP2102坏掉了导致电脑不识别这块板。而ESP8266应该是完好的。

查了一下NodeMCU的资料发现这货其实已经把ESP8266的全部引脚都引出了。既然这样,为何不自己做外围下载电路和UART模块来救活这个ESP8266。

翻了一下抽屉,发现手头上只有一块古老的PL-2303。于是用他做了一个USB转UART的电路。对照这块板的pinmap把RX->TXD0和TX->RXD0,VCC->+5V和GND->GND分别接好。

pinmap

连上电脑,发现了虚拟串口设备,装好PL2303的驱动,打开ESP8266Flasher,选择好编译好的NodeMCU固件点击刷入。按住板子上的flash后点按reset。电脑成功识别到了ESP8266。

经过十几分钟的等待,终于刷入成功。

迫不及待的打开ESPlorer,连接成功。果然是CP2102的锅。终于救活了这块ESP8266,接下来就可以好好折腾NodeMCU了。