MySqlDbMng.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524
  1. using Dapper;
  2. using System;
  3. using System.Collections.Generic;
  4. using System.Data;
  5. using System.IO;
  6. using System.Linq;
  7. using System.Text;
  8. using Vit.Core.Util.Common;
  9. using Vit.Extensions;
  10. using SqlConnection = MySql.Data.MySqlClient.MySqlConnection;
  11. namespace Vit.Db.DbMng
  12. {
  13. public class MySqlDbMng : BaseDbMng<SqlConnection>
  14. {
  15. #region 构造函数
  16. /// <summary>
  17. ///
  18. /// </summary>
  19. public MySqlDbMng(SqlConnection conn, string BackupPath = null):base(conn)
  20. {
  21. oriConnectionString = conn.ConnectionString;
  22. if (string.IsNullOrWhiteSpace(BackupPath))
  23. {
  24. BackupPath = CommonHelp.GetAbsPath("Data", "MySqlBackup");
  25. }
  26. this.BackupPath = BackupPath;
  27. dbName = new MySql.Data.MySqlClient.MySqlConnectionStringBuilder(conn.ConnectionString).Database;
  28. }
  29. #endregion
  30. #region 成员变量
  31. string oriConnectionString;
  32. /// <summary>
  33. /// 数据库名称
  34. /// </summary>
  35. private string dbName { get; set; } = null;
  36. #endregion
  37. #region 备份文件夹
  38. /// <summary>
  39. /// 数据库备份文件的文件夹路径。例:@"F:\\db"
  40. /// </summary>
  41. private string BackupPath { get; set; }
  42. #region BackupFile_GetPathByName
  43. public string BackupFile_GetPathByName(string fileName)
  44. {
  45. return Path.Combine(BackupPath, fileName);
  46. }
  47. #endregion
  48. #region BackupFile_GetFileInfos
  49. /// <summary>
  50. /// <para>获取所有备份文件的信息</para>
  51. /// <para>返回的DataTable的列分别为 Name(包含后缀)、Remark、Size</para>
  52. /// </summary>
  53. /// <returns></returns>
  54. public List<BackupFileInfo> BackupFile_GetFileInfos()
  55. {
  56. DirectoryInfo bakDirectory = new DirectoryInfo(BackupPath);
  57. if (!bakDirectory.Exists)
  58. {
  59. return new List<BackupFileInfo>();
  60. }
  61. return bakDirectory.GetFiles().Select(f => new BackupFileInfo { fileName = f.Name, size = f.Length / 1024.0f / 1024.0f, createTime = f.CreationTime }).ToList();
  62. }
  63. #endregion
  64. #endregion
  65. #region Exec
  66. public T Exec<T>(Func<IDbConnection, T> run)
  67. {
  68. try
  69. {
  70. var builder = new MySql.Data.MySqlClient.MySqlConnectionStringBuilder(oriConnectionString);
  71. builder.Database = "";
  72. conn.ConnectionString = builder.ToString();
  73. return run(conn);
  74. }
  75. finally
  76. {
  77. conn.ConnectionString = oriConnectionString;
  78. }
  79. }
  80. #endregion
  81. #region Quote
  82. protected override string Quote(string name)
  83. {
  84. return conn.Quote(name);
  85. }
  86. #endregion
  87. #region GetAllDataBase 获取所有数据库
  88. /// <summary>
  89. /// 获取所有数据库
  90. /// </summary>
  91. /// <returns></returns>
  92. public DataTable GetAllDataBase()
  93. {
  94. return Exec((conn) =>
  95. {
  96. return conn.ExecuteDataSet("show databases").Tables[0];
  97. });
  98. }
  99. #endregion
  100. #region GetDataBaseState 获取数据库状态
  101. /// <summary>
  102. /// 获取数据库状态(online、none、unknow)
  103. /// </summary>
  104. /// <returns></returns>
  105. public override EDataBaseState GetDataBaseState()
  106. {
  107. try
  108. {
  109. if (null != Exec(conn => conn.ExecuteScalar("show databases like @dbName", new { dbName = dbName })))
  110. {
  111. return EDataBaseState.online;
  112. }
  113. }
  114. catch
  115. {
  116. return EDataBaseState.unknow;
  117. }
  118. return EDataBaseState.none;
  119. }
  120. #endregion
  121. #region 获取数据库当前连接数
  122. /// <summary>
  123. /// 获取数据库当前连接数
  124. /// </summary>
  125. /// <param name="dbName"></param>
  126. /// <returns></returns>
  127. public int GetProcessCount(string dbName = null)
  128. {
  129. // show full processlist ;
  130. // select id, db, user, host, command, time, state, info from information_schema.processlist order by time desc
  131. return Convert.ToInt32(Exec(conn => conn.ExecuteScalar("select count(*) from information_schema.processlist where db=@dbName", new { dbName = dbName ?? this.dbName })));
  132. }
  133. #endregion
  134. #region GetDataBaseVersion
  135. public string GetDataBaseVersion()
  136. {
  137. try
  138. {
  139. return conn.ExecuteScalar<string>("select version()");
  140. }
  141. catch
  142. {
  143. }
  144. return null;
  145. }
  146. #endregion
  147. #region CreateDataBase 创建数据库
  148. /// <summary>
  149. /// 创建数据库
  150. /// </summary>
  151. public override void CreateDataBase()
  152. {
  153. Exec(conn => conn.Execute("create database " + Quote(dbName)));
  154. }
  155. #endregion
  156. #region DropDataBase 删除数据库
  157. /// <summary>
  158. /// 删除数据库
  159. /// </summary>
  160. public override void DropDataBase()
  161. {
  162. Exec(conn => conn.Execute("drop database " + Quote(dbName)));
  163. }
  164. #endregion
  165. #region BuildCreateDataBaseSql
  166. /// <summary>
  167. /// 构建建库语句
  168. /// </summary>
  169. /// <returns></returns>
  170. public override string BuildCreateDataBaseSql()
  171. {
  172. // show命令可以提供关于数据库、表、列,或关于服务器的状态信息
  173. // https://www.cnblogs.com/Rohn/p/11072228.html
  174. StringBuilder builder = new StringBuilder();
  175. string dbName = conn.ExecuteScalar("select database()") as string;
  176. string delimiter = "/*GO*/";
  177. #region (x.1)构建标头 备份时间、数据库版本、数据库名称
  178. {
  179. builder.AppendLine("-- (x.1)备份信息");
  180. builder.AppendLine("-- 备份时间 :" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
  181. builder.AppendLine("-- MySql版本 :" + GetDataBaseVersion());
  182. builder.AppendLine("-- 数据库名称:" + dbName);
  183. builder.AppendLine("-- DELIMITER " + delimiter);
  184. }
  185. #endregion
  186. #region (x.2)建表
  187. {
  188. builder.AppendLine();
  189. builder.AppendLine();
  190. builder.AppendLine();
  191. builder.AppendLine("-- (x.2)建表");
  192. var names = conn.MySql_GetAllTableName();
  193. int index = 0;
  194. foreach (var name in names)
  195. {
  196. builder.AppendLine(" -- (x.x." + (++index) + ")创建表 " + name);
  197. #region(x.x.x.1)建表语句
  198. {
  199. var dt = conn.ExecuteDataTable("SHOW CREATE table " + Quote(name));
  200. string sql = dt.Rows[0][1] as string;
  201. builder.Append(sql).AppendLine(";");
  202. builder.AppendLine(delimiter);
  203. builder.AppendLine();
  204. }
  205. #endregion
  206. //建表语句已经制定索引,无需再次创建
  207. continue;
  208. #region(x.x.x.2)创建索引语句
  209. {
  210. #region const builder
  211. string indexBuilderSql = @"SELECT
  212. CONCAT('ALTER TABLE `',TABLE_NAME,'` ', 'ADD ',
  213. IF(NON_UNIQUE = 1,
  214. CASE UPPER(INDEX_TYPE)
  215. WHEN 'FULLTEXT' THEN 'FULLTEXT INDEX'
  216. WHEN 'SPATIAL' THEN 'SPATIAL INDEX'
  217. ELSE CONCAT('INDEX `',
  218. INDEX_NAME,
  219. '` USING ',
  220. INDEX_TYPE
  221. )
  222. END,
  223. IF(UPPER(INDEX_NAME) = 'PRIMARY',
  224. CONCAT('PRIMARY KEY USING ',
  225. INDEX_TYPE
  226. ),
  227. CONCAT('UNIQUE INDEX `',
  228. INDEX_NAME,
  229. '` USING ',
  230. INDEX_TYPE
  231. )
  232. )
  233. ),'(', GROUP_CONCAT(DISTINCT CONCAT('`', COLUMN_NAME, '`') ORDER BY SEQ_IN_INDEX ASC SEPARATOR ', '), ');') AS 'Show_Add_Indexes'
  234. FROM information_schema.STATISTICS
  235. WHERE TABLE_SCHEMA = @dbName and TABLE_NAME=@tableName
  236. -- and UPPER(INDEX_NAME) != 'PRIMARY' -- 剔除主码
  237. GROUP BY TABLE_NAME, INDEX_NAME
  238. ORDER BY TABLE_NAME ASC, INDEX_NAME ASC;";
  239. #endregion
  240. var sqlList = conn.Query<string>(indexBuilderSql, new { dbName = dbName, tableName = name }).ToList();
  241. foreach (var sql in sqlList)
  242. {
  243. builder.Append(sql).AppendLine(";");
  244. builder.AppendLine(delimiter);
  245. builder.AppendLine();
  246. }
  247. }
  248. #endregion
  249. }
  250. }
  251. #endregion
  252. #region (x.3)创建触发器
  253. {
  254. builder.AppendLine();
  255. builder.AppendLine();
  256. builder.AppendLine();
  257. builder.AppendLine("-- (x.3)创建触发器");
  258. var names = conn.Query<string>("show TRIGGERS;").ToList();
  259. var index = 0;
  260. foreach (var name in names)
  261. {
  262. builder.AppendLine(" -- (x.x." + (++index) + ")创建触发器 " + name);
  263. var dt = conn.ExecuteDataTable("SHOW CREATE TRIGGER " + Quote(name));
  264. string sql = dt.Rows[0][2] as string;
  265. builder.Append(sql).AppendLine(";");
  266. builder.AppendLine(delimiter);
  267. builder.AppendLine();
  268. }
  269. }
  270. #endregion
  271. #region (x.4)创建事件
  272. {
  273. builder.AppendLine();
  274. builder.AppendLine();
  275. builder.AppendLine();
  276. builder.AppendLine("-- (x.4)创建事件");
  277. var dtEvents = conn.ExecuteDataTable("show EVENTs;");
  278. var index = 0;
  279. foreach (DataRow row in dtEvents.Rows)
  280. {
  281. if (row["Db"].ToString() != dbName) continue;
  282. var name = row["Name"].ToString();
  283. var enabled = row["Status"].ToString().ToUpper() == "ENABLED";
  284. //(x.x.1)创建事件
  285. {
  286. builder.AppendLine(" -- (x.x." + (++index) + ")创建事件 " + name);
  287. var dt = conn.ExecuteDataTable("SHOW CREATE EVENT " + Quote(name));
  288. string sql = dt.Rows[0][3] as string;
  289. builder.Append(sql).AppendLine(";");
  290. builder.AppendLine(delimiter);
  291. builder.AppendLine();
  292. }
  293. //(x.x.2)启用事件
  294. if (enabled)
  295. {
  296. builder.AppendLine(" -- (x.x." + index + ")启用事件 " + name);
  297. string sql = "ALTER EVENT " + Quote(name) + " ON COMPLETION PRESERVE ENABLE";
  298. builder.Append(sql).AppendLine(";");
  299. builder.AppendLine(delimiter);
  300. builder.AppendLine();
  301. }
  302. }
  303. }
  304. #endregion
  305. #region (x.5)创建函数
  306. {
  307. builder.AppendLine();
  308. builder.AppendLine();
  309. builder.AppendLine();
  310. builder.AppendLine("-- (x.5)创建函数");
  311. var dtName = conn.ExecuteDataTable("show FUNCTION status;");
  312. var index = 0;
  313. foreach (DataRow row in dtName.Rows)
  314. {
  315. if (row["Db"].ToString() != dbName) continue;
  316. var name = row["Name"].ToString();
  317. builder.AppendLine(" -- (x.x." + (++index) + ")创建函数 " + name);
  318. var dt = conn.ExecuteDataTable("SHOW CREATE FUNCTION " + Quote(name));
  319. string sql = dt.Rows[0][2] as string;
  320. builder.Append(sql).AppendLine(";");
  321. builder.AppendLine(delimiter);
  322. builder.AppendLine();
  323. }
  324. }
  325. #endregion
  326. #region (x.6)创建存储过程
  327. {
  328. builder.AppendLine();
  329. builder.AppendLine();
  330. builder.AppendLine();
  331. builder.AppendLine("-- (x.6)创建存储过程");
  332. var dtName = conn.ExecuteDataTable("show procedure status WHERE Db = @dbName AND `Type` = 'PROCEDURE'", new { dbName = dbName });
  333. var index = 0;
  334. foreach (DataRow row in dtName.Rows)
  335. {
  336. var name = row["Name"].ToString();
  337. builder.AppendLine(" -- (x.x." + (++index) + ")创建存储过程 " + name);
  338. var dt = conn.ExecuteDataTable("SHOW CREATE procedure " + Quote(name));
  339. string sql = dt.Rows[0][2] as string;
  340. builder.Append(sql).AppendLine(";");
  341. builder.AppendLine(delimiter);
  342. builder.AppendLine();
  343. }
  344. }
  345. #endregion
  346. #region (x.7)创建视图
  347. {
  348. builder.AppendLine();
  349. builder.AppendLine();
  350. builder.AppendLine();
  351. builder.AppendLine("-- (x.7)创建视图");
  352. //var dtName = conn.ExecuteDataTable("SELECT TABLE_NAME as Name from information_schema.VIEWS;");
  353. var dtName = conn.ExecuteDataTable("SELECT TABLE_NAME as Name from information_schema.VIEWS where Table_Schema=@dbName", new { dbName = dbName });
  354. var index = 0;
  355. foreach (DataRow row in dtName.Rows)
  356. {
  357. var name = row["Name"].ToString();
  358. builder.AppendLine(" -- (x.x." + (++index) + ")创建存储过程 " + name);
  359. var dt = conn.ExecuteDataTable("SHOW CREATE view " + Quote(name) );
  360. string sql = dt.Rows[0][1] as string;
  361. builder.Append(sql).AppendLine(";");
  362. builder.AppendLine(delimiter);
  363. builder.AppendLine();
  364. }
  365. }
  366. #endregion
  367. //builder.AppendLine("-- DELIMITER ;");
  368. return builder.ToString();
  369. }
  370. #endregion
  371. #region BackupSqler
  372. /// <summary>
  373. /// 备份数据库
  374. /// </summary>
  375. /// <param name="filePath">备份的文件路径,若不指定则自动构建。demo:@"F:\\website\appdata\dbname_2020-02-02_121212.bak"</param>
  376. /// <param name="useMemoryCache">是否使用内存进行全量缓存,默认:true。缓存到内存可以加快备份速度。在数据源特别庞大时请禁用此功能。</param>
  377. /// <returns>备份的文件路径</returns>
  378. public override string BackupSqler(string filePath = null, bool useMemoryCache = true)
  379. {
  380. if (string.IsNullOrEmpty(filePath)) filePath = Path.Combine(BackupPath, GenerateBackupFileNameBackupFileName(dbName));
  381. base.BackupSqler(filePath, useMemoryCache: useMemoryCache);
  382. return filePath;
  383. }
  384. #endregion
  385. #region GenerateBackupFileName
  386. static string GenerateBackupFileNameBackupFileName(string dbName)
  387. {
  388. // dbname_2010-02-02_121212.zip
  389. return $"{dbName}_{DateTime.Now.ToString("yyyy-MM-dd")}_{DateTime.Now.ToString("HHmmss")}.zip";
  390. }
  391. #endregion
  392. #region Restore
  393. /// <summary>
  394. /// 远程还原数据库
  395. /// </summary>
  396. /// <param name="filePath">数据库备份文件的路径</param>
  397. /// <returns>备份文件的路径</returns>
  398. public void Restore(string filePath)
  399. {
  400. RestoreSqler(filePath);
  401. }
  402. #endregion
  403. }
  404. }