UPDATE语句内用于状态码处理的SELECT语句的替代方法

huangapple go评论106阅读模式
英文:

Alternatives to UPDATE within a SELECT statement for status code processing

问题

在SQL语句中,您不能在SELECT语句内嵌套UPDATE语句。UPDATE语句不返回数据,而SELECT语句不修改数据。我找到了2个选项,但都不是很好:准备好的语句事务或在我的代码中同步调用数据库。

我正在寻找用于处理状态代码的替代方法:我想找到具有最低idcode=0的记录,然后以原子方式和线程安全地保留该记录的id并将code设置为1。我希望有一种方法来更新单行并保留rowid。我有多个线程尝试获取下一个值,并希望防止2个线程处理相同的记录。似乎SQLite将提供最后插入但未更新的rowid。我不确定它是否线程安全,也不确定sqlite3_changes()是否线程安全。

我的SQLite3表(iOS 14.0,Swift):

  1. CREATE TABLE IF NOT EXISTS Transactions (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, url TEXT NOT NULL, code INTEGER NOT NULL)
id url code
1 https:/x.com/?a=1 0
2 https:/x.com/?a=2 0
3 https:/x.com/?a=3 0

然后:

  1. BEGIN;
  2. SELECT id,url FROM Transactions WHERE code=0 ORDER BY id ASC LIMIT 1;
  3. // 读取 'id' 的值
  4. UPDATE Transactions SET code=1 WHERE code=0 AND id='id';
  5. COMMIT;

我希望完全在SQL中执行此操作。我知道SQLite有一些限制,比如没有SELECT... FOR UPDATE。我发现在iOS上在同步调度队列中运行这个操作(假设只有一个线程),不使用准备好的语句会运行96小时,只使用82MB的RAM,但是使用准备好的语句会导致应用程序在56小时内崩溃,RAM使用量为1.81GB(请参阅本问题底部的代码 - 我保持数据库连接打开,并为每个函数调用中的每个语句创建一个OpaquePointer,使用_v2()准备语句,并使用sqlite3_finalize()完成语句)。请帮助我找到另一种选择,因为我不想假设同步。

SQLite3准备语句:

  1. // 检索下一个设备交易 - 如果没有记录则返回空字符串
  2. class func getNextDeviceTransaction() throws -> String {
  3. // 代码已省略
  4. }

iOS调度队列:

  1. // 从同步调度队列调用 - 检索下一个设备交易 - 如果没有记录则返回空字符串
  2. class func getNextDeviceTransaction() throws -> String {
  3. // 代码已省略
  4. }
英文:

You cannot embed an UPDATE within a SELECT statement. UPDATE's do not return data and SELECT's do not modify data. I found 2 options but neither is great: prepared statement transactions or synchronizing the calls to the database in my code.

I am looking for alternatives for status code processing: I want to find the record with the lowest id that has code=0, then atomically and thread safely retain that record's id and set code to 1. I want a way to update a single row and retain the rowid. I have multiple threads attempting to get the next value and want to safeguard against 2 threads processing the same record. It seems SQLite will give rowid of the last row inserted but not updated. I'm not sure if it's thread safe or not. I'm also not positive that sqlite3_changes() is thread safe.

My table in SQLite3 (iOS 14.0, Swift):

  1. CREATE TABLE IF NOT EXISTS Transactions (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, url TEXT NOT NULL, code INTEGER NOT NULL)
id url code
1 https:/x.com/?a=1 0
2 https:/x.com/?a=2 0
3 https:/x.com/?a=3 0

I then:

  1. BEGIN;
  2. SELECT id,url FROM Transactions WHERE code=0 ORDER BY id ASC LIMIT 1;
  3. // Read the 'id' in code
  4. UPDATE Transactions SET code=1 WHERE code=0 AND id='id';
  5. COMMIT;

I would like to do this entirely in SQL. I know SQLite has limitations like no SELECT... FOR UPDATE. I found that running this on iOS in a synchronous dispatch queue without prepared statement (assuming only one thread) will run for 96 hours with only 82MB of RAM used, but the prepared statement results in the app crashing with 1.81GB of RAM usage in 56 hours (code at bottom of this question - I keep a database connection open and create an OpaquePointer for each statement in each function call, prepare with _v2() and finalize the statement with sqlite3_finalize()). Help me out with another option since I'd like to not assume synchronization.

SQLite3 prepared statement:

  1. // Retrieve the Next Device Transaction - Returns Blank String if No Records Remain
  2. class func getNextDeviceTransaction() throws -> String {
  3. // Database Statement and Value Buffers
  4. var stmt: OpaquePointer?
  5. var id = -1
  6. var url = ""
  7. // Prepare the Begin
  8. if sqlite3_prepare_v2( db, "BEGIN", -1, &stmt, nil ) != SQLITE_OK {
  9. let errorMessage = String( cString: sqlite3_errmsg( db )! )
  10. sqlite3_finalize( stmt ) // Finalize the Prepared Statement to Avoid Memory Leaks - https://www.sqlite.org/malloc.html
  11. throw NSError( domain: "com.", code: 921, userInfo: [ "Error": "Error Preparing Begin Transaction: \( errorMessage )" ] )
  12. }
  13. // Begin the Transaction
  14. if sqlite3_step( stmt ) != SQLITE_DONE {
  15. let errorMessage = String( cString: sqlite3_errmsg( db )! )
  16. sqlite3_finalize( stmt ) // Finalize the Prepared Statement to Avoid Memory Leaks - https://www.sqlite.org/malloc.html
  17. throw NSError( domain: "com.", code: 922, userInfo: [ "Error": "Database Transaction Malfunction: \( errorMessage )" ] )
  18. }
  19. // Select Query
  20. var queryString = "SELECT id,url FROM Transactions WHERE code=0 ORDER BY id ASC LIMIT 1"
  21. // Prepare the Query
  22. if sqlite3_prepare_v2( db, queryString, -1, &stmt, nil ) != SQLITE_OK {
  23. let errorMessage = String( cString: sqlite3_errmsg( db )! )
  24. sqlite3_finalize( stmt ) // Finalize the Prepared Statement to Avoid Memory Leaks - https://www.sqlite.org/malloc.html
  25. throw NSError( domain: "com.", code: 923, userInfo: [ "Error": "Error Preparing Select: \( errorMessage )" ] )
  26. }
  27. // Traverse Through Records
  28. if sqlite3_step( stmt ) == SQLITE_ROW {
  29. // Retrieve Value and Return
  30. id = Int( sqlite3_column_int( stmt, 0 ) )
  31. url = String( cString: sqlite3_column_text( stmt, 1 ) )
  32. }
  33. // Evaluate if No Records Found
  34. if id == -1 || url == "" {
  35. // Rollback
  36. sqlite3_prepare_v2( db, "ROLLBACK", -1, &stmt, nil )
  37. sqlite3_step( stmt )
  38. // Finalize the Prepared Statement to Avoid Memory Leaks - https://www.sqlite.org/malloc.html
  39. sqlite3_finalize( stmt )
  40. // No Records Exist
  41. return ""
  42. }
  43. // Select Query
  44. queryString = "UPDATE Transactions SET code=1 WHERE code=0 AND id=\( id )"
  45. // Prepare the Update Query
  46. if sqlite3_prepare_v2( db, queryString, -1, &stmt, nil ) != SQLITE_OK {
  47. let errorMessage = String( cString: sqlite3_errmsg( db )! )
  48. sqlite3_finalize( stmt ) // Finalize the Prepared Statement to Avoid Memory Leaks - https://www.sqlite.org/malloc.html
  49. throw NSError( domain: "com.", code: 924, userInfo: [ "Error": "Error Preparing Update: \( errorMessage )" ] )
  50. }
  51. // Execute the Update
  52. if sqlite3_step( stmt ) != SQLITE_DONE {
  53. let errorMessage = String( cString: sqlite3_errmsg( db )! )
  54. // Rollback
  55. sqlite3_prepare( db, "ROLLBACK", -1, &stmt, nil )
  56. sqlite3_step( stmt )
  57. sqlite3_finalize( stmt ) // Finalize the Prepared Statement to Avoid Memory Leaks - https://www.sqlite.org/malloc.html
  58. throw NSError( domain: "com.", code: 925, userInfo: [ "Error": "Transaction Update Malfunction: \( errorMessage )" ] )
  59. }
  60. // Prepare the Commit
  61. if sqlite3_prepare_v2( db, "COMMIT", -1, &stmt, nil ) != SQLITE_OK {
  62. let errorMessage = String( cString: sqlite3_errmsg( db )! )
  63. // Rollback
  64. sqlite3_prepare_v2( db, "ROLLBACK", -1, &stmt, nil )
  65. sqlite3_step( stmt )
  66. sqlite3_finalize( stmt ) // Finalize the Prepared Statement to Avoid Memory Leaks - https://www.sqlite.org/malloc.html
  67. throw NSError( domain: "com.", code: 926, userInfo: [ "Error": "Error Preparing Commit: \( errorMessage )" ] )
  68. }
  69. // Commit the Transaction
  70. if sqlite3_step( stmt ) != SQLITE_DONE {
  71. let errorMessage = String( cString: sqlite3_errmsg( db )! )
  72. // Rollback
  73. sqlite3_prepare_v2( db, "ROLLBACK", -1, &stmt, nil )
  74. sqlite3_step( stmt )
  75. sqlite3_finalize( stmt ) // Finalize the Prepared Statement to Avoid Memory Leaks - https://www.sqlite.org/malloc.html
  76. throw NSError( domain: "com.", code: 927, userInfo: [ "Error": "Database Commit Transaction Malfunction: \( errorMessage )" ] )
  77. }
  78. // Confirm a Single Row Touched
  79. if sqlite3_changes( db ) != 1 {
  80. let errorMessage = String( cString: sqlite3_errmsg( db )! )
  81. sqlite3_finalize( stmt ) // Finalize the Prepared Statement to Avoid Memory Leaks - https://www.sqlite.org/malloc.html
  82. throw NSError( domain: "com.", code: ALLOWABLE_DATABASE_COLLISION_ERROR, userInfo: [ "Error": "Database Update Count Malfunction or Simple Transaction Collision: \( errorMessage )" ] ) // 928
  83. }
  84. // Finalize the Prepared Statement to Avoid Memory Leaks - https://www.sqlite.org/malloc.html
  85. sqlite3_finalize( stmt )
  86. // Return Next Available URL
  87. return url
  88. }

iOS dispatch queue:

  1. // Retrieve the Next Device Transaction - Returns Blank String if No Records Remain - MUST BE CALLED FROM SYNCHRONIZED DISPATCH QUEUE
  2. class func getNextDeviceTransaction() throws -> String {
  3. // Database Statement and Value Buffers
  4. var stmt: OpaquePointer?
  5. var id: Int = -1
  6. var url: String = ""
  7. // Select Query
  8. var queryString = "SELECT id,url FROM Transactions WHERE code=0 ORDER BY id ASC LIMIT 1"
  9. // Prepare the Query
  10. if sqlite3_prepare_v2( db, queryString, -1, &stmt, nil ) != SQLITE_OK {
  11. // Finalize the Prepared Statement to Avoid Memory Leaks - https://www.sqlite.org/malloc.html
  12. sqlite3_finalize( stmt )
  13. let errorMessage = String( cString: sqlite3_errmsg( db )! )
  14. print( "Error Preparing Select: \( errorMessage )" )
  15. throw NSError( domain: "com.", code: 921, userInfo: [ "Error": "Error Querying Device Transactions: \( errorMessage )" ] )
  16. }
  17. // Traverse Through the Single Record
  18. if sqlite3_step( stmt ) == SQLITE_ROW {
  19. // Retrieve IDs and URLs
  20. id = Int( sqlite3_column_int( stmt, 0 ) )
  21. url = String( cString: sqlite3_column_text( stmt, 1 ) )
  22. // Finalize the Prepared Statement to Avoid Memory Leaks - https://www.sqlite.org/malloc.html
  23. sqlite3_finalize( stmt )
  24. // Evaluate IDs and URLs
  25. if id > 0 && url != "" {
  26. // Update Query to Synchronously Set the Records Status Code
  27. queryString = "UPDATE Transactions SET code=1 WHERE code=0 AND id=\( id )"
  28. // Prepare the Update Query
  29. if sqlite3_exec( db, queryString, nil, nil, nil ) != SQLITE_OK {
  30. let errorMessage = String( cString: sqlite3_errmsg( db )! )
  31. print( "Error Preparing Update: \( errorMessage )" )
  32. throw NSError( domain: "com.", code: 922, userInfo: [ "Error": "Error Setting Transaction Status Code: \( errorMessage )" ] )
  33. }
  34. // Confirm a Single Row Touched
  35. if sqlite3_changes( db ) == 1 {
  36. // Success - Return the Next Record's URL
  37. return url
  38. }
  39. else {
  40. let errorMessage = String( cString: sqlite3_errmsg( db )! )
  41. print( "Device Transaction Not Captured: \( errorMessage )" )
  42. throw NSError( domain: "com.", code: 922, userInfo: [ "Error": "Device Transaction Not Captured: \( errorMessage )" ] )
  43. }
  44. }
  45. }
  46. // Finalize the Prepared Statement to Avoid Memory Leaks - https://www.sqlite.org/malloc.html
  47. sqlite3_finalize( stmt )
  48. // No Records Exist
  49. return ""
  50. }

答案1

得分: 0

自SQLite版本3.35.0开始,支持RETURNING子句:

  1. UPDATE transactions
  2. SET code = 1
  3. WHERE id = (SELECT MIN(id) FROM transactions WHERE code = 0)
  4. RETURNING id;

请参阅演示

英文:

> Basically I want a way to update a single row and retain the rowID of
> what I updated

Since version 3.35.0 SQLite supports the RETURNING clause:

  1. UPDATE transactions
  2. SET code = 1
  3. WHERE id = (SELECT MIN(id) FROM transactions WHERE code = 0)
  4. RETURNING id;

See the demo.<br/>

huangapple
  • 本文由 发表于 2023年7月11日 06:23:18
  • 转载请务必保留本文链接:https://go.coder-hub.com/76657692.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定