多线程并发读写MySql数据库可能出现脏读的问题

在多线程中使用同一mysql连接,一边读取数据库,一般修改数据库(insert,update,delete) 可能出现程序脏读的问题。

线程一:

delete from tb where ...

insert into tb... 

线程二:

select * from tb 

我们可以在查询中增加锁机制【lock in share mode】

select * from tb lock in share mode

共享锁 (lock in share mode)

允许不同事务之前共享加锁读取,但不允许其它事务修改或者加入排他锁
如果有修改必须等待一个事务提交完成,才可以执行,容易出现死锁

排他锁 (for update)

当一个事物加入排他锁后,不允许其他事务加共享锁或者排它锁读取,更加不允许其他事务修改加锁的行。

本文只是对共享锁进行测试

新建C#控制台应用程序MultiThreadMySqlDemo。

一、新建枚举DatabaseType

源程序如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace MultiThreadMySqlDemo
{
    /// <summary>
    /// 数据库类型
    /// </summary>
    public enum DatabaseType
    {
        /// <summary>
        /// 微软SqlServer数据库
        /// </summary>
        SqlServer = 0,
        /// <summary>
        /// Oracle数据库
        /// </summary>
        Oracle = 1,
        /// <summary>
        /// MySql数据库
        /// </summary>
        MySql = 2,
        /// <summary>
        /// 轻量级SQLite数据库,多用于本地保存数据
        /// </summary>
        SQLite = 3,
        /// <summary>
        /// PostgreSql数据库
        /// </summary>
        PostgreSql = 4,
        /// <summary>
        /// IBM DB2数据
        /// </summary>
        DB2 = 5,
        /// <summary>
        /// IBM Informix数据库
        /// </summary>
        Informix = 6,
        /// <summary>
        /// 对象链接和嵌入数据库:如Access数据库
        /// </summary>
        OleDb = 7,
        /// <summary>
        /// 开放数据库互连:Open Database Connectivity
        /// </summary>
        ODBC = 8
    }
}

二、数据库工厂类ProviderFactory

using System;
using System.Collections.Generic;
using System.Data.Common;
using System.Linq;
using System.Text;

namespace MultiThreadMySqlDemo
{
    /// <summary>
    /// 数据库工厂,为不同类型的数据源类进行实例化
    /// </summary>
    public class ProviderFactory
    {
        private static Dictionary<DatabaseType, string> providerInvariantNames = new Dictionary<DatabaseType, string>();
        private static Dictionary<DatabaseType, DbProviderFactory> providerFactory = new Dictionary<DatabaseType, DbProviderFactory>();

        static ProviderFactory()
        {
            //加载已知的数据库访问类的程序集  
            providerInvariantNames.Add(DatabaseType.SqlServer, "System.Data.SqlClient");
            providerInvariantNames.Add(DatabaseType.Oracle, "Oracle.DataAccess.Client");
            providerInvariantNames.Add(DatabaseType.MySql, "MySql.Data.MySqlClient");
            providerInvariantNames.Add(DatabaseType.SQLite, "System.Data.SQLite");
            providerInvariantNames.Add(DatabaseType.PostgreSql, "Npgsql");
            providerInvariantNames.Add(DatabaseType.DB2, "IBM.Data.DB2.iSeries");
            providerInvariantNames.Add(DatabaseType.Informix, "IBM.Data.Informix");
            providerInvariantNames.Add(DatabaseType.OleDb, "System.Data.OleDb");
            providerInvariantNames.Add(DatabaseType.ODBC, "System.Data.ODBC");
        }

        /// <summary>
        /// 获取指定数据库类型对应的程序集名称  
        /// </summary>
        /// <param name="providerType"></param>
        /// <returns></returns>
        public static string GetProviderInvariantName(DatabaseType providerType)
        {
            return providerInvariantNames[providerType];
        }

        /// <summary>
        /// 获取指定类型的数据库对应的DbProviderFactory   
        /// </summary>
        /// <param name="providerType"></param>
        /// <returns></returns>
        public static DbProviderFactory GetDbProviderFactory(DatabaseType providerType)
        {
            //如果还没有加载,则加载该DbProviderFactory   
            if (!providerFactory.ContainsKey(providerType))
            {
                providerFactory.Add(providerType, ImportDbProviderFactory(providerType));
            }
            return providerFactory[providerType];
        }
        /// <summary>   
        /// 加载指定数据库类型的DbProviderFactory   
        /// </summary>   
        /// <param name="providerType">数据库类型枚举</param>   
        /// <returns></returns>   
        private static DbProviderFactory ImportDbProviderFactory(DatabaseType providerType)
        {
            string providerName = providerInvariantNames[providerType];
            DbProviderFactory factory = null;
            try
            {
                //invariant 不变的,固定的  variant 变化的,不同的,变量 
                //从全局程序集中查找,如果使用的dll(如:System.Data.SQLite.dll,该类库需要放在bin\debug目录下) 是32位的,则编译程序也要以32位(x86)运行 
                //如果没有在App.config(或web.config)中配置节点:
                //<system.data><DbProviderFactories><add name="SQLite Data Provider".. /></DbProviderFactories></system.data>..,则需要更改对应的machine.config
                //32位(x86)的 C:\Windows\Microsoft.NET\Framework\v4.0.30319\Config\machine.config
                //64位(x64)的 C:\Windows\Microsoft.NET\Framework64\v4.0.30319\Config\machine.config
                //<remove invariant = "System.Data.SQLite" /><add name = "SQLite Data Provider" invariant = "System.Data.SQLite" description = ".Net Framework Data Provider for SQLite" type = "System.Data.SQLite.SQLiteFactory, System.Data.SQLite" />
                factory = DbProviderFactories.GetFactory(providerName);
            }
            catch (Exception ex)
            {
                Console.WriteLine("加载程序集失败:" + providerName + "\n" + ex.Message, "指定的数据库对应的程序集无法加载");
                factory = null;
            }
            return factory;
        }
    }
}

三、新建数据库操作类DbUtility

using System;
using System.Collections.Generic;
using System.Text;
using System.Data;
using System.Data.Common;
using System.Reflection;
using System.Text.RegularExpressions;

namespace MultiThreadMySqlDemo
{
    /// <summary>
    /// 通用数据库访问类,封装了对数据库的常见操作
    /// Author:斯内科
    /// </summary>
    public sealed class DbUtility
    {
        /// <summary>
        /// 定义一个静态变量来保存该类的实例
        /// </summary>
        private static DbUtility uniqueInstance;
        /// <summary>
        /// 锁对象
        /// </summary>
        private static object thisLock = new object();
        /// <summary>
        /// 数据库连接字符串
        /// </summary>
        public string ConnectionString { get; private set; }
        /// <summary>
        /// 数据库工厂,可以根据不同的数据库类型,实例化出对应的数据源类对象
        /// </summary>
        private DbProviderFactory providerFactory;
        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="connectionString">数据库连接字符串</param>
        /// <param name="providerType">数据库类型枚举</param>
        public DbUtility(string connectionString, DatabaseType providerType)
        {
            ConnectionString = connectionString;
            providerFactory = ProviderFactory.GetDbProviderFactory(providerType);
            if (providerFactory == null)
            {
                throw new ArgumentException("Can't load DbProviderFactory for given value of providerType");
            }
        }

        /// <summary>
        /// 实例化该数据库访问类
        /// </summary>
        /// <param name="connectionString">数据库连接字符串</param>
        /// <param name="dbType">数据库类型字符串,如 SqlServer、Oracle</param>
        /// <returns></returns>
        public static DbUtility GetInstance(string connectionString, string dbType)
        {
            DatabaseType databaseType;
            bool parseResult = Enum.TryParse(dbType, true, out databaseType);
            if (!parseResult)
            {
                throw new Exception("不支持的数据库类型:" + dbType);
            }
            return GetInstance(connectionString, databaseType);
        }

        /// <summary>
        /// 实例化该数据库访问类
        /// </summary>
        /// <param name="connectionString">数据库连接字符串</param>
        /// <param name="databaseType">数据库类型枚举</param>
        /// <returns></returns>
        public static DbUtility GetInstance(string connectionString, DatabaseType databaseType)
        {
            // 如果类的实例不存在则创建,否则直接返回
            if (uniqueInstance == null)
            {
                lock (thisLock)
                {
                    if (uniqueInstance == null)
                    {
                        uniqueInstance = new DbUtility(connectionString, databaseType);
                    }
                }
            }
            return uniqueInstance;
        }

        /// <summary>
        /// 创建一个数据库命令对象
        /// </summary>
        /// <param name="sql">要执行的sql语句 或者 存储过程名</param>
        /// <param name="dict">sql(或存储过程)中的参数以及具体的值</param>
        /// <param name="commandType">命令字符串类型:Text为 SQL文本,StoredProcedure为 存储过程名</param>
        /// <returns></returns>
        public DbCommand CreateCommand(string sql, Dictionary<string, object> dict, CommandType commandType)
        {
            DbConnection connection = providerFactory.CreateConnection();
            connection.ConnectionString = ConnectionString;
            DbCommand command = CreateCommand(sql, dict, commandType, connection);            
            return command;
        }

        /// <summary>
        /// 创建一个数据库命令对象
        /// </summary>
        /// <param name="sql">要执行的sql语句 或者 存储过程名</param>
        /// <param name="dict">sql(或存储过程)中的参数以及具体的值</param>
        /// <param name="commandType">命令字符串类型:Text为 SQL文本,StoredProcedure为 存储过程名</param>
        /// <param name="connection">数据库连接对象</param>
        /// <returns></returns>
        public DbCommand CreateCommand(string sql, Dictionary<string, object> dict, CommandType commandType, DbConnection connection)
        {
            DbCommand command = providerFactory.CreateCommand();
            command.Connection = connection;
            command.CommandType = commandType;
            command.CommandText = sql;
            if (dict != null)
            {
                foreach (string parameterName in dict.Keys)
                {
                    DbParameter dbParameter = command.CreateParameter();
                    dbParameter.ParameterName = parameterName;
                    dbParameter.Value = dict[parameterName];
                    command.Parameters.Add(dbParameter);
                }
            }
            return command;
        }

        /// <summary>
        /// 更新数据,执行增(insert into)、删(delete)、改(update)、创建表(create table)等操作
        /// </summary>
        /// <param name="sql">要执行的sql语句 或者 存储过程名</param>
        /// <param name="dict">sql(或存储过程)中的参数以及具体的值</param>
        /// <param name="commandType">命令字符串类型:Text(默认)为 SQL文本,StoredProcedure为 存储过程名</param>
        /// <returns></returns>
        public int UpdateData(string sql, Dictionary<string, object> dict, CommandType commandType = CommandType.Text)
        {
            int affectedRows = -1;
            using (DbCommand command = CreateCommand(sql, dict, commandType))
            {                
                try
                {
                    command.Connection.Open();
                    affectedRows = command.ExecuteNonQuery();
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.Message, "更改数据时出现异常");
                }
                finally
                {
                    command.Connection.Close();
                }
            }
            return affectedRows;
        }

        /// <summary>
        /// 事务处理
        /// </summary>
        /// <param name="sqlCollection">sql语句集合</param>
        /// <param name="dictCollection">每一条对应sql中的参数以及对应的值,没有参数时为null</param>
        /// <returns></returns>
        public bool ProcessTransaction(List<string> sqlCollection, List<Dictionary<string, object>> dictCollection)
        {
            bool processResult = false;
            if (sqlCollection == null || dictCollection == null || sqlCollection.Count != dictCollection.Count)
            {
                throw new Exception("没有sql指令 或者 参数个数不匹配");
            }
            DbConnection connection = providerFactory.CreateConnection();
            connection.ConnectionString = ConnectionString;
            DbTransaction transaction = null;
            try
            {
                if (connection.State != ConnectionState.Open)
                {
                    connection.Open();
                }
                using (transaction = connection.BeginTransaction())
                {
                    for (int i = 0; i < sqlCollection.Count; i++)
                    {
                        DbCommand command = CreateCommand(sqlCollection[i], dictCollection[i], CommandType.Text, connection);
                        //增加事务与命令的绑定
                        command.Transaction = transaction;
                        command.ExecuteNonQuery();
                    }
                    //提交事务
                    transaction.Commit();
                    processResult = true;
                }
            }
            catch (Exception ex)
            {
                //回滚事务
                if (transaction != null)
                {
                    transaction.Rollback();
                }
                Console.WriteLine(ex.Message, "事务处理时出现异常");
                processResult = false;
            }
            finally
            {
                connection.Close();
            }
            return processResult;
        }

        /// <summary>
        /// 执行查询,并返回查询所返回的结果集中第一行的第一列。所有其他的列和行将被忽略
        /// </summary>
        /// <param name="sql">要执行的sql语句 或者 存储过程名</param>
        /// <param name="dict">sql(或存储过程)中的参数以及具体的值</param>
        /// <param name="commandType">命令字符串类型:Text(默认)为 SQL文本,StoredProcedure为 存储过程名</param>
        /// <returns></returns>        
        public object GetScalar(string sql, Dictionary<string, object> dict, CommandType commandType = CommandType.Text)
        {
            object result = null;
            using (DbCommand command = CreateCommand(sql, dict, commandType))
            {
                try
                {
                    command.Connection.Open();
                    result = command.ExecuteScalar();
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.Message, "获取第一行的第一列数据时出现异常");
                }
                finally
                {
                    command.Connection.Close();
                }
            }
            return result;
        }

        /// <summary>
        /// 执行查询,并返回查询所返回的结果集中第一行的第一列。返回一个字符串
        /// </summary>
        /// <param name="sql">要执行的sql语句 或者 存储过程名</param>
        /// <param name="dict">sql(或存储过程)中的参数以及具体的值</param>
        /// <param name="commandType">命令字符串类型:Text(默认)为 SQL文本,StoredProcedure为 存储过程名</param>
        /// <returns></returns>   
        public string GetScalarString(string sql, Dictionary<string, object> dict, CommandType commandType = CommandType.Text)
        {
            object result = GetScalar(sql, dict, commandType);
            if (result == null)
            {
                return string.Empty;
            }
            return result.ToString();
        }

        /// <summary>
        /// 执行查询,并返回查询所返回的结果集中第一行的第一列。返回一个整数
        /// </summary>
        /// <param name="sql">要执行的sql语句 或者 存储过程名</param>
        /// <param name="dict">sql(或存储过程)中的参数以及具体的值</param>
        /// <param name="commandType">命令字符串类型:Text(默认)为 SQL文本,StoredProcedure为 存储过程名</param>
        /// <returns></returns>   
        public int GetScalarInt(string sql, Dictionary<string, object> dict, CommandType commandType = CommandType.Text)
        {
            object result = GetScalar(sql, dict, commandType);
            if (result == null)
            {
                return 0;
            }
            return Convert.ToInt32(result);
        }

        /// <summary>
        /// 读取数据,注意读取数据完成后,请调用dataReader.Close()进行手动关闭
        /// 代码示例:while (reader.Read()){string result=reader.GetString(0);} reader.Close();
        /// </summary>
        /// <param name="sql">要执行的sql语句 或者 存储过程名</param>
        /// <param name="dict">sql(或存储过程)中的参数以及具体的值</param>
        /// <param name="commandType">命令字符串类型:Text(默认)为 SQL文本,StoredProcedure为 存储过程名</param>
        /// <returns></returns>
        public DbDataReader GetDataReader(string sql, Dictionary<string, object> dict, CommandType commandType = CommandType.Text)
        {
            DbDataReader dataReader = null;
            DbCommand command = CreateCommand(sql, dict, commandType);
            try
            {
                command.Connection.Open();
                dataReader = command.ExecuteReader(CommandBehavior.CloseConnection);            
            }
            catch (Exception ex)
            {
                if (dataReader != null)
                {
                    dataReader.Close();
                }
                command.Connection.Close();
                Console.WriteLine(ex.Message, "获取只读数据流出现错误");
            }
            return dataReader;
        }

        /// <summary>
        /// 获取查询的结果,存入一个数据集中,该函数多用于返回多个数据表的查询
        /// </summary>
        /// <param name="sql">要执行的sql语句 或者 存储过程名</param>
        /// <param name="dict">sql(或存储过程)中的参数以及具体的值</param>
        /// <param name="commandType">命令字符串类型:Text(默认)为 SQL文本,StoredProcedure为 存储过程名</param>
        /// <returns></returns>
        public DataSet GetDataSet(string sql, Dictionary<string, object> dict, CommandType commandType = CommandType.Text)
        {
            DataSet ds = new DataSet("HansDataSet");
            using (DbCommand command = CreateCommand(sql, dict, commandType))
            {
                using (DbDataAdapter adapter = providerFactory.CreateDataAdapter())
                {
                    adapter.SelectCommand = command;
                    try
                    {
                        adapter.Fill(ds);
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine(ex.Message, "查询数据时出现异常");
                    }
                }
            }
            return ds;
        }

        /// <summary>
        /// 获取查询的结果,存入一个数据表中
        /// </summary>
        /// <param name="sql">要执行的sql语句 或者 存储过程名</param>
        /// <param name="dict">sql(或存储过程)中的参数以及具体的值</param>
        /// <param name="commandType">命令字符串类型:Text(默认)为 SQL文本,StoredProcedure为 存储过程名</param>
        /// <returns></returns>
        public DataTable GetDataTable(string sql, Dictionary<string, object> dict, CommandType commandType = CommandType.Text)
        {
            DataTable dt = new DataTable("HansDataTable");
            using (DbCommand command = CreateCommand(sql, dict, commandType))
            {
                using (DbDataAdapter adapter = providerFactory.CreateDataAdapter())
                {
                    adapter.SelectCommand = command;
                    try
                    {
                        adapter.Fill(dt);
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine(ex.Message, "查询数据时出现异常");
                    }
                }
            }
            return dt;
        }

        /// <summary>
        /// 获取数据表某一行数据,多用于获取数据库中某一个唯一的ID的数据行
        /// </summary>
        /// <param name="sql">要执行的sql语句 或者 存储过程名</param>
        /// <param name="dict">sql(或存储过程)中的参数以及具体的值</param>
        /// <param name="commandType">命令字符串类型:Text(默认)为 SQL文本,StoredProcedure为 存储过程名</param>
        /// <returns></returns>
        public DataRow GetDataRow(string sql, Dictionary<string, object> dict, CommandType commandType = CommandType.Text)
        {
            DataTable dt = GetDataTable(sql, dict, commandType);
            if (dt != null && dt.Rows.Count > 0)
            {
                return dt.Rows[0];
            }
            return null;
        }

    }
}

四、默认的Program测试程序如下:

using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace MultiThreadMySqlDemo
{
    class Program
    {
        //多线程并发mysql读写
        const string connectionString = "server=127.0.0.1;Database=testx;Uid=root;Pwd=root";
        static DbUtility dbUtility = DbUtility.GetInstance(connectionString, DatabaseType.MySql);
        static void Main(string[] args)
        {
            try
            {
                InitCreateTable();
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }

            Task.Factory.StartNew(() =>
            {
                for (int i = 0; i < 10; i++)
                {
                    PrintCurrentData(GetCurrentDataWithLock);
                    PrintCurrentData(GetCurrentData);
                    Thread.Sleep(500);
                }
            });
            Task.Run(() =>
            {
                for (int i = 0; i < 10; i++)
                {
                    ModifyData();
                    Thread.Sleep(500);
                }
            });
            Console.ReadLine();
        }

        /// <summary>
        /// 初始化创建数据库
        /// </summary>
        private static void InitCreateTable()
        {
            string sqlCreateTable = @"CREATE TABLE IF NOT EXISTS `busbar_measure_fai` (
  `CoreId` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键自增编号',
  `ModuleBarcode` varchar(100) NOT NULL DEFAULT '' COMMENT '模组条码',
  `EquipmentNo` varchar(50) NOT NULL DEFAULT '' COMMENT '设备编号',
  `ProcessEndTime` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '处理结束时间',
  `ProcessResult` varchar(10) NOT NULL DEFAULT '' COMMENT '处理结果',
  `MeasureDistance1` float NOT NULL DEFAULT '0' COMMENT '首件测距1',
  `MeasureDistance2` float NOT NULL DEFAULT '0' COMMENT '首件测距2',
  `MeasureDistance3` float NOT NULL DEFAULT '0' COMMENT '首件测距3',
  PRIMARY KEY (`CoreId`),
  KEY `IDX_BSB_WELD_FAI_TIME` (`ProcessEndTime`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='首件测距表';";
            dbUtility.UpdateData(sqlCreateTable, null);
        }

        /// <summary>
        /// 获得当前表的全部数据
        /// </summary>
        /// <returns></returns>
        private static DataTable GetCurrentData()
        {
            return dbUtility.GetDataTable("select CoreId,ModuleBarcode,ProcessEndTime from busbar_measure_fai", null);
        }

        /// <summary>
        /// 获得当前表的全部数据【增加锁】
        /// from tb lock in share mode
        /// </summary>
        /// <returns></returns>
        private static DataTable GetCurrentDataWithLock()
        {
            return dbUtility.GetDataTable("select CoreId,ModuleBarcode,ProcessEndTime from busbar_measure_fai lock in share mode", null);
        }

        /// <summary>
        /// 打印当前数据
        /// </summary>
        /// <param name="funcGetData">一个返回当前数据表的方法</param>
        private static void PrintCurrentData(Func<DataTable> funcGetData)
        {
            DataTable dataTable = funcGetData();
            Console.WriteLine($"读取当前数据行数:【{dataTable.Rows.Count}】");
            for (int i = 0; i < dataTable.Rows.Count; i++)
            {
                Console.WriteLine($"----行【{i + 1}】:【{dataTable.Rows[i]["CoreId"]},{dataTable.Rows[i]["ModuleBarcode"]},{dataTable.Rows[i]["ProcessEndTime"]}】");
            }
        }

        /// <summary>
        /// 删除指定的数据后再添加
        /// </summary>
        /// <param name="moduleBarcode"></param>
        /// <returns></returns>
        private static int DeleteAndAddData(string moduleBarcode)
        {
            string sqlUpdate = @"delete from busbar_measure_fai where ModuleBarcode=?ModuleBarcode;
insert into busbar_measure_fai (ModuleBarcode,EquipmentNo,ProcessEndTime,ProcessResult,MeasureDistance1,MeasureDistance2,MeasureDistance3) 
values (?ModuleBarcode,?EquipmentNo,?ProcessEndTime,?ProcessResult,?MeasureDistance1,?MeasureDistance2,?MeasureDistance3)";
            Dictionary<string, object> dict = new Dictionary<string, object>();
            dict.Add("ModuleBarcode", moduleBarcode);
            dict.Add("EquipmentNo", "XX焊接");
            dict.Add("ProcessEndTime", DateTime.Now);
            dict.Add("ProcessResult", "OK");
            dict.Add("MeasureDistance1", 1.1F);
            dict.Add("MeasureDistance2", 2.2F);
            dict.Add("MeasureDistance3", 3.3F);
            return dbUtility.UpdateData(sqlUpdate, dict);
        }

        /// <summary>
        /// 更新数据
        /// </summary>
        public static void ModifyData()
        {
            Random random = new Random(Guid.NewGuid().GetHashCode());
            int affectRow = DeleteAndAddData($"M{random.Next(1, 100).ToString("D2")}");
            Console.WriteLine($"更新当前数据,受影响的行数:【{affectRow}】");
        }
    }
}

五、程序运行如图:

猜你喜欢

转载自blog.csdn.net/ylq1045/article/details/120651333